@atproto/bsync 0.0.32 → 0.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/logger.js +2 -2
  3. package/dist/logger.js.map +1 -1
  4. package/package.json +17 -13
  5. package/bin/migration-create.ts +0 -38
  6. package/buf.gen.yaml +0 -12
  7. package/jest.config.cjs +0 -21
  8. package/proto/bsync.proto +0 -134
  9. package/src/client.ts +0 -25
  10. package/src/config.ts +0 -90
  11. package/src/context.ts +0 -48
  12. package/src/db/index.ts +0 -200
  13. package/src/db/migrations/20240108T220751294Z-init.ts +0 -26
  14. package/src/db/migrations/20240717T224303472Z-notif-ops.ts +0 -24
  15. package/src/db/migrations/20250527T022203400Z-add-operation.ts +0 -20
  16. package/src/db/migrations/20250603T163446567Z-alter-operation.ts +0 -19
  17. package/src/db/migrations/index.ts +0 -8
  18. package/src/db/migrations/provider.ts +0 -8
  19. package/src/db/schema/index.ts +0 -16
  20. package/src/db/schema/mute_item.ts +0 -13
  21. package/src/db/schema/mute_op.ts +0 -18
  22. package/src/db/schema/notif_item.ts +0 -13
  23. package/src/db/schema/notif_op.ts +0 -16
  24. package/src/db/schema/operation.ts +0 -20
  25. package/src/db/types.ts +0 -19
  26. package/src/index.ts +0 -132
  27. package/src/logger.ts +0 -26
  28. package/src/routes/add-mute-operation.ts +0 -154
  29. package/src/routes/add-notif-operation.ts +0 -80
  30. package/src/routes/auth.ts +0 -15
  31. package/src/routes/delete-operations.ts +0 -45
  32. package/src/routes/index.ts +0 -28
  33. package/src/routes/put-operation.ts +0 -115
  34. package/src/routes/scan-mute-operations.ts +0 -65
  35. package/src/routes/scan-notif-operations.ts +0 -64
  36. package/src/routes/scan-operations.ts +0 -67
  37. package/src/routes/util.ts +0 -67
  38. package/tests/delete-operations.test.ts +0 -108
  39. package/tests/mutes.test.ts +0 -352
  40. package/tests/notifications.test.ts +0 -209
  41. package/tests/operations.test.ts +0 -327
  42. package/tsconfig.build.json +0 -8
  43. package/tsconfig.build.tsbuildinfo +0 -1
  44. package/tsconfig.json +0 -7
  45. package/tsconfig.tests.json +0 -7
@@ -1,26 +0,0 @@
1
- import { Kysely, sql } from 'kysely'
2
-
3
- export async function up(db: Kysely<unknown>): Promise<void> {
4
- await db.schema
5
- .createTable('mute_op')
6
- .addColumn('id', 'bigserial', (col) => col.primaryKey())
7
- .addColumn('type', 'int2', (col) => col.notNull()) // integer enum: 0->add, 1->remove, 2->clear
8
- .addColumn('actorDid', 'varchar', (col) => col.notNull())
9
- .addColumn('subject', 'varchar', (col) => col.notNull())
10
- .addColumn('createdAt', 'timestamptz', (col) =>
11
- col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`),
12
- )
13
- .execute()
14
- await db.schema
15
- .createTable('mute_item')
16
- .addColumn('actorDid', 'varchar', (col) => col.notNull())
17
- .addColumn('subject', 'varchar', (col) => col.notNull())
18
- .addColumn('fromId', 'bigint', (col) => col.notNull())
19
- .addPrimaryKeyConstraint('mute_item_pkey', ['actorDid', 'subject'])
20
- .execute()
21
- }
22
-
23
- export async function down(db: Kysely<unknown>): Promise<void> {
24
- await db.schema.dropTable('mute_item').execute()
25
- await db.schema.dropTable('mute_op').execute()
26
- }
@@ -1,24 +0,0 @@
1
- import { Kysely, sql } from 'kysely'
2
-
3
- export async function up(db: Kysely<unknown>): Promise<void> {
4
- await db.schema
5
- .createTable('notif_op')
6
- .addColumn('id', 'bigserial', (col) => col.primaryKey())
7
- .addColumn('actorDid', 'varchar', (col) => col.notNull())
8
- .addColumn('priority', 'boolean')
9
- .addColumn('createdAt', 'timestamptz', (col) =>
10
- col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`),
11
- )
12
- .execute()
13
- await db.schema
14
- .createTable('notif_item')
15
- .addColumn('actorDid', 'varchar', (col) => col.primaryKey())
16
- .addColumn('priority', 'boolean', (col) => col.notNull())
17
- .addColumn('fromId', 'bigint', (col) => col.notNull())
18
- .execute()
19
- }
20
-
21
- export async function down(db: Kysely<unknown>): Promise<void> {
22
- await db.schema.dropTable('notif_item').execute()
23
- await db.schema.dropTable('notif_op').execute()
24
- }
@@ -1,20 +0,0 @@
1
- import { Kysely, sql } from 'kysely'
2
-
3
- export async function up(db: Kysely<unknown>): Promise<void> {
4
- await db.schema
5
- .createTable('operation')
6
- .addColumn('id', 'bigserial', (col) => col.primaryKey())
7
- .addColumn('collection', 'varchar', (col) => col.notNull())
8
- .addColumn('actorDid', 'varchar', (col) => col.notNull())
9
- .addColumn('rkey', 'varchar', (col) => col.notNull())
10
- .addColumn('method', 'int2', (col) => col.notNull())
11
- .addColumn('payload', sql`bytea`)
12
- .addColumn('createdAt', 'timestamptz', (col) =>
13
- col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`),
14
- )
15
- .execute()
16
- }
17
-
18
- export async function down(db: Kysely<unknown>): Promise<void> {
19
- await db.schema.dropTable('operation').execute()
20
- }
@@ -1,19 +0,0 @@
1
- import { Kysely } from 'kysely'
2
-
3
- export async function up(db: Kysely<unknown>): Promise<void> {
4
- await db.schema
5
- .alterTable('operation')
6
- .renameColumn('collection', 'namespace')
7
- .execute()
8
-
9
- await db.schema.alterTable('operation').renameColumn('rkey', 'key').execute()
10
- }
11
-
12
- export async function down(db: Kysely<unknown>): Promise<void> {
13
- await db.schema
14
- .alterTable('operation')
15
- .renameColumn('namespace', 'collection')
16
- .execute()
17
-
18
- await db.schema.alterTable('operation').renameColumn('key', 'rkey').execute()
19
- }
@@ -1,8 +0,0 @@
1
- // NOTE this file can be edited by hand, but it is also appended to by the migration:create command.
2
- // It's important that every migration is exported from here with the proper name. We'd simplify
3
- // this with kysely's FileMigrationProvider, but it doesn't play nicely with the build process.
4
-
5
- export * as _20240108T220751294Z from './20240108T220751294Z-init.js'
6
- export * as _20240717T224303472Z from './20240717T224303472Z-notif-ops.js'
7
- export * as _20250527T022203400Z from './20250527T022203400Z-add-operation.js'
8
- export * as _20250603T163446567Z from './20250603T163446567Z-alter-operation.js'
@@ -1,8 +0,0 @@
1
- import { Migration, MigrationProvider } from 'kysely/migration'
2
-
3
- export class DbMigrationProvider implements MigrationProvider {
4
- constructor(private migrations: Record<string, Migration>) {}
5
- async getMigrations(): Promise<Record<string, Migration>> {
6
- return this.migrations
7
- }
8
- }
@@ -1,16 +0,0 @@
1
- import { Kysely } from 'kysely'
2
- import * as muteItem from './mute_item.js'
3
- import * as muteOp from './mute_op.js'
4
- import * as notifItem from './notif_item.js'
5
- import * as notifOp from './notif_op.js'
6
- import * as op from './operation.js'
7
-
8
- export type DatabaseSchemaType = muteItem.PartialDB &
9
- muteOp.PartialDB &
10
- notifItem.PartialDB &
11
- notifOp.PartialDB &
12
- op.PartialDB
13
-
14
- export type DatabaseSchema = Kysely<DatabaseSchemaType>
15
-
16
- export default DatabaseSchema
@@ -1,13 +0,0 @@
1
- import { Selectable } from 'kysely'
2
-
3
- export interface MuteItem {
4
- actorDid: string
5
- subject: string // did or aturi for list
6
- fromId: number
7
- }
8
-
9
- export type MuteItemEntry = Selectable<MuteItem>
10
-
11
- export const tableName = 'mute_item'
12
-
13
- export type PartialDB = { [tableName]: MuteItem }
@@ -1,18 +0,0 @@
1
- import { GeneratedAlways, Selectable } from 'kysely'
2
- import { MuteOperation_Type } from '../../proto/bsync_pb.js'
3
-
4
- export interface MuteOp {
5
- id: GeneratedAlways<number>
6
- type: MuteOperation_Type // integer enum: 0->add, 1->remove, 2->clear
7
- actorDid: string
8
- subject: string // did or aturi for list
9
- createdAt: GeneratedAlways<Date>
10
- }
11
-
12
- export type MuteOpEntry = Selectable<MuteOp>
13
-
14
- export const tableName = 'mute_op'
15
-
16
- export type PartialDB = { [tableName]: MuteOp }
17
-
18
- export const createMuteOpChannel = 'mute_op_create' // used with listen/notify
@@ -1,13 +0,0 @@
1
- import { Selectable } from 'kysely'
2
-
3
- export interface NotifItem {
4
- actorDid: string
5
- priority: boolean
6
- fromId: number
7
- }
8
-
9
- export type NotifItemEntry = Selectable<NotifItem>
10
-
11
- export const tableName = 'notif_item'
12
-
13
- export type PartialDB = { [tableName]: NotifItem }
@@ -1,16 +0,0 @@
1
- import { GeneratedAlways, Selectable } from 'kysely'
2
-
3
- export interface NotifOp {
4
- id: GeneratedAlways<number>
5
- actorDid: string
6
- priority: boolean | null
7
- createdAt: GeneratedAlways<Date>
8
- }
9
-
10
- export type NotifOpEntry = Selectable<NotifOp>
11
-
12
- export const tableName = 'notif_op'
13
-
14
- export type PartialDB = { [tableName]: NotifOp }
15
-
16
- export const createNotifOpChannel = 'notif_op_create' // used with listen/notify
@@ -1,20 +0,0 @@
1
- import { GeneratedAlways } from 'kysely'
2
- import { Method } from '../../proto/bsync_pb.js'
3
-
4
- export type OperationMethod = Method.CREATE | Method.UPDATE | Method.DELETE
5
-
6
- export interface Operation {
7
- id: GeneratedAlways<number>
8
- actorDid: string
9
- namespace: string
10
- key: string
11
- method: OperationMethod
12
- payload: Uint8Array<ArrayBuffer>
13
- createdAt: GeneratedAlways<Date>
14
- }
15
-
16
- export const tableName = 'operation'
17
-
18
- export type PartialDB = { [tableName]: Operation }
19
-
20
- export const createOperationChannel = 'operation_create' // used with listen/notify
package/src/db/types.ts DELETED
@@ -1,19 +0,0 @@
1
- import { DynamicModule, RawBuilder, SelectQueryBuilder } from 'kysely'
2
- // eslint-disable-next-line import/default
3
- import pg from 'pg'
4
- type PgPool = pg.Pool
5
-
6
- export type DbRef =
7
- | RawBuilder<unknown>
8
- | ReturnType<DynamicModule<unknown>['ref']>
9
-
10
- export type AnyQb = SelectQueryBuilder<any, any, any>
11
-
12
- export type PgOptions = {
13
- url: string
14
- pool?: PgPool
15
- schema?: string
16
- poolSize?: number
17
- poolMaxUses?: number
18
- poolIdleTimeoutMs?: number
19
- }
package/src/index.ts DELETED
@@ -1,132 +0,0 @@
1
- import events, { setMaxListeners } from 'node:events'
2
- import http from 'node:http'
3
- import { connectNodeAdapter } from '@connectrpc/connect-node'
4
- // eslint-disable-next-line import/default
5
- import httpTerminator from 'http-terminator'
6
- import { ServerConfig } from './config.js'
7
- import { AppContext, AppContextOptions } from './context.js'
8
- import { createMuteOpChannel } from './db/schema/mute_op.js'
9
- import { createNotifOpChannel } from './db/schema/notif_op.js'
10
- import { createOperationChannel } from './db/schema/operation.js'
11
- import { dbLogger, loggerMiddleware } from './logger.js'
12
- import routes from './routes/index.js'
13
-
14
- export * from './config.js'
15
- export * from './client.js'
16
- export { Database } from './db/index.js'
17
- export { AppContext } from './context.js'
18
- export { httpLogger } from './logger.js'
19
-
20
- type BsyncServiceState = 'initialized' | 'started' | 'destroyed'
21
-
22
- export class BsyncService {
23
- public ctx: AppContext
24
- public server: http.Server
25
- private terminator: httpTerminator.HttpTerminator
26
- private ac: AbortController
27
- private state: BsyncServiceState = 'initialized'
28
-
29
- constructor(opts: {
30
- ctx: AppContext
31
- server: http.Server
32
- ac: AbortController
33
- }) {
34
- this.ctx = opts.ctx
35
- this.server = opts.server
36
- this.ac = opts.ac
37
- this.terminator = httpTerminator.createHttpTerminator(opts)
38
- }
39
-
40
- static async create(
41
- cfg: ServerConfig,
42
- overrides?: Partial<AppContextOptions>,
43
- ): Promise<BsyncService> {
44
- const ac = new AbortController()
45
- // Prevents unhelpful warnings.
46
- setMaxListeners(100, ac.signal)
47
- const ctx = await AppContext.fromConfig(cfg, ac.signal, overrides)
48
- const handler = connectNodeAdapter({
49
- routes: routes(ctx),
50
- shutdownSignal: ac.signal,
51
- })
52
- const server = http.createServer((req, res) => {
53
- loggerMiddleware(req, res)
54
- if (isHealth(req.url)) {
55
- res.statusCode = 200
56
- res.setHeader('content-type', 'application/json')
57
- return res.end(JSON.stringify({ version: cfg.service.version }))
58
- }
59
- handler(req, res)
60
- })
61
- return new BsyncService({ ctx, server, ac })
62
- }
63
-
64
- async start(): Promise<http.Server> {
65
- if (this.state !== 'initialized') {
66
- throw new Error(`${this.constructor.name} already started`)
67
- }
68
- this.state = 'started'
69
-
70
- const dbStatsInterval = setInterval(() => {
71
- dbLogger.info(
72
- {
73
- idleCount: this.ctx.db.pool.idleCount,
74
- totalCount: this.ctx.db.pool.totalCount,
75
- waitingCount: this.ctx.db.pool.waitingCount,
76
- },
77
- 'db pool stats',
78
- )
79
- }, 10000)
80
-
81
- this.ac.signal.addEventListener('abort', () => {
82
- clearInterval(dbStatsInterval)
83
- })
84
-
85
- await this.setupAppEvents()
86
- this.server.listen(this.ctx.cfg.service.port)
87
- this.server.keepAliveTimeout = 90000
88
- await events.once(this.server, 'listening')
89
- return this.server
90
- }
91
-
92
- async destroy(): Promise<void> {
93
- if (this.state === 'destroyed') return
94
- this.state = 'destroyed'
95
- this.ac.abort()
96
- try {
97
- await this.terminator.terminate()
98
- } finally {
99
- await this.ctx.db.close()
100
- }
101
- }
102
-
103
- async setupAppEvents() {
104
- const conn = await this.ctx.db.pool.connect()
105
- this.ac.signal.addEventListener('abort', () => conn.release(), {
106
- once: true,
107
- })
108
- // if these error, unhandled rejection should cause process to exit
109
- conn.query(`listen ${createMuteOpChannel}`)
110
- conn.query(`listen ${createNotifOpChannel}`)
111
- conn.query(`listen ${createOperationChannel}`)
112
- conn.on('notification', (notif) => {
113
- if (notif.channel === createMuteOpChannel) {
114
- this.ctx.events.emit(createMuteOpChannel)
115
- }
116
- if (notif.channel === createNotifOpChannel) {
117
- this.ctx.events.emit(createNotifOpChannel)
118
- }
119
- if (notif.channel === createOperationChannel) {
120
- this.ctx.events.emit(createOperationChannel)
121
- }
122
- })
123
- }
124
- }
125
-
126
- export default BsyncService
127
-
128
- const isHealth = (urlStr: string | undefined) => {
129
- if (!urlStr) return false
130
- const url = new URL(urlStr, 'http://host')
131
- return url.pathname === '/_health'
132
- }
package/src/logger.ts DELETED
@@ -1,26 +0,0 @@
1
- import { type IncomingMessage } from 'node:http'
2
- import { pinoHttp, stdSerializers } from 'pino-http'
3
- import { obfuscateHeaders, subsystemLogger } from '@atproto/common'
4
-
5
- export const dbLogger: ReturnType<typeof subsystemLogger> =
6
- subsystemLogger('bsync:db')
7
- export const httpLogger: ReturnType<typeof subsystemLogger> =
8
- subsystemLogger('bsync')
9
-
10
- export const loggerMiddleware = pinoHttp({
11
- logger: httpLogger,
12
- redact: {
13
- paths: ['req.headers.authorization'],
14
- },
15
- serializers: {
16
- err: (err: unknown) => ({
17
- code: err?.['code'],
18
- message: err?.['message'],
19
- }),
20
- req: (req: IncomingMessage) => {
21
- const serialized = stdSerializers.req(req)
22
- const headers = obfuscateHeaders(serialized.headers)
23
- return { ...serialized, headers }
24
- },
25
- },
26
- })
@@ -1,154 +0,0 @@
1
- import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
2
- import { sql } from 'kysely'
3
- import { AtUri } from '@atproto/syntax'
4
- import { AppContext } from '../context.js'
5
- import { Database } from '../db/index.js'
6
- import { createMuteOpChannel } from '../db/schema/mute_op.js'
7
- import { Service } from '../proto/bsync_connect.js'
8
- import {
9
- AddMuteOperationResponse,
10
- MuteOperation_Type,
11
- } from '../proto/bsync_pb.js'
12
- import { authWithApiKey } from './auth.js'
13
- import { isValidAtUri, isValidDid } from './util.js'
14
-
15
- export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
16
- async addMuteOperation(req, handlerCtx) {
17
- authWithApiKey(ctx, handlerCtx)
18
- const { db } = ctx
19
- const op = validMuteOp(req)
20
- const id = await db.transaction(async (txn) => {
21
- // create mute op
22
- const id = await createMuteOp(txn, op)
23
- // update mute state
24
- if (op.type === MuteOperation_Type.ADD) {
25
- await addMuteItem(txn, id, op)
26
- } else if (op.type === MuteOperation_Type.REMOVE) {
27
- await removeMuteItem(txn, op)
28
- } else if (op.type === MuteOperation_Type.CLEAR) {
29
- await clearMuteItems(txn, op)
30
- } else {
31
- const exhaustiveCheck: never = op.type
32
- throw new Error(`unreachable: ${exhaustiveCheck}`)
33
- }
34
- return id
35
- })
36
- return new AddMuteOperationResponse({
37
- operation: {
38
- id: String(id),
39
- type: op.type,
40
- actorDid: op.actorDid,
41
- subject: op.subject,
42
- },
43
- })
44
- },
45
- })
46
-
47
- const createMuteOp = async (db: Database, op: MuteOpInfo) => {
48
- const { ref } = db.db.dynamic
49
- const { id } = await db.db
50
- .insertInto('mute_op')
51
- .values({
52
- type: op.type,
53
- actorDid: op.actorDid,
54
- subject: op.subject,
55
- })
56
- .returning('id')
57
- .executeTakeFirstOrThrow()
58
- await sql`notify ${ref(createMuteOpChannel)}`.execute(db.db) // emitted transactionally
59
- return id
60
- }
61
-
62
- const addMuteItem = async (db: Database, fromId: number, op: MuteOpInfo) => {
63
- const { ref } = db.db.dynamic
64
- await db.db
65
- .insertInto('mute_item')
66
- .values({
67
- actorDid: op.actorDid,
68
- subject: op.subject,
69
- fromId,
70
- })
71
- .onConflict((oc) =>
72
- oc
73
- .constraint('mute_item_pkey')
74
- .doUpdateSet({ fromId: sql`${ref('excluded.fromId')}` }),
75
- )
76
- .execute()
77
- }
78
-
79
- const removeMuteItem = async (db: Database, op: MuteOpInfo) => {
80
- await db.db
81
- .deleteFrom('mute_item')
82
- .where('actorDid', '=', op.actorDid)
83
- .where('subject', '=', op.subject)
84
- .execute()
85
- }
86
-
87
- const clearMuteItems = async (db: Database, op: MuteOpInfo) => {
88
- await db.db
89
- .deleteFrom('mute_item')
90
- .where('actorDid', '=', op.actorDid)
91
- .execute()
92
- }
93
-
94
- const validMuteOp = (op: MuteOpInfo): MuteOpInfoValid => {
95
- if (!Object.values(MuteOperation_Type).includes(op.type)) {
96
- throw new ConnectError('bad mute operation type', Code.InvalidArgument)
97
- }
98
- if (op.type === MuteOperation_Type.UNSPECIFIED) {
99
- throw new ConnectError(
100
- 'unspecified mute operation type',
101
- Code.InvalidArgument,
102
- )
103
- }
104
- if (!isValidDid(op.actorDid)) {
105
- throw new ConnectError(
106
- 'actor_did must be a valid did',
107
- Code.InvalidArgument,
108
- )
109
- }
110
- if (op.type === MuteOperation_Type.CLEAR) {
111
- if (op.subject !== '') {
112
- throw new ConnectError(
113
- 'subject must not be set on a clear op',
114
- Code.InvalidArgument,
115
- )
116
- }
117
- } else {
118
- if (isValidDid(op.subject)) {
119
- // all good
120
- } else if (isValidAtUri(op.subject)) {
121
- const uri = new AtUri(op.subject)
122
- if (
123
- uri.collection !== 'app.bsky.graph.list' &&
124
- uri.collection !== 'app.bsky.feed.post'
125
- ) {
126
- throw new ConnectError(
127
- 'subject aturis must reference a list or post record',
128
- Code.InvalidArgument,
129
- )
130
- }
131
- } else {
132
- throw new ConnectError(
133
- 'subject must be a did or aturi on add or remove op',
134
- Code.InvalidArgument,
135
- )
136
- }
137
- }
138
- return op as MuteOpInfoValid // op.type has been checked
139
- }
140
-
141
- type MuteOpInfo = {
142
- type: MuteOperation_Type
143
- actorDid: string
144
- subject: string
145
- }
146
-
147
- type MuteOpInfoValid = {
148
- type:
149
- | MuteOperation_Type.ADD
150
- | MuteOperation_Type.REMOVE
151
- | MuteOperation_Type.CLEAR
152
- actorDid: string
153
- subject: string
154
- }
@@ -1,80 +0,0 @@
1
- import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
2
- import { sql } from 'kysely'
3
- import { AppContext } from '../context.js'
4
- import { Database } from '../db/index.js'
5
- import { createNotifOpChannel } from '../db/schema/notif_op.js'
6
- import { Service } from '../proto/bsync_connect.js'
7
- import { AddNotifOperationResponse } from '../proto/bsync_pb.js'
8
- import { authWithApiKey } from './auth.js'
9
- import { isValidDid } from './util.js'
10
-
11
- export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
12
- async addNotifOperation(req, handlerCtx) {
13
- authWithApiKey(ctx, handlerCtx)
14
- const { db } = ctx
15
- const { actorDid, priority } = req
16
- if (!isValidDid(actorDid)) {
17
- throw new ConnectError(
18
- 'actor_did must be a valid did',
19
- Code.InvalidArgument,
20
- )
21
- }
22
- const id = await db.transaction(async (txn) => {
23
- // create notif op
24
- const id = await createNotifOp(txn, actorDid, priority)
25
- // update notif state
26
- if (priority !== undefined) {
27
- await updateNotifItem(txn, id, actorDid, priority)
28
- }
29
- return id
30
- })
31
- return new AddNotifOperationResponse({
32
- operation: {
33
- id: String(id),
34
- actorDid,
35
- priority,
36
- },
37
- })
38
- },
39
- })
40
-
41
- const createNotifOp = async (
42
- db: Database,
43
- actorDid: string,
44
- priority: boolean | undefined,
45
- ) => {
46
- const { ref } = db.db.dynamic
47
- const { id } = await db.db
48
- .insertInto('notif_op')
49
- .values({
50
- actorDid,
51
- priority,
52
- })
53
- .returning('id')
54
- .executeTakeFirstOrThrow()
55
- await sql`notify ${ref(createNotifOpChannel)}`.execute(db.db) // emitted transactionally
56
- return id
57
- }
58
-
59
- const updateNotifItem = async (
60
- db: Database,
61
- fromId: number,
62
- actorDid: string,
63
- priority: boolean,
64
- ) => {
65
- const { ref } = db.db.dynamic
66
- await db.db
67
- .insertInto('notif_item')
68
- .values({
69
- actorDid,
70
- priority,
71
- fromId,
72
- })
73
- .onConflict((oc) =>
74
- oc.column('actorDid').doUpdateSet({
75
- priority: sql`${ref('excluded.priority')}`,
76
- fromId: sql`${ref('excluded.fromId')}`,
77
- }),
78
- )
79
- .execute()
80
- }
@@ -1,15 +0,0 @@
1
- import { Code, ConnectError, HandlerContext } from '@connectrpc/connect'
2
- import { AppContext } from '../context.js'
3
-
4
- const BEARER = 'Bearer '
5
-
6
- export const authWithApiKey = (ctx: AppContext, handlerCtx: HandlerContext) => {
7
- const authorization = handlerCtx.requestHeader.get('authorization')
8
- if (!authorization?.startsWith(BEARER)) {
9
- throw new ConnectError('missing auth', Code.Unauthenticated)
10
- }
11
- const key = authorization.slice(BEARER.length)
12
- if (!ctx.cfg.auth.apiKeys.has(key)) {
13
- throw new ConnectError('invalid api key', Code.Unauthenticated)
14
- }
15
- }