feat(backend): add DB persistence layer (#79)

* feat(backend): add DB persistence layer

Replace raw pg Pool with Drizzle ORM backed by Bun.sql.
Add per-user source configuration table (user_sources).
Migrate Better Auth to drizzle-adapter.
Add AES-256-GCM credential encryption.

Co-authored-by: Ona <no-reply@ona.com>

* fix(backend): set updatedAt explicitly in all mutations

onConflictDoUpdate bypasses Drizzle's $onUpdate hook.
Set updatedAt explicitly in all mutation methods.

Co-authored-by: Ona <no-reply@ona.com>

* fix(backend): add composite index on user_sources

Add (user_id, enabled) index for the enabled() query path.

Co-authored-by: Ona <no-reply@ona.com>

---------

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2026-03-16 01:30:02 +00:00
committed by GitHub
parent 9ac88d921c
commit 61c1ade631
29 changed files with 2034 additions and 115 deletions

View File

@@ -0,0 +1,32 @@
/**
* Thrown by a FeedSourceProvider when the source is not enabled for a user.
*
* UserSessionManager's Promise.allSettled handles this gracefully —
* the source is excluded from the session without crashing.
*/
export class SourceDisabledError extends Error {
readonly sourceId: string
readonly userId: string
constructor(sourceId: string, userId: string) {
super(`Source "${sourceId}" is not enabled for user "${userId}"`)
this.name = "SourceDisabledError"
this.sourceId = sourceId
this.userId = userId
}
}
/**
* Thrown when an operation targets a user source that doesn't exist.
*/
export class SourceNotFoundError extends Error {
readonly sourceId: string
readonly userId: string
constructor(sourceId: string, userId: string) {
super(`Source "${sourceId}" not found for user "${userId}"`)
this.name = "SourceNotFoundError"
this.sourceId = sourceId
this.userId = userId
}
}