···11+export * from "./DatabaseConnection";
22+export * from "./DatabaseService";
+2
netlify/functions/infrastructure/oauth/index.ts
···11+export * from "./config";
22+export * from "./OAuthClientFactory";
+4-4
netlify/functions/init-db.ts
···11-import { SimpleHandler } from "./shared/types/api.types";
22-import { DatabaseService } from "./shared/services/database";
33-import { withErrorHandling } from "./shared/middleware";
44-import { successResponse } from "./shared/utils";
11+import { SimpleHandler } from "./core/types/api.types";
22+import { DatabaseService } from "./infrastructure/database/DatabaseService";
33+import { withErrorHandling } from "./core/middleware";
44+import { successResponse } from "./utils";
5566const initDbHandler: SimpleHandler = async () => {
77 const dbService = new DatabaseService();
+11-10
netlify/functions/logout.ts
···11-import { SimpleHandler } from "./shared/types/api.types";
22-import { SessionService } from "./shared/services/session";
33-import { getOAuthConfig } from "./shared/services/oauth";
44-import { extractSessionId } from "./shared/middleware";
55-import { withErrorHandling } from "./shared/middleware";
11+import { ApiError } from "./core/errors";
22+import { SimpleHandler } from "./core/types/api.types";
33+import { SessionService } from "./services/SessionService";
44+import { getOAuthConfig } from "./infrastructure/oauth";
55+import { extractSessionId } from "./core/middleware";
66+import { withErrorHandling } from "./core/middleware";
6778const logoutHandler: SimpleHandler = async (event) => {
88- // Only allow POST for logout
99 if (event.httpMethod !== "POST") {
1010- throw new Error("Method not allowed");
1010+ throw new ApiError(
1111+ "Method not allowed",
1212+ 405,
1313+ `Only POST method is supported for ${event.path}`,
1414+ );
1115 }
12161317 console.log("[logout] Starting logout process...");
1414- console.log("[logout] Cookies received:", event.headers.cookie);
15181619 const sessionId = extractSessionId(event);
1720 console.log("[logout] Session ID from cookie:", sessionId);
18211922 if (sessionId) {
2020- // Use SessionService to properly clean up both user and OAuth sessions
2123 await SessionService.deleteSession(sessionId);
2224 console.log("[logout] Successfully deleted session:", sessionId);
2325 }
24262525- // Clear the session cookie with matching flags from when it was set
2627 const config = getOAuthConfig();
2728 const isDev = config.clientType === "loopback";
2829
+6-10
netlify/functions/oauth-callback.ts
···11-import { SimpleHandler } from "./shared/types/api.types";
22-import { createOAuthClient, getOAuthConfig } from "./shared/services/oauth";
33-import { userSessions } from "./shared/services/session";
44-import { redirectResponse } from "./shared/utils";
55-import { withErrorHandling } from "./shared/middleware";
66-import { CONFIG } from "./shared/constants";
11+import { SimpleHandler } from "./core/types/api.types";
22+import { createOAuthClient, getOAuthConfig } from "./infrastructure/oauth";
33+import { userSessions } from "./infrastructure/oauth/stores";
44+import { redirectResponse } from "./utils";
55+import { withErrorHandling } from "./core/middleware";
66+import { CONFIG } from "./core/config/constants";
77import * as crypto from "crypto";
8899const oauthCallbackHandler: SimpleHandler = async (event) => {
···2828 return redirectResponse(`${currentUrl}/?error=Missing OAuth parameters`);
2929 }
30303131- // Create OAuth client using shared helper
3231 const client = await createOAuthClient();
33323434- // Process the OAuth callback
3533 const result = await client.callback(params);
36343735 console.log(
···3937 result.session.did,
4038 );
41394242- // Store session
4340 const sessionId = crypto.randomUUID();
4441 const did = result.session.did;
4542 await userSessions.set(sessionId, { did });
46434744 console.log("[oauth-callback] Created user session:", sessionId);
48454949- // Cookie flags - no Secure flag for loopback
5046 const cookieFlags = isDev
5147 ? `HttpOnly; SameSite=Lax; Max-Age=${CONFIG.COOKIE_MAX_AGE}; Path=/`
5248 : `HttpOnly; SameSite=Lax; Max-Age=${CONFIG.COOKIE_MAX_AGE}; Path=/; Secure`;
+5-7
netlify/functions/oauth-start.ts
···11-import { SimpleHandler } from "./shared/types/api.types";
22-import { createOAuthClient } from "./shared/services/oauth";
33-import { successResponse } from "./shared/utils";
44-import { withErrorHandling } from "./shared/middleware";
55-import { ValidationError } from "./shared/constants/errors";
11+import { SimpleHandler } from "./core/types/api.types";
22+import { createOAuthClient } from "./infrastructure/oauth/OAuthClientFactory";
33+import { successResponse } from "./utils";
44+import { withErrorHandling } from "./core/middleware";
55+import { ValidationError } from "./core/errors";
6677interface OAuthStartRequestBody {
88 login_hint?: string;
···23232424 console.log("[oauth-start] Starting OAuth flow for:", loginHint);
25252626- // Create OAuth client using shared helper
2726 const client = await createOAuthClient(event);
28272929- // Start the authorization flow
3028 const authUrl = await client.authorize(loginHint, {
3129 scope: "atproto transition:generic",
3230 });
···11-export * from "./types";
22-export * from "./constants";
33-export * from "./utils";
44-export * from "./middleware";
55-export * from "./services/database";
66-export * from "./services/session";
77-export * from "./services/oauth";
···11-export * from "./BaseRepository";
21export * from "./UploadRepository";
32export * from "./SourceAccountRepository";
43export * from "./MatchRepository";
···11-import { getDbClient } from "./connection";
22-import { DatabaseError } from "../../constants/errors";
11+import { getDbClient } from "./DatabaseConnection";
22+import { DatabaseError } from "../../core/errors";
33+import { DbStatusRow } from "../../core/types";
3445export class DatabaseService {
56 private sql = getDbClient();
···1112 process.env.NETLIFY_DATABASE_URL?.split("@")[1],
1213 );
13141414- // Test connection
1515 const res = (await this
1616- .sql`SELECT current_database() AS db, current_user AS user, NOW() AS now`) as Record<
1717- string,
1818- any
1919- >[];
1616+ .sql`SELECT current_database() AS db, current_user AS user, NOW() AS now`) as DbStatusRow[];
2017 console.log("✅ Connected:", res[0]);
21182222- // Create tables
2319 await this.createTables();
2420 await this.createIndexes();
2521···3430 }
35313632 private async createTables(): Promise<void> {
3737- // OAuth Tables
3833 await this.sql`
3934 CREATE TABLE IF NOT EXISTS oauth_states (
4035 key TEXT PRIMARY KEY,
···6257 )
6358 `;
64596565- // User + Match Tracking
6660 await this.sql`
6761 CREATE TABLE IF NOT EXISTS user_uploads (
6862 upload_id TEXT PRIMARY KEY,
···156150 }
157151158152 private async createIndexes(): Promise<void> {
159159- // Existing indexes
160153 await this
161154 .sql`CREATE INDEX IF NOT EXISTS idx_source_accounts_to_check ON source_accounts(source_platform, match_found, last_checked)`;
162155 await this
···175168 .sql`CREATE INDEX IF NOT EXISTS idx_user_match_status_did_followed ON user_match_status(did, followed)`;
176169 await this
177170 .sql`CREATE INDEX IF NOT EXISTS idx_notification_queue_pending ON notification_queue(sent, created_at) WHERE sent = false`;
178178-179179- // Enhanced indexes
180171 await this
181172 .sql`CREATE INDEX IF NOT EXISTS idx_atproto_matches_stats ON atproto_matches(source_account_id, found_at DESC, post_count DESC, follower_count DESC)`;
182173 await this
···11+export function formatDate(dateString: string): string {
22+ const date = new Date(dateString);
33+ return date.toLocaleDateString("en-US", {
44+ month: "short",
55+ day: "numeric",
66+ year: "numeric",
77+ hour: "2-digit",
88+ minute: "2-digit",
99+ });
1010+}
1111+1212+export function formatRelativeTime(dateString: string): string {
1313+ const date = new Date(dateString);
1414+ const now = new Date();
1515+ const diffMs = now.getTime() - date.getTime();
1616+ const diffMins = Math.floor(diffMs / 60000);
1717+ const diffHours = Math.floor(diffMs / 3600000);
1818+ const diffDays = Math.floor(diffMs / 86400000);
1919+2020+ if (diffMins < 1) return "just now";
2121+ if (diffMins < 60) return `${diffMins}m ago`;
2222+ if (diffHours < 24) return `${diffHours}h ago`;
2323+ if (diffDays < 7) return `${diffDays}d ago`;
2424+2525+ return formatDate(dateString);
2626+}
+2-2
src/lib/utils/platform.ts
···11-import { PLATFORMS, type PlatformConfig } from "../../constants/platforms";
22-import { ATPROTO_APPS, type AtprotoApp } from "../../constants/atprotoApps";
11+import { PLATFORMS, type PlatformConfig } from "../../config/platforms";
22+import { ATPROTO_APPS, type AtprotoApp } from "../../config/atprotoApps";
33import type { AtprotoAppId } from "../../types/settings";
4455/**
+1-1
src/pages/Home.tsx
···66import UploadTab from "../components/UploadTab";
77import HistoryTab from "../components/HistoryTab";
88import PlaceholderTab from "../components/PlaceholderTab";
99-import { apiClient } from "../lib/apiClient";
99+import { apiClient } from "../lib/api/client";
1010import type { Upload as UploadType } from "../types";
1111import type { UserSettings } from "../types/settings";
1212import SettingsPage from "./Settings";
+2-2
src/pages/Settings.tsx
···11import { Settings as SettingsIcon, ChevronRight } from "lucide-react";
22-import { PLATFORMS } from "../constants/platforms";
33-import { ATPROTO_APPS } from "../constants/atprotoApps";
22+import { PLATFORMS } from "../config/platforms";
33+import { ATPROTO_APPS } from "../config/atprotoApps";
44import type { UserSettings, PlatformDestinations } from "../types/settings";
5566interface SettingsPageProps {
···11-// Common shared types
22-31export type AppStep =
42 | "checking"
53 | "login"
-5
src/types/index.ts
···11-// Session and Auth Types
21export * from "./auth.types";
33-44-// Search and Match Types
52export * from "./search.types";
66-77-// Common Types
83export * from "./common.types";
94105// Re-export settings types for convenience