Skip to content

feat: integrate SQLite support and enhance authentication logic#16

Merged
danships merged 2 commits intomainfrom
feat/add-sqlite-support-and-auth-fixes
Jan 22, 2026
Merged

feat: integrate SQLite support and enhance authentication logic#16
danships merged 2 commits intomainfrom
feat/add-sqlite-support-and-auth-fixes

Conversation

@danships
Copy link
Owner

  • Added SQLite database support by introducing a new database adapter that handles both SQLite and MySQL connection strings.
  • Updated the authentication initialization to utilize the new database adapter.
  • Modified the default database connection to use SQLite when no DB environment variable is set.
  • Enhanced the login page with improved form handling and error notifications.
  • Updated environment variables to include a flag for skipping automatic schema synchronization in production.

- Added SQLite database support by introducing a new database adapter that handles both SQLite and MySQL connection strings.
- Updated the authentication initialization to utilize the new database adapter.
- Modified the default database connection to use SQLite when no DB environment variable is set.
- Enhanced the login page with improved form handling and error notifications.
- Updated environment variables to include a flag for skipping automatic schema synchronization in production.
@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This PR adds SQLite database support alongside MySQL via better-sqlite3, introduces OIDC authentication alongside credential-based auth, implements a database migration system for better-auth tables, and updates login/signup flows with dynamic authentication mode selection based on environment configuration.

Changes

Cohort / File(s) Summary
Project Configuration
.gitignore, next.config.ts, package.json, pnpm-workspace.yaml
Adds SQLite database files to gitignore, expands serverExternalPackages to include better-sqlite3, upgrades better-sqlite3 to runtime dependency, adds mantine-form-zod-resolver for form handling, adds type declarations, updates better-auth:generate script, and configures pnpm onlyBuiltDependencies for better-sqlite3.
Server Startup & Environment
scripts/start-server.mjs, src/lib/environment.ts
Changes default database from MySQL to local SQLite file, updates server startup to use Next.js standalone script, passes homeDirectory parameter to database resolver, adds SUPERSAVE_SKIP_SYNC environment variable for migration control.
Database Layer
src/lib/database/index.ts, src/lib/database/migrations/better-auth.ts, src/lib/database/migrations/index.ts
Introduces migration system integration with SuperSave, adds SQLite and MySQL DDL schemas for better-auth tables (users, sessions, accounts, verification), implements migration runners for both database engines.
Authentication Configuration
src/lib/auth/config.ts, src/lib/auth/better-auth.ts
Adds database adapter factory that selects SQLite or MySQL based on connection string prefix, modifies getAuth to conditionally open database for CLI contexts, exports auth constant for better-auth CLI usage.
Authentication API
src/app/api/v1/config/route.ts, src/types/api/endpoints/get-auth-config.ts, src/types/api/endpoints/index.ts
Introduces new GET /v1/config endpoint that determines authMode from OIDC environment variables, defines and exports API response schema and types.
Login & Authentication Pages
src/app/(auth)/login/page.tsx, src/app/(auth)/signup/page.tsx
Replaces static login with dynamic multi-mode flow (fetches authMode from config, conditionally renders credentials form or OIDC button), adds new signup page with email/password validation, form handling via Mantine useForm and zod, error notifications, and loading states.
Layout & Navigation
src/app/@sidebar/page.tsx, src/app/@sidebar/[...catchAll]/page.tsx, src/components/layout.tsx, src/components/molecules/page-breadcrumb.tsx
Renames Sidebar to SidebarRoot, adds re-export in catchAll route, replaces anchor logout link with Next.js Link component, adjusts breadcrumb typography sizes and element semantics.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as Browser/Client
    participant Server as Next.js Server
    participant AuthAPI as Auth API
    participant AuthProvider as OIDC/Auth Service
    
    User->>Client: Visit /login
    Client->>Server: GET /api/v1/config
    Server->>Server: Check OIDC env vars
    Server-->>Client: { authMode: 'oidc' | 'credentials' }
    Client->>Client: Render form based on authMode
    
    alt Credentials Mode
        User->>Client: Enter email & password
        Client->>AuthAPI: signIn.email(email, password)
        AuthAPI-->>Client: Success / Error
        Client->>Server: Redirect to / or show error
    else OIDC Mode
        User->>Client: Click Sign In with OIDC
        Client->>AuthProvider: Initiate OAuth flow
        AuthProvider-->>Client: Redirect with auth code
        Client->>AuthAPI: Exchange code for session
        AuthAPI-->>Client: Session established
        Client->>Server: Redirect to /
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main objectives of the PR: adding SQLite support and enhancing authentication logic, which align with the substantial changes across database configuration, auth initialization, and login/signup forms.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@src/app/`(auth)/login/page.tsx:
- Around line 48-61: Replace the raw fetch in the useEffect's fetchAuthConfig
with the project API client: import the exported API client (e.g., apiClient)
and call apiClient.get('/config') inside fetchAuthConfig, read the response.data
as GetAuthConfigResponse, then call setAuthMode(data.authMode); keep the
existing try/catch behavior (default to 'credentials' on error) and ensure you
update the import at the top and types accordingly so fetchAuthConfig,
setAuthMode, and GetAuthConfigResponse work with the axios-style response.data
shape.
- Around line 85-91: handleOidcLogin currently sets isOidcLoading true but never
resets it if authClient.signIn.social fails or redirect is blocked; make
handleOidcLogin async, await authClient.signIn.social inside a try/catch, and in
the catch call setIsOidcLoading(false) (and optionally log or surface the
error); you can also reset the loading state in a finally block if you prefer,
but ensure setIsOidcLoading(false) is executed on error so the button doesn't
stay stuck.

In `@src/lib/database/migrations/better-auth.ts`:
- Around line 1-11: The documentation comment in better-auth.ts contains a
hardcoded secret value; replace the literal secret in the example env export
(the `BETTER_AUTH_SECRET=4d675dd...64ac` token) with a clear placeholder like
`BETTER_AUTH_SECRET=<your_secret_here>` or `BETTER_AUTH_SECRET=your_secret_here`
so the example remains functional but does not include real secrets; update any
matching example text in the same comment block to use the placeholder and keep
the rest of the instructions intact.

In `@src/lib/database/migrations/index.ts`:
- Around line 6-29: The migration SQL uses plain CREATE TABLE/CREATE INDEX which
will error if re-run; update the SQL constants BETTER_AUTH_SQLITE_SQL and
BETTER_AUTH_MYSQL_SQL (referenced by the migrations array) to add IF NOT EXISTS
on every CREATE TABLE and CREATE INDEX statement so repeated runMigrations()
calls do not fail; ensure both the SQLite and MySQL SQL strings in
better-auth.ts are changed accordingly and keep statement splitting/execution
logic in the mysql run block unchanged.
🧹 Nitpick comments (3)
src/components/layout.tsx (1)

51-61: Move inline styles to CSS module.

Per coding guidelines, component-specific styles should use CSS modules instead of inline styles. Consider extracting these styles to a .module.css file.

♻️ Suggested refactor

Create a CSS module file layout.module.css:

.logoutLink {
  text-decoration: none;
  color: inherit;
  font-weight: 500;
  font-size: 0.95rem;
}

Then update the component:

+import styles from './layout.module.css';
 import Link from 'next/link';
-          <Link
-            href="/logout"
-            style={{
-              textDecoration: 'none',
-              color: 'inherit',
-              fontWeight: 500,
-              fontSize: '0.95rem',
-            }}
-          >
-            Logout
-          </Link>
+          <Link href="/logout" className={styles.logoutLink}>
+            Logout
+          </Link>
src/lib/auth/config.ts (1)

17-23: Consider validating unsupported connection string schemes.

The function assumes any non-SQLite connection string is MySQL-compatible. If an unsupported scheme (e.g., postgres://) is accidentally passed, it will fail with a confusing MySQL connection error rather than a clear validation message.

Proposed validation
 function createDatabaseAdapter(connectionString: string) {
   if (connectionString.startsWith('sqlite://')) {
     const databasePath = connectionString.replace('sqlite://', '');
     return new Database(databasePath);
   }
+  if (!connectionString.startsWith('mysql://')) {
+    throw new Error(`Unsupported database connection string. Expected sqlite:// or mysql:// but got: ${connectionString.split('://')[0]}://`);
+  }
   return createPool(connectionString);
 }
src/lib/database/migrations/index.ts (1)

25-27: Consider wrapping MySQL migration in a transaction for atomicity.

If a statement fails mid-migration, the database will be left in a partially migrated state. Wrapping in a transaction would allow rollback on failure.

Proposed transaction wrapper
     run: async (superSave: SuperSave) => {
       const pool = superSave.getConnection<Pool>();
+      const connection = await pool.getConnection();
+      await connection.beginTransaction();
+      try {
         // MySQL requires executing statements one at a time
         const statements = BETTER_AUTH_MYSQL_SQL.split(';')
           .map((s) => s.trim())
           .filter((s) => s.length > 0);

         for (const statement of statements) {
-          await pool.query(statement);
+          await connection.query(statement);
         }
+        await connection.commit();
+      } catch (error) {
+        await connection.rollback();
+        throw error;
+      } finally {
+        connection.release();
+      }
     },

Comment on lines 48 to 61
useEffect(() => {
const fetchAuthConfig = async () => {
try {
const response = await fetch('/api/v1/config');
const data: GetAuthConfigResponse = await response.json();
setAuthMode(data.authMode);
} catch {
// Default to credentials if we can't fetch config
setAuthMode('credentials');
}
};

fetchAuthConfig();
}, []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use the API client instead of raw fetch.

Per coding guidelines, front-end code should call API routes through src/lib/api/client.ts which uses axios with baseURL /api/v1.

Proposed fix using API client
+import { apiClient } from '@/lib/api/client';
+
 // In the useEffect:
-        const response = await fetch('/api/v1/config');
-        const data: GetAuthConfigResponse = await response.json();
+        const { data } = await apiClient.get<GetAuthConfigResponse>('/config');
         setAuthMode(data.authMode);

Based on coding guidelines, front-end code must call API routes through src/lib/api/client.ts.

🤖 Prompt for AI Agents
In `@src/app/`(auth)/login/page.tsx around lines 48 - 61, Replace the raw fetch in
the useEffect's fetchAuthConfig with the project API client: import the exported
API client (e.g., apiClient) and call apiClient.get('/config') inside
fetchAuthConfig, read the response.data as GetAuthConfigResponse, then call
setAuthMode(data.authMode); keep the existing try/catch behavior (default to
'credentials' on error) and ensure you update the import at the top and types
accordingly so fetchAuthConfig, setAuthMode, and GetAuthConfigResponse work with
the axios-style response.data shape.

Comment on lines 85 to 91
const handleOidcLogin = () => {
setIsOidcLoading(true);
authClient.signIn.social({
provider: 'oidc',
callbackURL: `${globalThis.location.origin}/`,
});
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

OIDC loading state is never reset if redirect fails.

If authClient.signIn.social fails or the redirect doesn't occur (e.g., popup blocked), isOidcLoading remains true and the button stays in a perpetual loading state.

Proposed fix with error handling
-  const handleOidcLogin = () => {
+  const handleOidcLogin = async () => {
     setIsOidcLoading(true);
-    authClient.signIn.social({
-      provider: 'oidc',
-      callbackURL: `${globalThis.location.origin}/`,
-    });
+    try {
+      await authClient.signIn.social({
+        provider: 'oidc',
+        callbackURL: `${globalThis.location.origin}/`,
+      });
+    } catch (error) {
+      const errorMessage = error instanceof Error ? error.message : 'Failed to initiate sign in';
+      showError(errorMessage);
+      setIsOidcLoading(false);
+    }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleOidcLogin = () => {
setIsOidcLoading(true);
authClient.signIn.social({
provider: 'oidc',
callbackURL: `${globalThis.location.origin}/`,
});
};
const { showNotification } = useNotification();
const handleOidcLogin = async () => {
setIsOidcLoading(true);
try {
await authClient.signIn.social({
provider: 'oidc',
callbackURL: `${globalThis.location.origin}/`,
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to initiate sign in';
showNotification({ message: errorMessage, title: 'Sign In Error' });
setIsOidcLoading(false);
}
};
🤖 Prompt for AI Agents
In `@src/app/`(auth)/login/page.tsx around lines 85 - 91, handleOidcLogin
currently sets isOidcLoading true but never resets it if
authClient.signIn.social fails or redirect is blocked; make handleOidcLogin
async, await authClient.signIn.social inside a try/catch, and in the catch call
setIsOidcLoading(false) (and optionally log or surface the error); you can also
reset the loading state in a finally block if you prefer, but ensure
setIsOidcLoading(false) is executed on error so the button doesn't stay stuck.

Comment on lines 1 to 11
/**
* These migrations are generated using the better-auth CLI.
* ```
* export DB=sqlite://:memory:
* export NODE_ENV=development
* export BETTER_AUTH_SECRET=4d675dd169730483103eb302793617397357b4b86a63e19eb77c9cda3d0d64ac
* npx @better-auth/cli generate --config ./src/lib/auth/better-auth.ts
* ```
*
* to generate for mysql, replace DB= env var with a mysql alternative.
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace hardcoded secret with a placeholder in documentation.

The comment contains a hardcoded secret value (4d675dd169730483103eb302793617397357b4b86a63e19eb77c9cda3d0d64ac) which triggered the Gitleaks static analysis warning. While this is just documentation, it's best practice to use a placeholder to avoid:

  1. Accidental use in production
  2. Security scanner false positives
📝 Suggested fix
- * export BETTER_AUTH_SECRET=4d675dd169730483103eb302793617397357b4b86a63e19eb77c9cda3d0d64ac
+ * export BETTER_AUTH_SECRET=<your-secret-here>
🧰 Tools
🪛 Gitleaks (8.30.0)

[high] 6-6: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
In `@src/lib/database/migrations/better-auth.ts` around lines 1 - 11, The
documentation comment in better-auth.ts contains a hardcoded secret value;
replace the literal secret in the example env export (the
`BETTER_AUTH_SECRET=4d675dd...64ac` token) with a clear placeholder like
`BETTER_AUTH_SECRET=<your_secret_here>` or `BETTER_AUTH_SECRET=your_secret_here`
so the example remains functional but does not include real secrets; update any
matching example text in the same comment block to use the placeholder and keep
the rest of the instructions intact.

Comment on lines +6 to +29
export const migrations: Migration[] = [
{
name: 'better-auth-tables',
engine: 'sqlite',
run: async (superSave: SuperSave) => {
const database = superSave.getConnection<Database>();
database.exec(BETTER_AUTH_SQLITE_SQL);
},
},
{
name: 'better-auth-tables',
engine: 'mysql',
run: async (superSave: SuperSave) => {
const pool = superSave.getConnection<Pool>();
// MySQL requires executing statements one at a time
const statements = BETTER_AUTH_MYSQL_SQL.split(';')
.map((s) => s.trim())
.filter((s) => s.length > 0);

for (const statement of statements) {
await pool.query(statement);
}
},
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the SuperSave setup and see what files are involved
find . -type f -name "*.ts" -o -name "*.tsx" | grep -i "migration\|supersave\|database" | head -20

Repository: danships/thoth

Length of output: 545


🏁 Script executed:

# Let's look at the migration index file to understand the structure better
cat -n src/lib/database/migrations/index.ts

Repository: danships/thoth

Length of output: 1195


🏁 Script executed:

# Check where the SQL statements are defined
fd -type f -name "better-auth.ts"

Repository: danships/thoth

Length of output: 228


🏁 Script executed:

# Look for SuperSave package information and how migrations are tracked
cat package.json | jq '.dependencies' | grep -i "supersave\|migrate"

Repository: danships/thoth

Length of output: 87


🏁 Script executed:

# Check for any migration execution/runner code
fd -type f -name "*migration*" -o -name "*runner*" -o -name "*execute*" | grep -E "\.(ts|tsx)$"

Repository: danships/thoth

Length of output: 228


🏁 Script executed:

cat -n src/lib/database/migrations/better-auth.ts

Repository: danships/thoth

Length of output: 3776


🏁 Script executed:

# Let's search for any migration state tracking or database initialization
rg "migration" --type ts --type tsx -i | grep -v node_modules | head -15

Repository: danships/thoth

Length of output: 85


🏁 Script executed:

# Check for SuperSave configuration or initialization
rg "SuperSave\|supersave" --type ts --type tsx | grep -v node_modules

Repository: danships/thoth

Length of output: 85


🌐 Web query:

SuperSave migration tracking beta version 1.0.0-beta4 idempotency

💡 Result:

I can help — could you share the repository/ docs/ release notes or a link for "SuperSave migration tracking 1.0.0-beta4"? Also say what you mean by "idempotency": do you want to confirm whether migrations are idempotent, reproduce a bug, or get guidance to make them idempotent?


🏁 Script executed:

# Find SuperSave initialization or configuration
rg "new SuperSave\|createSuperSave\|SuperSave\(" --type ts -A 5 | head -30

Repository: danships/thoth

Length of output: 40


🏁 Script executed:

# Check database initialization/connection setup
find src -name "*.ts" -type f | xargs grep -l "SuperSave\|migrations" | head -10

Repository: danships/thoth

Length of output: 221


🏁 Script executed:

# Look at the database index file for initialization
cat -n src/lib/database/index.ts

Repository: danships/thoth

Length of output: 1627


🏁 Script executed:

# Check if there's any migration state table or tracking mechanism
rg "migration.*table\|_migrations\|applied.*migration" --type ts -i

Repository: danships/thoth

Length of output: 40


🌐 Web query:

SuperSave package npm migration idempotency tracking

💡 Result:

Short answer: Supersave (the @supersave/* packages on npm) does not appear to provide a built‑in, opinionated migration system with automatic idempotency tracking; you should either (A) make migrations idempotent and store applied‑migration metadata yourself in a Supersave collection, or (B) use a dedicated migration library that persists migration history (a migrations table/collection) and run idempotent migration steps. [1]. (npmjs.com)

Practical options (concise):

  • Make each migration idempotent (CREATE IF NOT EXISTS, guards, upserts) and write a small "migrations" collection/document that records migration id, hash, timestamp and status after successful run. This is simple with Supersave hooks/APIs. [1]. (npmjs.com)
  • Use an existing migration tool that supports stored history and idempotency (examples: OneTable Migrate for DynamoDB or other DB‑specific migrators) and run it alongside Supersave; those tools persist applied migrations so re‑runs are safe. [2]. (npmjs.com)
  • Follow idempotent SQL/DDL patterns (IF NOT EXISTS / existence checks / ALTER guarded by existence queries) or frameworks that recommend idempotent notebook/SQL migration patterns. [3]. (sprymd.org)

If you want, tell me:

  • which database backend Supersave is using for you (SQLite/Postgres/DynamoDB/etc.), and I’ll suggest a concrete migration + tracking implementation and example code.

Citations:


🏁 Script executed:

# Check how runMigrations is implemented or if there's error handling
rg "runMigrations\|applyMigration\|executeMigration" --type ts -B 2 -A 5 | head -40

Repository: danships/thoth

Length of output: 40


🏁 Script executed:

# Check if there's a migrations tracking table or collection
rg "migrations\|_applied\|migration.*log" --type ts -i | grep -v node_modules | head -20

Repository: danships/thoth

Length of output: 40


🏁 Script executed:

# Look for any error handling around migration execution
cat -n src/lib/database/index.ts | grep -A 10 -B 5 "runMigrations"

Repository: danships/thoth

Length of output: 556


🏁 Script executed:

# Check if migrations could be run multiple times
rg "getDatabase\|runMigrations" --type ts | head -20

Repository: danships/thoth

Length of output: 40


Add IF NOT EXISTS to migration SQL to prevent failures on re-execution.

SuperSave does not track applied migrations or prevent re-execution. Both SQLite and MySQL SQL statements use plain CREATE TABLE and CREATE INDEX without IF NOT EXISTS guards. If runMigrations() is called a second time (e.g., during testing, manual triggering, or misconfiguration), these statements will fail with "table already exists" errors.

Modify the SQL statements in better-auth.ts to include IF NOT EXISTS on all CREATE TABLE and CREATE INDEX statements.

🤖 Prompt for AI Agents
In `@src/lib/database/migrations/index.ts` around lines 6 - 29, The migration SQL
uses plain CREATE TABLE/CREATE INDEX which will error if re-run; update the SQL
constants BETTER_AUTH_SQLITE_SQL and BETTER_AUTH_MYSQL_SQL (referenced by the
migrations array) to add IF NOT EXISTS on every CREATE TABLE and CREATE INDEX
statement so repeated runMigrations() calls do not fail; ensure both the SQLite
and MySQL SQL strings in better-auth.ts are changed accordingly and keep
statement splitting/execution logic in the mysql run block unchanged.

@danships danships merged commit f7f919c into main Jan 22, 2026
2 checks passed
@danships danships deleted the feat/add-sqlite-support-and-auth-fixes branch January 22, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant