Signed-off-by: brookjeynes me@brookjeynes.dev
+108
-4
internal/db/db.go
+108
-4
internal/db/db.go
···
8
8
"strings"
9
9
10
10
_ "github.com/mattn/go-sqlite3"
11
+
"yoten.app/internal/server/log"
11
12
)
12
13
13
14
type DB struct {
···
26
27
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
27
28
}
28
29
29
-
func Make(ctx context.Context, dbPath string, logger *slog.Logger) (*DB, error) {
30
+
func Make(ctx context.Context, dbPath string) (*DB, error) {
30
31
opts := []string{
31
32
"_foreign_keys=1",
32
33
"_journal_mode=WAL",
33
34
"_synchronous=NORMAL",
34
35
"_auto_vacuum=incremental",
35
36
}
37
+
38
+
logger := log.FromContext(ctx)
39
+
logger = log.SubLogger(logger, "db")
36
40
37
41
db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&"))
38
42
if err != nil {
···
174
178
actor_did text not null,
175
179
subject_uri text not null,
176
180
177
-
state text not null default 'unread' check(state in ('unread', 'read')),
178
-
type text not null check(type in ('follow', 'reaction', 'comment')),
181
+
state integer not null default 0,
182
+
type text not null,
179
183
180
184
created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
181
185
···
213
217
is_deleted boolean not null default false,
214
218
created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
215
219
216
-
foreign key (did) references profiles(did) on delete cascade
220
+
foreign key (did) references profiles(did) on delete cascade,
217
221
unique (did, rkey)
218
222
);
219
223
···
231
235
return nil, fmt.Errorf("failed to execute db create statement: %w", err)
232
236
}
233
237
238
+
// This migration removes the type constraint on the notification type as
239
+
// it was painful to add new types. It also changes state to an integer
240
+
// check instead of text.
241
+
runMigration(conn, logger, "simplify-notification-constraints", func(tx *sql.Tx) error {
242
+
// Create new table with state as integer and no type constraint
243
+
_, err := tx.Exec(`
244
+
create table if not exists notifications_new (
245
+
id integer primary key autoincrement,
246
+
247
+
recipient_did text not null,
248
+
actor_did text not null,
249
+
subject_uri text not null,
250
+
251
+
state integer not null default 0,
252
+
type text not null,
253
+
254
+
created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
255
+
256
+
foreign key (recipient_did) references profiles(did) on delete cascade,
257
+
foreign key (actor_did) references profiles(did) on delete cascade
258
+
);
259
+
`)
260
+
if err != nil {
261
+
return err
262
+
}
263
+
264
+
// Copy data, converting state from text to integer
265
+
_, err = tx.Exec(`
266
+
insert into notifications_new (id, recipient_did, actor_did, subject_uri, state, type, created_at)
267
+
select
268
+
id,
269
+
recipient_did,
270
+
actor_did,
271
+
subject_uri,
272
+
case state
273
+
when 'unread' then 0
274
+
when 'read' then 1
275
+
else 0
276
+
end,
277
+
type,
278
+
created_at
279
+
from notifications;
280
+
`)
281
+
if err != nil {
282
+
return err
283
+
}
284
+
285
+
// Drop old table
286
+
_, err = tx.Exec(`drop table notifications`)
287
+
if err != nil {
288
+
return err
289
+
}
290
+
291
+
// Rename new table
292
+
_, err = tx.Exec(`alter table notifications_new rename to notifications`)
293
+
return err
294
+
})
295
+
234
296
return &DB{
235
297
db,
236
298
logger,
237
299
}, nil
238
300
}
301
+
302
+
type migrationFn = func(*sql.Tx) error
303
+
304
+
func runMigration(c *sql.Conn, logger *slog.Logger, name string, migrationFn migrationFn) error {
305
+
logger = logger.With("migration", name)
306
+
307
+
tx, err := c.BeginTx(context.Background(), nil)
308
+
if err != nil {
309
+
return err
310
+
}
311
+
defer tx.Rollback()
312
+
313
+
var exists bool
314
+
err = tx.QueryRow("select exists (select 1 from migrations where name = ?)", name).Scan(&exists)
315
+
if err != nil {
316
+
return err
317
+
}
318
+
319
+
if !exists {
320
+
err = migrationFn(tx)
321
+
if err != nil {
322
+
logger.Error("failed to run migration", "err", err)
323
+
return err
324
+
}
325
+
326
+
_, err = tx.Exec("insert into migrations (name) values (?)", name)
327
+
if err != nil {
328
+
logger.Error("failed to mark migration as complete", "err", err)
329
+
return err
330
+
}
331
+
332
+
if err := tx.Commit(); err != nil {
333
+
return err
334
+
}
335
+
336
+
logger.Info("migration applied successfully")
337
+
} else {
338
+
logger.Warn("skipped migration, already applied")
339
+
}
340
+
341
+
return nil
342
+
}
+6
-6
internal/db/notification.go
+6
-6
internal/db/notification.go
···
16
16
NotificationTypeReply NotificationType = "reply"
17
17
)
18
18
19
-
type NotificationState string
19
+
type NotificationState int
20
20
21
21
const (
22
-
NotificationStateUnread NotificationState = "unread"
23
-
NotificationStateRead NotificationState = "read"
22
+
NotificationStateUnread NotificationState = 0
23
+
NotificationStateRead NotificationState = 1
24
24
)
25
25
26
26
type NotificationWithBskyHandle struct {
···
124
124
}
125
125
126
126
func GetUnreadNotificationCount(e Execer, recipientDid string) (int, error) {
127
-
query := `select count(*) from notifications where recipient_did = ? and state = 'unread';`
127
+
query := `select count(*) from notifications where recipient_did = ? and state = 0;`
128
128
129
129
var count int
130
130
row := e.QueryRow(query, recipientDid)
···
138
138
func MarkAllNotificationsAsRead(e Execer, did string) error {
139
139
query := `
140
140
update notifications
141
-
set state = 'read'
142
-
where recipient_did = ? and state = 'unread';
141
+
set state = 1
142
+
where recipient_did = ? and state = 0;
143
143
`
144
144
145
145
_, err := e.Exec(query, did)
+1
-1
internal/server/app.go
+1
-1
internal/server/app.go
···
51
51
func Make(ctx context.Context, config *config.Config) (*Server, error) {
52
52
logger := log.FromContext(ctx)
53
53
54
-
d, err := db.Make(ctx, config.Core.DbPath, log.SubLogger(logger, "db"))
54
+
d, err := db.Make(ctx, config.Core.DbPath)
55
55
if err != nil {
56
56
return nil, err
57
57
}
+1
-1
internal/server/oauth/consts.go
+1
-1
internal/server/oauth/consts.go
+5
-5
internal/server/oauth/handler.go
+5
-5
internal/server/oauth/handler.go
···
34
34
clientName := ClientName
35
35
clientUri := ClientURI
36
36
37
-
meta := o.ClientApp.Config.ClientMetadata()
38
-
meta.JWKSURI = &o.JwksUri
39
-
meta.ClientName = &clientName
40
-
meta.ClientURI = &clientUri
37
+
doc := o.ClientApp.Config.ClientMetadata()
38
+
doc.JWKSURI = &o.JwksUri
39
+
doc.ClientName = &clientName
40
+
doc.ClientURI = &clientUri
41
41
42
42
w.Header().Set("Content-Type", "application/json")
43
-
if err := json.NewEncoder(w).Encode(meta); err != nil {
43
+
if err := json.NewEncoder(w).Encode(doc); err != nil {
44
44
http.Error(w, err.Error(), http.StatusInternalServerError)
45
45
return
46
46
}
-26
migrations/update_notification_type.sql
-26
migrations/update_notification_type.sql
···
1
-
-- This script should be used and updated whenever a new notification type
2
-
-- constraint needs to be added.
3
-
4
-
BEGIN TRANSACTION;
5
-
6
-
ALTER TABLE notifications RENAME TO notifications_old;
7
-
8
-
CREATE TABLE notifications (
9
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
10
-
recipient_did TEXT NOT NULL,
11
-
actor_did TEXT NOT NULL,
12
-
subject_uri TEXT NOT NULL,
13
-
state TEXT NOT NULL DEFAULT 'unread' CHECK(state IN ('unread', 'read')),
14
-
type TEXT NOT NULL CHECK(type IN ('follow', 'reaction', 'comment', 'reply')),
15
-
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
16
-
FOREIGN KEY (recipient_did) REFERENCES profiles(did) ON DELETE CASCADE,
17
-
FOREIGN KEY (actor_did) REFERENCES profiles(did) ON DELETE CASCADE
18
-
);
19
-
20
-
INSERT INTO notifications (id, recipient_did, actor_did, subject_uri, state, type, created_at)
21
-
SELECT id, recipient_did, actor_did, subject_uri, state, type, created_at
22
-
FROM notifications_old;
23
-
24
-
DROP TABLE notifications_old;
25
-
26
-
COMMIT;
History
1 round
0 comments
brookjeynes.dev
submitted
#0
expand 0 comments
pull request successfully merged