···648649 return nil
650}
651+func (t *FeedComment) MarshalCBOR(w io.Writer) error {
652+ if t == nil {
653+ _, err := w.Write(cbg.CborNull)
654+ return err
655+ }
656+657+ cw := cbg.NewCborWriter(w)
658+ fieldCount := 5
659+660+ if t.Reply == nil {
661+ fieldCount--
662+ }
663+664+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
665+ return err
666+ }
667+668+ // t.Body (string) (string)
669+ if len("body") > 1000000 {
670+ return xerrors.Errorf("Value in field \"body\" was too long")
671+ }
672+673+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("body"))); err != nil {
674+ return err
675+ }
676+ if _, err := cw.WriteString(string("body")); err != nil {
677+ return err
678+ }
679+680+ if len(t.Body) > 1000000 {
681+ return xerrors.Errorf("Value in field t.Body was too long")
682+ }
683+684+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Body))); err != nil {
685+ return err
686+ }
687+ if _, err := cw.WriteString(string(t.Body)); err != nil {
688+ return err
689+ }
690+691+ // t.LexiconTypeID (string) (string)
692+ if len("$type") > 1000000 {
693+ return xerrors.Errorf("Value in field \"$type\" was too long")
694+ }
695+696+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
697+ return err
698+ }
699+ if _, err := cw.WriteString(string("$type")); err != nil {
700+ return err
701+ }
702+703+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("app.yoten.feed.comment"))); err != nil {
704+ return err
705+ }
706+ if _, err := cw.WriteString(string("app.yoten.feed.comment")); err != nil {
707+ return err
708+ }
709+710+ // t.Reply (yoten.FeedComment_Reply) (struct)
711+ if t.Reply != nil {
712+713+ if len("reply") > 1000000 {
714+ return xerrors.Errorf("Value in field \"reply\" was too long")
715+ }
716+717+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("reply"))); err != nil {
718+ return err
719+ }
720+ if _, err := cw.WriteString(string("reply")); err != nil {
721+ return err
722+ }
723+724+ if err := t.Reply.MarshalCBOR(cw); err != nil {
725+ return err
726+ }
727+ }
728+729+ // t.Subject (string) (string)
730+ if len("subject") > 1000000 {
731+ return xerrors.Errorf("Value in field \"subject\" was too long")
732+ }
733+734+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
735+ return err
736+ }
737+ if _, err := cw.WriteString(string("subject")); err != nil {
738+ return err
739+ }
740+741+ if len(t.Subject) > 1000000 {
742+ return xerrors.Errorf("Value in field t.Subject was too long")
743+ }
744+745+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil {
746+ return err
747+ }
748+ if _, err := cw.WriteString(string(t.Subject)); err != nil {
749+ return err
750+ }
751+752+ // t.CreatedAt (string) (string)
753+ if len("createdAt") > 1000000 {
754+ return xerrors.Errorf("Value in field \"createdAt\" was too long")
755+ }
756+757+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
758+ return err
759+ }
760+ if _, err := cw.WriteString(string("createdAt")); err != nil {
761+ return err
762+ }
763+764+ if len(t.CreatedAt) > 1000000 {
765+ return xerrors.Errorf("Value in field t.CreatedAt was too long")
766+ }
767+768+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
769+ return err
770+ }
771+ if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
772+ return err
773+ }
774+ return nil
775+}
776+777+func (t *FeedComment) UnmarshalCBOR(r io.Reader) (err error) {
778+ *t = FeedComment{}
779+780+ cr := cbg.NewCborReader(r)
781+782+ maj, extra, err := cr.ReadHeader()
783+ if err != nil {
784+ return err
785+ }
786+ defer func() {
787+ if err == io.EOF {
788+ err = io.ErrUnexpectedEOF
789+ }
790+ }()
791+792+ if maj != cbg.MajMap {
793+ return fmt.Errorf("cbor input should be of type map")
794+ }
795+796+ if extra > cbg.MaxLength {
797+ return fmt.Errorf("FeedComment: map struct too large (%d)", extra)
798+ }
799+800+ n := extra
801+802+ nameBuf := make([]byte, 9)
803+ for i := uint64(0); i < n; i++ {
804+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
805+ if err != nil {
806+ return err
807+ }
808+809+ if !ok {
810+ // Field doesn't exist on this type, so ignore it
811+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
812+ return err
813+ }
814+ continue
815+ }
816+817+ switch string(nameBuf[:nameLen]) {
818+ // t.Body (string) (string)
819+ case "body":
820+821+ {
822+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
823+ if err != nil {
824+ return err
825+ }
826+827+ t.Body = string(sval)
828+ }
829+ // t.LexiconTypeID (string) (string)
830+ case "$type":
831+832+ {
833+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
834+ if err != nil {
835+ return err
836+ }
837+838+ t.LexiconTypeID = string(sval)
839+ }
840+ // t.Reply (yoten.FeedComment_Reply) (struct)
841+ case "reply":
842+843+ {
844+845+ b, err := cr.ReadByte()
846+ if err != nil {
847+ return err
848+ }
849+ if b != cbg.CborNull[0] {
850+ if err := cr.UnreadByte(); err != nil {
851+ return err
852+ }
853+ t.Reply = new(FeedComment_Reply)
854+ if err := t.Reply.UnmarshalCBOR(cr); err != nil {
855+ return xerrors.Errorf("unmarshaling t.Reply pointer: %w", err)
856+ }
857+ }
858+859+ }
860+ // t.Subject (string) (string)
861+ case "subject":
862+863+ {
864+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
865+ if err != nil {
866+ return err
867+ }
868+869+ t.Subject = string(sval)
870+ }
871+ // t.CreatedAt (string) (string)
872+ case "createdAt":
873+874+ {
875+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
876+ if err != nil {
877+ return err
878+ }
879+880+ t.CreatedAt = string(sval)
881+ }
882+883+ default:
884+ // Field doesn't exist on this type, so ignore it
885+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
886+ return err
887+ }
888+ }
889+ }
890+891+ return nil
892+}
893+func (t *FeedComment_Reply) MarshalCBOR(w io.Writer) error {
894+ if t == nil {
895+ _, err := w.Write(cbg.CborNull)
896+ return err
897+ }
898+899+ cw := cbg.NewCborWriter(w)
900+901+ if _, err := cw.Write([]byte{162}); err != nil {
902+ return err
903+ }
904+905+ // t.Root (string) (string)
906+ if len("root") > 1000000 {
907+ return xerrors.Errorf("Value in field \"root\" was too long")
908+ }
909+910+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("root"))); err != nil {
911+ return err
912+ }
913+ if _, err := cw.WriteString(string("root")); err != nil {
914+ return err
915+ }
916+917+ if len(t.Root) > 1000000 {
918+ return xerrors.Errorf("Value in field t.Root was too long")
919+ }
920+921+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Root))); err != nil {
922+ return err
923+ }
924+ if _, err := cw.WriteString(string(t.Root)); err != nil {
925+ return err
926+ }
927+928+ // t.Parent (string) (string)
929+ if len("parent") > 1000000 {
930+ return xerrors.Errorf("Value in field \"parent\" was too long")
931+ }
932+933+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("parent"))); err != nil {
934+ return err
935+ }
936+ if _, err := cw.WriteString(string("parent")); err != nil {
937+ return err
938+ }
939+940+ if len(t.Parent) > 1000000 {
941+ return xerrors.Errorf("Value in field t.Parent was too long")
942+ }
943+944+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Parent))); err != nil {
945+ return err
946+ }
947+ if _, err := cw.WriteString(string(t.Parent)); err != nil {
948+ return err
949+ }
950+ return nil
951+}
952+953+func (t *FeedComment_Reply) UnmarshalCBOR(r io.Reader) (err error) {
954+ *t = FeedComment_Reply{}
955+956+ cr := cbg.NewCborReader(r)
957+958+ maj, extra, err := cr.ReadHeader()
959+ if err != nil {
960+ return err
961+ }
962+ defer func() {
963+ if err == io.EOF {
964+ err = io.ErrUnexpectedEOF
965+ }
966+ }()
967+968+ if maj != cbg.MajMap {
969+ return fmt.Errorf("cbor input should be of type map")
970+ }
971+972+ if extra > cbg.MaxLength {
973+ return fmt.Errorf("FeedComment_Reply: map struct too large (%d)", extra)
974+ }
975+976+ n := extra
977+978+ nameBuf := make([]byte, 6)
979+ for i := uint64(0); i < n; i++ {
980+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
981+ if err != nil {
982+ return err
983+ }
984+985+ if !ok {
986+ // Field doesn't exist on this type, so ignore it
987+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
988+ return err
989+ }
990+ continue
991+ }
992+993+ switch string(nameBuf[:nameLen]) {
994+ // t.Root (string) (string)
995+ case "root":
996+997+ {
998+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
999+ if err != nil {
1000+ return err
1001+ }
1002+1003+ t.Root = string(sval)
1004+ }
1005+ // t.Parent (string) (string)
1006+ case "parent":
1007+1008+ {
1009+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
1010+ if err != nil {
1011+ return err
1012+ }
1013+1014+ t.Parent = string(sval)
1015+ }
1016+1017+ default:
1018+ // Field doesn't exist on this type, so ignore it
1019+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1020+ return err
1021+ }
1022+ }
1023+ }
1024+1025+ return nil
1026+}
1027func (t *FeedReaction) MarshalCBOR(w io.Writer) error {
1028 if t == nil {
1029 _, err := w.Write(cbg.CborNull)
+35
api/yoten/feedcomment.go
···00000000000000000000000000000000000
···1+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2+3+package yoten
4+5+// schema: app.yoten.feed.comment
6+7+import (
8+ "github.com/bluesky-social/indigo/lex/util"
9+)
10+11+const (
12+ FeedCommentNSID = "app.yoten.feed.comment"
13+)
14+15+func init() {
16+ util.RegisterType("app.yoten.feed.comment", &FeedComment{})
17+} //
18+// RECORDTYPE: FeedComment
19+type FeedComment struct {
20+ LexiconTypeID string `json:"$type,const=app.yoten.feed.comment" cborgen:"$type,const=app.yoten.feed.comment"`
21+ Body string `json:"body" cborgen:"body"`
22+ CreatedAt string `json:"createdAt" cborgen:"createdAt"`
23+ // reply: Indicates that this comment is a reply to another comment.
24+ Reply *FeedComment_Reply `json:"reply,omitempty" cborgen:"reply,omitempty"`
25+ // subject: A reference to the study session being commented on.
26+ Subject string `json:"subject" cborgen:"subject"`
27+}
28+29+// Indicates that this comment is a reply to another comment.
30+type FeedComment_Reply struct {
31+ // parent: A reference to the specific comment being replied to.
32+ Parent string `json:"parent" cborgen:"parent"`
33+ // root: A reference to the original study session (the root of the conversation).
34+ Root string `json:"root" cborgen:"root"`
35+}
+2
cmd/gen.go
···28 yoten.ActivityDef{},
29 yoten.GraphFollow{},
30 yoten.FeedReaction{},
0031 }
3233 for name, rt := range AllLexTypes() {
···28 yoten.ActivityDef{},
29 yoten.GraphFollow{},
30 yoten.FeedReaction{},
31+ yoten.FeedComment{},
32+ yoten.FeedComment_Reply{},
33 }
3435 for name, rt := range AllLexTypes() {
···29 return nil, fmt.Errorf("failed to open db: %w", err)
30 }
31 _, err = db.Exec(`
32- pragma journal_mode = WAL;
33- pragma synchronous = normal;
34- pragma foreign_keys = on;
35- pragma temp_store = memory;
36- pragma mmap_size = 30000000000;
37- pragma page_size = 32768;
38- pragma auto_vacuum = incremental;
39- pragma busy_timeout = 5000;
4041- create table if not exists oauth_requests (
42- id integer primary key autoincrement,
43- auth_server_iss text not null,
44- state text not null,
45- did text not null,
46- handle text not null,
47- pds_url text not null,
48- pkce_verifier text not null,
49- dpop_auth_server_nonce text not null,
50- dpop_private_jwk text not null
51- );
5253- create table if not exists oauth_sessions (
54- id integer primary key autoincrement,
55- did text not null,
56- handle text not null,
57- pds_url text not null,
58- auth_server_iss text not null,
59- access_jwt text not null,
60- refresh_jwt text not null,
61- dpop_pds_nonce text,
62- dpop_auth_server_nonce text not null,
63- dpop_private_jwk text not null,
64- expiry text not null
65- );
6667- create table if not exists profiles (
68- -- id
69- id integer primary key autoincrement,
70- did text not null,
7172- -- data
73- display_name text not null,
74- description text,
75- location text,
76- xp integer not null default 0, -- total accumulated xp
77- level integer not null default 0,
78- created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
7980- -- constraints
81- unique(did)
82- );
8384- create table if not exists profile_languages (
85- -- id
86- did text not null,
8788- -- data
89- language_code text not null,
9091- -- constraints
92- primary key (did, language_code),
93- check (length(language_code) = 2),
94- foreign key (did) references profiles(did) on delete cascade
95- );
9697- create table if not exists study_sessions (
98- -- id
99- did text not null,
100- rkey text not null,
101102- -- data
103- activity_id integer not null,
104- resource_id integer,
105- description text,
106- duration integer not null,
107- language_code text not null,
108- date text not null,
109- created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
110111- -- constraints
112- check (length(language_code) = 2),
113- foreign key (did) references profiles(did) on delete cascade,
114- foreign key (activity_id) references activities(id) on delete restrict,
115- foreign key (resource_id) references resources(id) on delete set null,
116- primary key (did, rkey)
117- );
118119- create table if not exists categories (
120- id integer primary key, -- Matches StudySessionCategory iota
121- name text not null unique
122- );
123124- create table if not exists activities (
125- id integer primary key autoincrement,
126127- did text,
128- rkey text,
129130- name text not null,
131- description text,
132- status integer not null default 0 check(status in (0, 1)),
133- created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
00000000000000000134135- foreign key (did) references profiles(did) on delete cascade,
136- unique (did, rkey)
137- );
138139- create table if not exists activity_categories (
140- activity_id integer not null,
141- category_id integer not null,
142143- foreign key (activity_id) references activities(id) on delete cascade,
144- foreign key (category_id) references categories(id) on delete cascade,
145- primary key (activity_id, category_id)
146- );
147148- create table if not exists follows (
149- user_did text not null,
150- subject_did text not null,
0151152- rkey text not null,
153- followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
00154155- primary key (user_did, subject_did),
156- check (user_did <> subject_did)
157- );
158159- create table if not exists xp_events (
160- id integer primary key autoincrement,
161162- did text not null,
163- session_rkey text not null,
164- xp_gained integer not null,
165- created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
166167- foreign key (did) references profiles (did),
168- foreign key (did, session_rkey) references study_sessions (did, rkey),
169- unique (did, session_rkey)
170- );
171172- create table if not exists study_session_reactions (
173- id integer primary key autoincrement,
174175- did text not null,
176- rkey text not null,
0177178- session_did text not null,
179- session_rkey text not null,
180- reaction_id integer not null,
181- created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
182183- foreign key (did) references profiles (did),
184- foreign key (session_did, session_rkey) references study_sessions (did, rkey),
185- unique (did, session_did, session_rkey, reaction_id)
186- );
187188- create table if not exists notifications (
189- id integer primary key autoincrement,
0190191- recipient_did text not null,
192- actor_did text not null,
193- subject_uri text not null,
194195- state text not null default 'unread' check(state in ('unread', 'read')),
196- type text not null check(type in ('follow', 'reaction')),
197198- created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
000000199200- foreign key (recipient_did) references profiles(did) on delete cascade,
201- foreign key (actor_did) references profiles(did) on delete cascade
202- );
203204- create table if not exists resources (
205- id integer primary key autoincrement,
206207- did text not null,
208- rkey text not null,
209210- title text not null,
211- type text not null,
212- author text not null,
213- link text,
214- description text not null,
215- status integer not null default 0 check(status in (0, 1)),
216- created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
217218- foreign key (did) references profiles (did) on delete cascade,
219- unique (did, rkey)
220- );
221222- create table if not exists _jetstream (
223- id integer primary key autoincrement,
224- last_time_us integer not null
225- );
226227- create table if not exists migrations (
228- id integer primary key autoincrement,
229- name text unique
230- );
231- `)
232 if err != nil {
233 return nil, fmt.Errorf("failed to execute db create statement: %w", err)
234 }
···29 return nil, fmt.Errorf("failed to open db: %w", err)
30 }
31 _, err = db.Exec(`
32+ pragma journal_mode = WAL;
33+ pragma synchronous = normal;
34+ pragma foreign_keys = on;
35+ pragma temp_store = memory;
36+ pragma mmap_size = 30000000000;
37+ pragma page_size = 32768;
38+ pragma auto_vacuum = incremental;
39+ pragma busy_timeout = 5000;
4041+ create table if not exists oauth_requests (
42+ id integer primary key autoincrement,
43+ auth_server_iss text not null,
44+ state text not null,
45+ did text not null,
46+ handle text not null,
47+ pds_url text not null,
48+ pkce_verifier text not null,
49+ dpop_auth_server_nonce text not null,
50+ dpop_private_jwk text not null
51+ );
5253+ create table if not exists oauth_sessions (
54+ id integer primary key autoincrement,
55+ did text not null,
56+ handle text not null,
57+ pds_url text not null,
58+ auth_server_iss text not null,
59+ access_jwt text not null,
60+ refresh_jwt text not null,
61+ dpop_pds_nonce text,
62+ dpop_auth_server_nonce text not null,
63+ dpop_private_jwk text not null,
64+ expiry text not null
65+ );
6667+ create table if not exists profiles (
68+ -- id
69+ id integer primary key autoincrement,
70+ did text not null,
7172+ -- data
73+ display_name text not null,
74+ description text,
75+ location text,
76+ xp integer not null default 0, -- total accumulated xp
77+ level integer not null default 0,
78+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
7980+ -- constraints
81+ unique(did)
82+ );
8384+ create table if not exists profile_languages (
85+ -- id
86+ did text not null,
8788+ -- data
89+ language_code text not null,
9091+ -- constraints
92+ primary key (did, language_code),
93+ check (length(language_code) = 2),
94+ foreign key (did) references profiles(did) on delete cascade
95+ );
9697+ create table if not exists study_sessions (
98+ -- id
99+ did text not null,
100+ rkey text not null,
101102+ -- data
103+ activity_id integer not null,
104+ resource_id integer,
105+ description text,
106+ duration integer not null,
107+ language_code text not null,
108+ date text not null,
109+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
110111+ -- constraints
112+ check (length(language_code) = 2),
113+ foreign key (did) references profiles(did) on delete cascade,
114+ foreign key (activity_id) references activities(id) on delete restrict,
115+ foreign key (resource_id) references resources(id) on delete set null,
116+ primary key (did, rkey)
117+ );
118119+ create table if not exists categories (
120+ id integer primary key, -- Matches StudySessionCategory iota
121+ name text not null unique
122+ );
123124+ create table if not exists activities (
125+ id integer primary key autoincrement,
126127+ did text,
128+ rkey text,
129130+ name text not null,
131+ description text,
132+ status integer not null default 0 check(status in (0, 1)),
133+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
134+135+ foreign key (did) references profiles(did) on delete cascade,
136+ unique (did, rkey)
137+ );
138+139+ create table if not exists activity_categories (
140+ activity_id integer not null,
141+ category_id integer not null,
142+143+ foreign key (activity_id) references activities(id) on delete cascade,
144+ foreign key (category_id) references categories(id) on delete cascade,
145+ primary key (activity_id, category_id)
146+ );
147+148+ create table if not exists follows (
149+ user_did text not null,
150+ subject_did text not null,
151152+ rkey text not null,
153+ followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
0154155+ primary key (user_did, subject_did),
156+ check (user_did <> subject_did)
157+ );
158159+ create table if not exists xp_events (
160+ id integer primary key autoincrement,
00161162+ did text not null,
163+ session_rkey text not null,
164+ xp_gained integer not null,
165+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
166167+ foreign key (did) references profiles (did),
168+ foreign key (did, session_rkey) references study_sessions (did, rkey),
169+ unique (did, session_rkey)
170+ );
171172+ create table if not exists study_session_reactions (
173+ id integer primary key autoincrement,
0174175+ did text not null,
176+ rkey text not null,
177178+ session_did text not null,
179+ session_rkey text not null,
180+ reaction_id integer not null,
181+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
182183+ foreign key (did) references profiles (did),
184+ foreign key (session_did, session_rkey) references study_sessions (did, rkey),
185+ unique (did, session_did, session_rkey, reaction_id)
186+ );
187188+ create table if not exists notifications (
189+ id integer primary key autoincrement,
190191+ recipient_did text not null,
192+ actor_did text not null,
193+ subject_uri text not null,
194195+ state text not null default 'unread' check(state in ('unread', 'read')),
196+ type text not null check(type in ('follow', 'reaction')),
00197198+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
000199200+ foreign key (recipient_did) references profiles(did) on delete cascade,
201+ foreign key (actor_did) references profiles(did) on delete cascade
202+ );
203204+ create table if not exists resources (
205+ id integer primary key autoincrement,
0206207+ did text not null,
208+ rkey text not null,
209210+ title text not null,
211+ type text not null,
212+ author text not null,
213+ link text,
214+ description text not null,
215+ status integer not null default 0 check(status in (0, 1)),
216+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
217218+ foreign key (did) references profiles (did) on delete cascade,
219+ unique (did, rkey)
220+ );
221222+ create table if not exists comments (
223+ id integer primary key autoincrement,
224225+ did text not null,
226+ rkey text not null,
227228+ study_session_uri text not null,
229+ parent_comment_uri text,
230+ body text not null,
231+ is_deleted boolean not null default false,
232+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
00233234+ foreign key (did) references profiles(did) on delete cascade
235+ unique (did, rkey)
236+ );
237238+ create table if not exists _jetstream (
239+ id integer primary key autoincrement,
240+ last_time_us integer not null
241+ );
242243+ create table if not exists migrations (
244+ id integer primary key autoincrement,
245+ name text unique
246+ );
247+ `)
248 if err != nil {
249 return nil, fmt.Errorf("failed to execute db create statement: %w", err)
250 }
+1
internal/server/views/new-study-session.templ
···500 stopAndLog() {
501 this.pause();
502 const form = this.$root;
0503504 let durationSeconds = form.querySelector('input[name="duration_seconds"]');
505 if (!durationSeconds) {
···500 stopAndLog() {
501 this.pause();
502 const form = this.$root;
503+ this.timerState = 'stopped';
504505 let durationSeconds = form.querySelector('input[name="duration_seconds"]');
506 if (!durationSeconds) {