1/**
2 * vt.oauthDB.service.ts โ OAuth database service for user and account management.
3 */
4
47
48/**
49 * Database user record structure.
50 */
51interface DatabaseUser {
52 id: string;
53 username: string;
62
63/**
64 * Database OAuth account record structure.
65 */
66interface DatabaseOAuthAccount {
67 id: string;
68 user_id: string;
116
117/**
118 * Convert database user to SQL-compatible format.
119 */
120function userToSqlRecord(user: DatabaseUser): Record<string, string | number | null> {
121 return {
122 id: user.id,
136 */
137function oauthAccountToSqlRecord(
138 account: DatabaseOAuthAccount,
139): Record<string, string | number | null> {
140 return {
155
156// ============================================================================
157// OAUTH DATABASE SERVICE CLASS
158// ============================================================================
159
160/**
161 * OAuth Database Service for handling user and account operations.
162 *
163 * @example
172
173 /**
174 * Ensure OAuth database tables exist (lazy initialization).
175 * Checks both tables exist and creates them if missing.
176 * Only runs once per server session.
198 error instanceof Error ? error.stack : String(error),
199 );
200 throw new Error("OAuth database initialization failed");
201 }
202 } /**
203 * Initialize OAuth database tables.
204 * Creates users and oauth_accounts tables if they don't exist.
205 */
206
207 async initializeTables(): Promise<void> {
208 console.log("๐๏ธ [OAuthDbService] Initializing OAuth database tables");
209
210 try {
249 await sqlService.createIndex("idx_Guardian_users_username", "Guardian_users", ["username"]);
250
251 console.log("โ
[OAuthDbService] Guardian database tables initialized successfully");
252 } catch (error) {
253 console.error("โ [OAuthDbService] Failed to initialize Guardian tables:", error);
265 provider: string,
266 providerUserId: string,
267 ): Promise<DatabaseOAuthAccount | null> {
268 try {
269 const result = await sqlService.findOne("Guardian_oauth_accounts", {
272 });
273
274 return result as unknown as DatabaseOAuthAccount | null;
275 } catch (error) {
276 console.error(
288 * @returns User record or null if not found
289 */
290 async findUserById(userId: string): Promise<DatabaseUser | null> {
291 try {
292 const result = await sqlService.findOne("Guardian_users", { id: userId });
293 return result as unknown as DatabaseUser | null;
294 } catch (error) {
295 console.error(`[OAuthDbService] Error finding user ${userId}:`, error);
304 * @returns User record or null if not found
305 */
306 async findUserByEmail(email: string): Promise<DatabaseUser | null> {
307 try {
308 const result = await sqlService.findOne("Guardian_users", { email });
309 return result as unknown as DatabaseUser | null;
310 } catch (error) {
311 console.error(`[OAuthDbService] Error finding user by email ${email}:`, error);
342 * @returns Created user record
343 */
344 async createUser(profile: OAuthUserProfile): Promise<DatabaseUser> {
345 const now = getSydneyTimestampMillis();
346 const userId = crypto.randomUUID();
347 const userRole = this.determineUserRole(profile.email);
348
349 const userData: DatabaseUser = {
350 id: userId,
351 username: profile.username || `user_${userId.slice(0, 8)}`,
384 profile: OAuthUserProfile,
385 tokenResponse?: OAuthTokenResponse | string,
386 ): Promise<DatabaseOAuthAccount> {
387 const now = getSydneyTimestampMillis();
388 const accountId = crypto.randomUUID();
453 }
454
455 const accountData: DatabaseOAuthAccount = {
456 id: accountId,
457 user_id: userId,
554 * @returns Decrypted access token or null if not available
555 */
556 async getDecryptedAccessToken(account: DatabaseOAuthAccount): Promise<string | null> {
557 if (!account.access_token) {
558 return null;
579 * @returns Decrypted refresh token or null if not available
580 */
581 async getDecryptedRefreshToken(account: DatabaseOAuthAccount): Promise<string | null> {
582 if (!account.refresh_token) {
583 return null;
605 * @returns true if token is expired or will expire within buffer time
606 */
607 isAccessTokenExpired(account: DatabaseOAuthAccount, bufferSeconds = 300): boolean {
608 return isTokenExpired(account.token_expires_at, bufferSeconds);
609 } /**
610 * Convert DatabaseUser to SessionUser for authentication.
611 *
612 * @param dbUser Database user record
613 * @returns Session user object
614 */
615
616 convertToSessionUser(dbUser: DatabaseUser): SessionUser {
617 return {
618 id: dbUser.id,
645 console.error("๐จ [OAuthDbService] VALTOWN_API_KEY not set - cannot process OAuth user");
646 console.error(`๐จ [OAuthDbService] Provider: ${provider}, Profile ID: ${profile.id}`);
647 throw new Error("Database access unavailable - OAuth requires valid API key");
648 }
649
767 */
768
769 async getUserOAuthAccounts(userId: string): Promise<DatabaseOAuthAccount[]> {
770 try {
771 const results = await sqlService.findMany("Guardian_oauth_accounts", { user_id: userId });
772 return results as unknown as DatabaseOAuthAccount[];
773 } catch (error) {
774 console.error(
801
802export type {
803 DatabaseOAuthAccount,
804 DatabaseUser,
805 OAuthAccountUpdate,
806 OAuthTokenResponse,
812
813/**
814 * Default OAuth database service instance.
815 *
816 * @example