Skip to content

Commit dfa0bdb

Browse files
committed
Migrate to Firestore and add Base.al OAuth authentication
- Replaced PostgreSQL with Firestore - Added Base.al OAuth integration - Created Firestore security rules - Updated all API routes for Firestore - Added OAuth handlers and UI components
1 parent 6309a22 commit dfa0bdb

23 files changed

Lines changed: 8448 additions & 3506 deletions

.env.example

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ NUXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_messaging_sender_id
77
NUXT_PUBLIC_FIREBASE_APP_ID=your_app_id
88

99
# Firebase Admin (Server-side only)
10+
# Get these from Firebase Console > Project Settings > Service Accounts > Generate Private Key
1011
FIREBASE_PRIVATE_KEY=your_private_key
1112
FIREBASE_CLIENT_EMAIL=your_client_email
1213

13-
# Database
14-
DATABASE_URL=postgresql://user:password@host:port/database
15-
16-
# Session
17-
SESSION_SECRET=your_session_secret_at_least_32_chars
14+
# Base.al OAuth Configuration
15+
OAUTH_BASE_CLIENT_ID=your_client_id
16+
OAUTH_BASE_CLIENT_SECRET=your_client_secret
17+
OAUTH_BASE_REDIRECT_URI=your_redirect_uri
18+
NUXT_PUBLIC_OAUTH_BASE_AUTHORIZE_URL=https://base.al/oauth/authorize
19+
NUXT_PUBLIC_OAUTH_BASE_TOKEN_URL=https://base.al/oauth/token
20+
NUXT_PUBLIC_OAUTH_BASE_USERINFO_URL=https://base.al/oauth/userinfo

README.md

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
# AlbDev - Albanian Developer Community
22

3-
A Reddit-like platform for Albanian developers built with Nuxt 4, Nuxt UI 4, Firebase, and PostgreSQL.
3+
A Reddit-like platform for Albanian developers built with Nuxt 4, Nuxt UI 4, and Firebase.
44

55
## Features
66

77
- 🔥 **Nuxt 4** - Latest version with enhanced performance
88
- 🎨 **Nuxt UI 4** - Beautiful, accessible components
9-
- 🔐 **Firebase Authentication** - Secure user authentication
10-
- 💾 **PostgreSQL** - Robust relational database with Drizzle ORM
9+
- 🔐 **Multiple Auth Options** - Firebase Email/Password + Base.al OAuth
10+
- 💾 **Firestore** - Real-time NoSQL database
1111
- 🚀 **Firebase Hosting** - Serverless deployment
1212
- 📱 **Responsive Design** - Works on all devices
1313
-**Real-time Features** - Live updates and interactions
14+
- 🔗 **OAuth Integration** - Sign in with Base.al (Albanian OAuth provider)
1415

1516
## Reddit-like Features
1617

1718
- Create and join communities
1819
- Post text, links, and images
1920
- Upvote/downvote system
20-
- Nested comments
21+
- Nested comments (coming soon)
2122
- User profiles with karma
2223
- Community moderation
2324
- Feed sorting (hot, new, top)
@@ -27,8 +28,8 @@ A Reddit-like platform for Albanian developers built with Nuxt 4, Nuxt UI 4, Fir
2728
### Prerequisites
2829

2930
- Node.js 18+ and npm
30-
- Firebase project
31-
- PostgreSQL database (Cloud SQL or other)
31+
- Firebase project with Firestore enabled
32+
- Base.al OAuth credentials (optional)
3233

3334
### Environment Variables
3435

@@ -47,11 +48,13 @@ NUXT_PUBLIC_FIREBASE_APP_ID=your_app_id
4748
FIREBASE_PRIVATE_KEY=your_private_key
4849
FIREBASE_CLIENT_EMAIL=your_client_email
4950
50-
# Database
51-
DATABASE_URL=postgresql://user:password@host:port/database
52-
53-
# Session
54-
SESSION_SECRET=your_session_secret_min_32_chars
51+
# Base.al OAuth (Optional - for OAuth login)
52+
OAUTH_BASE_CLIENT_ID=your_client_id
53+
OAUTH_BASE_CLIENT_SECRET=your_client_secret
54+
OAUTH_BASE_REDIRECT_URI=your_redirect_uri
55+
NUXT_PUBLIC_OAUTH_BASE_AUTHORIZE_URL=https://accounts.base.al/oauth/authorize
56+
NUXT_PUBLIC_OAUTH_BASE_TOKEN_URL=https://accounts.base.al/oauth/token
57+
NUXT_PUBLIC_OAUTH_BASE_USERINFO_URL=https://accounts.base.al/oauth/userinfo
5558
```
5659

5760
### Installation
@@ -60,23 +63,25 @@ SESSION_SECRET=your_session_secret_min_32_chars
6063
# Install dependencies
6164
npm install
6265

63-
# Generate database migrations
64-
npm run db:generate
65-
66-
# Run migrations
67-
npm run db:migrate
68-
6966
# Start development server
7067
npm run dev
7168
```
7269

73-
### Database Setup
74-
75-
The project uses Drizzle ORM with PostgreSQL. Run migrations to set up the schema:
76-
77-
```bash
78-
npm run db:push
79-
```
70+
### Firebase Setup
71+
72+
1. **Create Firebase Project** at https://console.firebase.google.com
73+
2. **Enable Firestore** - Go to Firestore Database and create database
74+
3. **Enable Authentication**:
75+
- Go to Authentication → Sign-in method
76+
- Enable Email/Password provider
77+
4. **Deploy Firestore Rules**:
78+
```bash
79+
firebase deploy --only firestore:rules
80+
```
81+
5. **Get Service Account Key**:
82+
- Go to Project Settings → Service Accounts
83+
- Generate new private key
84+
- Add credentials to `.env`
8085

8186
To view your database in Drizzle Studio:
8287

@@ -149,11 +154,54 @@ npm run postinstall
149154

150155
- **Framework**: Nuxt 4
151156
- **UI Library**: Nuxt UI 4 (Tailwind CSS)
152-
- **Authentication**: Firebase Auth
153-
- **Database**: PostgreSQL with Drizzle ORM
157+
- **Authentication**: Firebase Auth + Base.al OAuth
158+
- **Database**: Firestore (Firebase)
154159
- **Hosting**: Firebase Hosting + Cloud Functions
155160
- **Language**: TypeScript
156161

162+
## Authentication
163+
164+
The app supports two authentication methods:
165+
166+
### 1. Firebase Email/Password
167+
Traditional email and password authentication powered by Firebase.
168+
169+
### 2. Base.al OAuth
170+
OAuth integration with [Base.al](https://base.al), the Albanian authentication provider. Users can sign in with their Base.al account.
171+
172+
**OAuth Flow:**
173+
1. User clicks "Continue with Base"
174+
2. Redirected to Base.al authorization page
175+
3. After approval, callback creates/links Firebase account
176+
4. User automatically signed in
177+
178+
## Database Structure (Firestore)
179+
180+
```
181+
users/{userId}
182+
- email, username, displayName, avatar, bio, karma
183+
- createdAt, updatedAt
184+
185+
communities/{communityId}
186+
- name, displayName, description, icon, banner
187+
- memberCount, creatorId
188+
- createdAt, updatedAt
189+
190+
posts/{postId}
191+
- title, content, type, url
192+
- authorId, communityId
193+
- upvotes, downvotes, commentCount
194+
- isDeleted, createdAt, updatedAt
195+
196+
postVotes/{userId}_{postId}
197+
- userId, postId, value (1 or -1)
198+
- createdAt
199+
200+
communityMembers/{userId}_{communityId}
201+
- userId, communityId, role
202+
- joinedAt
203+
```
204+
157205
## License
158206

159207
MIT

components/AuthForm.vue

Lines changed: 75 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,80 @@
66
</h2>
77
</template>
88

9-
<form @submit.prevent="handleSubmit" class="space-y-4">
10-
<UFormGroup v-if="isSignUp" label="Username" required>
11-
<UInput
12-
v-model="formData.username"
13-
placeholder="Enter username"
14-
icon="i-heroicons-user"
15-
/>
16-
</UFormGroup>
17-
18-
<UFormGroup label="Email" required>
19-
<UInput
20-
v-model="formData.email"
21-
type="email"
22-
placeholder="Enter email"
23-
icon="i-heroicons-envelope"
24-
/>
25-
</UFormGroup>
26-
27-
<UFormGroup label="Password" required>
28-
<UInput
29-
v-model="formData.password"
30-
type="password"
31-
placeholder="Enter password"
32-
icon="i-heroicons-lock-closed"
33-
/>
34-
</UFormGroup>
35-
9+
<div class="space-y-4">
10+
<!-- OAuth Login -->
3611
<UButton
37-
type="submit"
12+
@click="handleBaseLogin"
3813
block
3914
size="lg"
40-
:loading="loading"
41-
:label="isSignUp ? 'Sign Up' : 'Log In'"
42-
/>
15+
variant="outline"
16+
color="gray"
17+
class="relative"
18+
>
19+
<template #leading>
20+
<img src="https://base.al/base.svg" alt="Base" class="w-5 h-5" />
21+
</template>
22+
Continue with Base
23+
</UButton>
24+
25+
<!-- Divider -->
26+
<div class="relative my-6">
27+
<div class="absolute inset-0 flex items-center">
28+
<div class="w-full border-t border-gray-300 dark:border-gray-700"></div>
29+
</div>
30+
<div class="relative flex justify-center text-sm">
31+
<span class="px-2 bg-white dark:bg-gray-800 text-gray-500">Or continue with email</span>
32+
</div>
33+
</div>
4334

44-
<p class="text-center text-sm text-gray-600 dark:text-gray-400">
45-
{{ isSignUp ? 'Already have an account?' : "Don't have an account?" }}
46-
<button
47-
type="button"
48-
class="text-primary font-semibold hover:underline"
49-
@click="isSignUp = !isSignUp"
50-
>
51-
{{ isSignUp ? 'Log In' : 'Sign Up' }}
52-
</button>
53-
</p>
54-
</form>
35+
<!-- Email/Password Form -->
36+
<form @submit.prevent="handleSubmit" class="space-y-4">
37+
<UFormGroup v-if="isSignUp" label="Username" required>
38+
<UInput
39+
v-model="formData.username"
40+
placeholder="Enter username"
41+
icon="i-heroicons-user"
42+
/>
43+
</UFormGroup>
44+
45+
<UFormGroup label="Email" required>
46+
<UInput
47+
v-model="formData.email"
48+
type="email"
49+
placeholder="Enter email"
50+
icon="i-heroicons-envelope"
51+
/>
52+
</UFormGroup>
53+
54+
<UFormGroup label="Password" required>
55+
<UInput
56+
v-model="formData.password"
57+
type="password"
58+
placeholder="Enter password"
59+
icon="i-heroicons-lock-closed"
60+
/>
61+
</UFormGroup>
62+
63+
<UButton
64+
type="submit"
65+
block
66+
size="lg"
67+
:loading="loading"
68+
:label="isSignUp ? 'Sign Up' : 'Log In'"
69+
/>
70+
71+
<p class="text-center text-sm text-gray-600 dark:text-gray-400">
72+
{{ isSignUp ? 'Already have an account?' : "Don't have an account?" }}
73+
<button
74+
type="button"
75+
class="text-primary font-semibold hover:underline"
76+
@click="isSignUp = !isSignUp"
77+
>
78+
{{ isSignUp ? 'Log In' : 'Sign Up' }}
79+
</button>
80+
</p>
81+
</form>
82+
</div>
5583
</UCard>
5684
</template>
5785

@@ -85,4 +113,9 @@ const handleSubmit = async () => {
85113
loading.value = false
86114
}
87115
}
116+
117+
const handleBaseLogin = () => {
118+
// Redirect to OAuth login endpoint
119+
window.location.href = '/api/auth/oauth/base/login'
120+
}
88121
</script>

composables/useAuth.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
signInWithEmailAndPassword,
33
createUserWithEmailAndPassword,
4+
signInWithCustomToken,
45
signOut as firebaseSignOut,
56
onAuthStateChanged,
67
type User
@@ -10,6 +11,19 @@ export const useAuth = () => {
1011
const { $auth } = useNuxtApp()
1112
const user = useState<User | null>('user', () => null)
1213
const authToken = useState<string | null>('authToken', () => null)
14+
const route = useRoute()
15+
16+
// Handle OAuth callback token
17+
onMounted(() => {
18+
const token = route.query.token as string
19+
if (token) {
20+
signInWithCustomToken($auth, token).then(() => {
21+
// Remove token from URL
22+
const newUrl = window.location.pathname
23+
window.history.replaceState({}, '', newUrl)
24+
})
25+
}
26+
})
1327

1428
const signIn = async (email: string, password: string) => {
1529
try {

drizzle.config.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

firebase.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
{
2+
"firestore": {
3+
"rules": "firestore.rules",
4+
"indexes": "firestore.indexes.json"
5+
},
26
"functions": [
37
{
48
"source": ".output/server",

0 commit comments

Comments
 (0)