refactor(api): migrate auth endpoints to Convex
- commit
- a5ef44750336b45c6b65067e2ad7cac7a3dcb473
- author
- bnema · · 2026-01-18 18:34 UTC
- parent
- cb3eadb
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +32 -36
M src/routes/api/register/+server.ts +7 -13
| @@ -1,11 +1,9 @@ | |||
| 1 | 1 | import type { RequestHandler } from '@sveltejs/kit'; | |
| 2 | 2 | import type { RegisterResponseSuccess, RegisterResponseError } from '$lib/types/payloads'; | |
| 3 | - | import db from '$lib/db/db.server'; | |
| 4 | - | import { users } from '$lib/db/schema'; | |
| 3 | + | import { users } from '$lib/db/convex.server'; | |
| 5 | 4 | import { hashPassword } from '$lib/utils/password'; | |
| 6 | 5 | import { createRegistrationSchema, DEFAULT_PASSWORD_CONSTRAINTS } from '$lib/validation/password'; | |
| 7 | 6 | import { createLogger } from '$lib/utils/logger.server'; | |
| 8 | - | import { eq } from 'drizzle-orm/sql'; | |
| 9 | 7 | ||
| 10 | 8 | const log = createLogger('register-server'); | |
| 11 | 9 | ||
| @@ -102,9 +100,9 @@ export const POST: RequestHandler = async ({ request }) => { | |||
| 102 | 100 | const username = suUsername.trim(); | |
| 103 | 101 | const password = suPassword.trim(); | |
| 104 | 102 | ||
| 105 | - | // Check if nickname already exists | |
| 106 | - | const existingUser = await db.select().from(users).where(eq(users.nickname, username)); | |
| 107 | - | if (existingUser.length > 0) { | |
| 103 | + | // Check if nickname already exists using Convex | |
| 104 | + | const existingUser = await users.getByNickname(username); | |
| 105 | + | if (existingUser) { | |
| 108 | 106 | log.warn('Username already exists', { username }); | |
| 109 | 107 | return new Response( | |
| 110 | 108 | JSON.stringify({ error: 'This username is already taken' } as RegisterResponseError), | |
| @@ -123,17 +121,13 @@ export const POST: RequestHandler = async ({ request }) => { | |||
| 123 | 121 | { status: 500 } | |
| 124 | 122 | ); | |
| 125 | 123 | } | |
| 126 | - | // Insert the new user into the database. | |
| 124 | + | ||
| 125 | + | // Insert the new user into Convex | |
| 127 | 126 | try { | |
| 128 | - | await db.insert(users).values({ | |
| 129 | - | nickname: username, | |
| 130 | - | password: hashedPassword, | |
| 131 | - | createdAt: Date.now() | |
| 132 | - | }); | |
| 127 | + | await users.create(username, hashedPassword); | |
| 133 | 128 | log.info(`User ${username} registered successfully`); | |
| 134 | 129 | } catch (err: unknown) { | |
| 135 | 130 | log.error('Database insertion error', { error: err as object }); | |
| 136 | - | // Assume a duplicate user error if the email (or derived unique field) already exists. | |
| 137 | 131 | return new Response( | |
| 138 | 132 | JSON.stringify({ | |
| 139 | 133 | error: 'User registration failed. Possibly user already exists.' | |
| @@ -1,11 +1,9 @@ | |||||
| 1 | import type { RequestHandler } from '@sveltejs/kit'; | 1 | import type { RequestHandler } from '@sveltejs/kit'; | ||
| 2 | import type { RegisterResponseSuccess, RegisterResponseError } from '$lib/types/payloads'; | 2 | import type { RegisterResponseSuccess, RegisterResponseError } from '$lib/types/payloads'; | ||
| 3 | - | import db from '$lib/db/db.server'; | 3 | + | import { users } from '$lib/db/convex.server'; |
| 4 | - | import { users } from '$lib/db/schema'; | |||
| 5 | import { hashPassword } from '$lib/utils/password'; | 4 | import { hashPassword } from '$lib/utils/password'; | ||
| 6 | import { createRegistrationSchema, DEFAULT_PASSWORD_CONSTRAINTS } from '$lib/validation/password'; | 5 | import { createRegistrationSchema, DEFAULT_PASSWORD_CONSTRAINTS } from '$lib/validation/password'; | ||
| 7 | import { createLogger } from '$lib/utils/logger.server'; | 6 | import { createLogger } from '$lib/utils/logger.server'; | ||
| 8 | - | import { eq } from 'drizzle-orm/sql'; | |||
| 9 | 7 | ||||
| 10 | const log = createLogger('register-server'); | 8 | const log = createLogger('register-server'); | ||
| 11 | 9 | ||||
| @@ -102,9 +100,9 @@ export const POST: RequestHandler = async ({ request }) => { | |||||
| 102 | const username = suUsername.trim(); | 100 | const username = suUsername.trim(); | ||
| 103 | const password = suPassword.trim(); | 101 | const password = suPassword.trim(); | ||
| 104 | 102 | ||||
| 105 | - | // Check if nickname already exists | 103 | + | // Check if nickname already exists using Convex |
| 106 | - | const existingUser = await db.select().from(users).where(eq(users.nickname, username)); | 104 | + | const existingUser = await users.getByNickname(username); |
| 107 | - | if (existingUser.length > 0) { | 105 | + | if (existingUser) { |
| 108 | log.warn('Username already exists', { username }); | 106 | log.warn('Username already exists', { username }); | ||
| 109 | return new Response( | 107 | return new Response( | ||
| 110 | JSON.stringify({ error: 'This username is already taken' } as RegisterResponseError), | 108 | JSON.stringify({ error: 'This username is already taken' } as RegisterResponseError), | ||
| @@ -123,17 +121,13 @@ export const POST: RequestHandler = async ({ request }) => { | |||||
| 123 | { status: 500 } | 121 | { status: 500 } | ||
| 124 | ); | 122 | ); | ||
| 125 | } | 123 | } | ||
| 126 | - | // Insert the new user into the database. | 124 | + | |
| 125 | + | // Insert the new user into Convex | |||
| 127 | try { | 126 | try { | ||
| 128 | - | await db.insert(users).values({ | 127 | + | await users.create(username, hashedPassword); |
| 129 | - | nickname: username, | |||
| 130 | - | password: hashedPassword, | |||
| 131 | - | createdAt: Date.now() | |||
| 132 | - | }); | |||
| 133 | log.info(`User ${username} registered successfully`); | 128 | log.info(`User ${username} registered successfully`); | ||
| 134 | } catch (err: unknown) { | 129 | } catch (err: unknown) { | ||
| 135 | log.error('Database insertion error', { error: err as object }); | 130 | log.error('Database insertion error', { error: err as object }); | ||
| 136 | - | // Assume a duplicate user error if the email (or derived unique field) already exists. | |||
| 137 | return new Response( | 131 | return new Response( | ||
| 138 | JSON.stringify({ | 132 | JSON.stringify({ | ||
| 139 | error: 'User registration failed. Possibly user already exists.' | 133 | error: 'User registration failed. Possibly user already exists.' | ||
M src/routes/api/session/login/+server.ts +19 -19
| @@ -1,8 +1,6 @@ | |||
| 1 | 1 | import type { RequestHandler } from '@sveltejs/kit'; | |
| 2 | 2 | import type { LoginResponseSuccess, LoginResponseError } from '$lib/types/payloads'; | |
| 3 | - | import db from '$lib/db/db.server'; | |
| 4 | - | import { users } from '$lib/db/schema'; | |
| 5 | - | import { eq } from 'drizzle-orm/sql'; | |
| 3 | + | import { users } from '$lib/db/convex.server'; | |
| 6 | 4 | import { verifyPassword } from '$lib/utils/password'; | |
| 7 | 5 | import { generateSessionToken, createSession } from '$lib/api/session.server'; | |
| 8 | 6 | import { setSessionTokenCookie } from '$lib/api/session.cookie'; | |
| @@ -84,10 +82,8 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||
| 84 | 82 | }); | |
| 85 | 83 | } | |
| 86 | 84 | ||
| 87 | - | // Find user by username | |
| 88 | - | const user = await db.query.users.findFirst({ | |
| 89 | - | where: eq(users.nickname, username.trim()) | |
| 90 | - | }); | |
| 85 | + | // Find user by username using Convex | |
| 86 | + | const user = await users.getByNickname(username.trim()); | |
| 91 | 87 | ||
| 92 | 88 | if (!user) { | |
| 93 | 89 | attemptData.count++; | |
| @@ -107,6 +103,9 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||
| 107 | 103 | ); | |
| 108 | 104 | } | |
| 109 | 105 | ||
| 106 | + | // Use native Convex _id | |
| 107 | + | const userId = user._id; | |
| 108 | + | ||
| 110 | 109 | // Verify password | |
| 111 | 110 | try { | |
| 112 | 111 | const isValid = await verifyPassword(password.trim(), user.password); | |
| @@ -117,7 +116,7 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||
| 117 | 116 | log.warn('Login failed - invalid password', { | |
| 118 | 117 | maskedIp, | |
| 119 | 118 | attemptCount: attemptData.count, | |
| 120 | - | userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}` | |
| 119 | + | odlUserId: `${userId.slice(0, 4)}...${userId.slice(-4)}` | |
| 121 | 120 | }); | |
| 122 | 121 | const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count); | |
| 123 | 122 | return new Response( | |
| @@ -139,29 +138,30 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||
| 139 | 138 | ||
| 140 | 139 | // Generate session token and create session | |
| 141 | 140 | const token = generateSessionToken(); | |
| 142 | - | const session = await createSession(token, user.id); | |
| 141 | + | const session = await createSession(token, userId); | |
| 143 | 142 | ||
| 144 | - | // Update user status to online | |
| 145 | - | await db | |
| 146 | - | .update(users) | |
| 147 | - | .set({ | |
| 148 | - | status: 'online', | |
| 149 | - | lastSeen: Date.now() | |
| 150 | - | }) | |
| 151 | - | .where(eq(users.id, user.id)); | |
| 143 | + | // Update user status to online using Convex | |
| 144 | + | await users.updateStatus(userId, 'online'); | |
| 152 | 145 | ||
| 153 | 146 | // Set session cookie | |
| 154 | 147 | setSessionTokenCookie({ cookies }, token, session.expiresAt); | |
| 155 | 148 | ||
| 156 | 149 | log.info('Login successful', { | |
| 157 | - | userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`, | |
| 150 | + | odlUserId: `${userId.slice(0, 4)}...${userId.slice(-4)}`, | |
| 158 | 151 | expiresAt: new Date(session.expiresAt).toISOString() | |
| 159 | 152 | }); | |
| 160 | 153 | ||
| 154 | + | // Return user with native Convex ID | |
| 161 | 155 | return new Response( | |
| 162 | 156 | JSON.stringify({ | |
| 163 | 157 | success: true, | |
| 164 | - | user: createSafeUser(user) | |
| 158 | + | user: createSafeUser({ | |
| 159 | + | id: userId, | |
| 160 | + | nickname: user.nickname, | |
| 161 | + | status: 'online', | |
| 162 | + | avatarUrl: user.avatarUrl, | |
| 163 | + | lastSeen: user.lastSeen | |
| 164 | + | }) | |
| 165 | 165 | } as LoginResponseSuccess), | |
| 166 | 166 | { | |
| 167 | 167 | status: 200 | |
| @@ -1,8 +1,6 @@ | |||||
| 1 | import type { RequestHandler } from '@sveltejs/kit'; | 1 | import type { RequestHandler } from '@sveltejs/kit'; | ||
| 2 | import type { LoginResponseSuccess, LoginResponseError } from '$lib/types/payloads'; | 2 | import type { LoginResponseSuccess, LoginResponseError } from '$lib/types/payloads'; | ||
| 3 | - | import db from '$lib/db/db.server'; | 3 | + | import { users } from '$lib/db/convex.server'; |
| 4 | - | import { users } from '$lib/db/schema'; | |||
| 5 | - | import { eq } from 'drizzle-orm/sql'; | |||
| 6 | import { verifyPassword } from '$lib/utils/password'; | 4 | import { verifyPassword } from '$lib/utils/password'; | ||
| 7 | import { generateSessionToken, createSession } from '$lib/api/session.server'; | 5 | import { generateSessionToken, createSession } from '$lib/api/session.server'; | ||
| 8 | import { setSessionTokenCookie } from '$lib/api/session.cookie'; | 6 | import { setSessionTokenCookie } from '$lib/api/session.cookie'; | ||
| @@ -84,10 +82,8 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||||
| 84 | }); | 82 | }); | ||
| 85 | } | 83 | } | ||
| 86 | 84 | ||||
| 87 | - | // Find user by username | 85 | + | // Find user by username using Convex |
| 88 | - | const user = await db.query.users.findFirst({ | 86 | + | const user = await users.getByNickname(username.trim()); |
| 89 | - | where: eq(users.nickname, username.trim()) | |||
| 90 | - | }); | |||
| 91 | 87 | ||||
| 92 | if (!user) { | 88 | if (!user) { | ||
| 93 | attemptData.count++; | 89 | attemptData.count++; | ||
| @@ -107,6 +103,9 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||||
| 107 | ); | 103 | ); | ||
| 108 | } | 104 | } | ||
| 109 | 105 | ||||
| 106 | + | // Use native Convex _id | |||
| 107 | + | const userId = user._id; | |||
| 108 | + | ||||
| 110 | // Verify password | 109 | // Verify password | ||
| 111 | try { | 110 | try { | ||
| 112 | const isValid = await verifyPassword(password.trim(), user.password); | 111 | const isValid = await verifyPassword(password.trim(), user.password); | ||
| @@ -117,7 +116,7 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||||
| 117 | log.warn('Login failed - invalid password', { | 116 | log.warn('Login failed - invalid password', { | ||
| 118 | maskedIp, | 117 | maskedIp, | ||
| 119 | attemptCount: attemptData.count, | 118 | attemptCount: attemptData.count, | ||
| 120 | - | userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}` | 119 | + | odlUserId: `${userId.slice(0, 4)}...${userId.slice(-4)}` |
| 121 | }); | 120 | }); | ||
| 122 | const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count); | 121 | const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count); | ||
| 123 | return new Response( | 122 | return new Response( | ||
| @@ -139,29 +138,30 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||||
| 139 | 138 | ||||
| 140 | // Generate session token and create session | 139 | // Generate session token and create session | ||
| 141 | const token = generateSessionToken(); | 140 | const token = generateSessionToken(); | ||
| 142 | - | const session = await createSession(token, user.id); | 141 | + | const session = await createSession(token, userId); |
| 143 | 142 | ||||
| 144 | - | // Update user status to online | 143 | + | // Update user status to online using Convex |
| 145 | - | await db | 144 | + | await users.updateStatus(userId, 'online'); |
| 146 | - | .update(users) | |||
| 147 | - | .set({ | |||
| 148 | - | status: 'online', | |||
| 149 | - | lastSeen: Date.now() | |||
| 150 | - | }) | |||
| 151 | - | .where(eq(users.id, user.id)); | |||
| 152 | 145 | ||||
| 153 | // Set session cookie | 146 | // Set session cookie | ||
| 154 | setSessionTokenCookie({ cookies }, token, session.expiresAt); | 147 | setSessionTokenCookie({ cookies }, token, session.expiresAt); | ||
| 155 | 148 | ||||
| 156 | log.info('Login successful', { | 149 | log.info('Login successful', { | ||
| 157 | - | userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`, | 150 | + | odlUserId: `${userId.slice(0, 4)}...${userId.slice(-4)}`, |
| 158 | expiresAt: new Date(session.expiresAt).toISOString() | 151 | expiresAt: new Date(session.expiresAt).toISOString() | ||
| 159 | }); | 152 | }); | ||
| 160 | 153 | ||||
| 154 | + | // Return user with native Convex ID | |||
| 161 | return new Response( | 155 | return new Response( | ||
| 162 | JSON.stringify({ | 156 | JSON.stringify({ | ||
| 163 | success: true, | 157 | success: true, | ||
| 164 | - | user: createSafeUser(user) | 158 | + | user: createSafeUser({ |
| 159 | + | id: userId, | |||
| 160 | + | nickname: user.nickname, | |||
| 161 | + | status: 'online', | |||
| 162 | + | avatarUrl: user.avatarUrl, | |||
| 163 | + | lastSeen: user.lastSeen | |||
| 164 | + | }) | |||
| 165 | } as LoginResponseSuccess), | 165 | } as LoginResponseSuccess), | ||
| 166 | { | 166 | { | ||
| 167 | status: 200 | 167 | status: 200 | ||
M src/routes/api/session/logout/+server.ts +3 -2
| @@ -1,11 +1,12 @@ | |||
| 1 | 1 | import { json } from '@sveltejs/kit'; | |
| 2 | + | import type { RequestHandler } from './$types'; | |
| 2 | 3 | import { invalidateSession } from '$lib/api/session.server'; | |
| 3 | 4 | import { sha256 } from '$lib/api/session.server'; // re-use sha256 helper to compute session id | |
| 4 | 5 | import { createLogger } from '$lib/utils/logger.server'; | |
| 5 | 6 | ||
| 6 | 7 | const log = createLogger('logout-server'); | |
| 7 | 8 | ||
| 8 | - | export async function POST({ cookies }) { | |
| 9 | + | export const POST: RequestHandler = async ({ cookies }) => { | |
| 9 | 10 | const token = cookies.get('session'); | |
| 10 | 11 | ||
| 11 | 12 | if (token) { | |
| @@ -20,4 +21,4 @@ export async function POST({ cookies }) { | |||
| 20 | 21 | } | |
| 21 | 22 | ||
| 22 | 23 | return json({ success: true }); | |
| 23 | - | } | |
| 24 | + | }; | |
| @@ -1,11 +1,12 @@ | |||||
| 1 | import { json } from '@sveltejs/kit'; | 1 | import { json } from '@sveltejs/kit'; | ||
| 2 | + | import type { RequestHandler } from './$types'; | |||
| 2 | import { invalidateSession } from '$lib/api/session.server'; | 3 | import { invalidateSession } from '$lib/api/session.server'; | ||
| 3 | import { sha256 } from '$lib/api/session.server'; // re-use sha256 helper to compute session id | 4 | import { sha256 } from '$lib/api/session.server'; // re-use sha256 helper to compute session id | ||
| 4 | import { createLogger } from '$lib/utils/logger.server'; | 5 | import { createLogger } from '$lib/utils/logger.server'; | ||
| 5 | 6 | ||||
| 6 | const log = createLogger('logout-server'); | 7 | const log = createLogger('logout-server'); | ||
| 7 | 8 | ||||
| 8 | - | export async function POST({ cookies }) { | 9 | + | export const POST: RequestHandler = async ({ cookies }) => { |
| 9 | const token = cookies.get('session'); | 10 | const token = cookies.get('session'); | ||
| 10 | 11 | ||||
| 11 | if (token) { | 12 | if (token) { | ||
| @@ -20,4 +21,4 @@ export async function POST({ cookies }) { | |||||
| 20 | } | 21 | } | ||
| 21 | 22 | ||||
| 22 | return json({ success: true }); | 23 | return json({ success: true }); | ||
| 23 | - | } | 24 | + | }; |
M src/routes/api/session/validate/+server.ts +3 -2
| @@ -1,10 +1,11 @@ | |||
| 1 | 1 | import { json } from '@sveltejs/kit'; | |
| 2 | + | import type { RequestHandler } from './$types'; | |
| 2 | 3 | import { validateSessionToken } from '$lib/api/session.server'; | |
| 3 | 4 | import { createLogger } from '$lib/utils/logger.server'; | |
| 4 | 5 | ||
| 5 | 6 | const log = createLogger('validate-server'); | |
| 6 | 7 | ||
| 7 | - | export async function GET({ cookies }) { | |
| 8 | + | export const GET: RequestHandler = async ({ cookies }) => { | |
| 8 | 9 | const token = cookies.get('session'); | |
| 9 | 10 | ||
| 10 | 11 | if (!token) { | |
| @@ -24,4 +25,4 @@ export async function GET({ cookies }) { | |||
| 24 | 25 | } | |
| 25 | 26 | ||
| 26 | 27 | return json(result); | |
| 27 | - | } | |
| 28 | + | }; | |
| @@ -1,10 +1,11 @@ | |||||
| 1 | import { json } from '@sveltejs/kit'; | 1 | import { json } from '@sveltejs/kit'; | ||
| 2 | + | import type { RequestHandler } from './$types'; | |||
| 2 | import { validateSessionToken } from '$lib/api/session.server'; | 3 | import { validateSessionToken } from '$lib/api/session.server'; | ||
| 3 | import { createLogger } from '$lib/utils/logger.server'; | 4 | import { createLogger } from '$lib/utils/logger.server'; | ||
| 4 | 5 | ||||
| 5 | const log = createLogger('validate-server'); | 6 | const log = createLogger('validate-server'); | ||
| 6 | 7 | ||||
| 7 | - | export async function GET({ cookies }) { | 8 | + | export const GET: RequestHandler = async ({ cookies }) => { |
| 8 | const token = cookies.get('session'); | 9 | const token = cookies.get('session'); | ||
| 9 | 10 | ||||
| 10 | if (!token) { | 11 | if (!token) { | ||
| @@ -24,4 +25,4 @@ export async function GET({ cookies }) { | |||||
| 24 | } | 25 | } | ||
| 25 | 26 | ||||
| 26 | return json(result); | 27 | return json(result); | ||
| 27 | - | } | 28 | + | }; |