···648648649649 return nil
650650}
651651+func (t *FeedComment) MarshalCBOR(w io.Writer) error {
652652+ if t == nil {
653653+ _, err := w.Write(cbg.CborNull)
654654+ return err
655655+ }
656656+657657+ cw := cbg.NewCborWriter(w)
658658+ fieldCount := 5
659659+660660+ if t.Reply == nil {
661661+ fieldCount--
662662+ }
663663+664664+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
665665+ return err
666666+ }
667667+668668+ // t.Body (string) (string)
669669+ if len("body") > 1000000 {
670670+ return xerrors.Errorf("Value in field \"body\" was too long")
671671+ }
672672+673673+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("body"))); err != nil {
674674+ return err
675675+ }
676676+ if _, err := cw.WriteString(string("body")); err != nil {
677677+ return err
678678+ }
679679+680680+ if len(t.Body) > 1000000 {
681681+ return xerrors.Errorf("Value in field t.Body was too long")
682682+ }
683683+684684+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Body))); err != nil {
685685+ return err
686686+ }
687687+ if _, err := cw.WriteString(string(t.Body)); err != nil {
688688+ return err
689689+ }
690690+691691+ // t.LexiconTypeID (string) (string)
692692+ if len("$type") > 1000000 {
693693+ return xerrors.Errorf("Value in field \"$type\" was too long")
694694+ }
695695+696696+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
697697+ return err
698698+ }
699699+ if _, err := cw.WriteString(string("$type")); err != nil {
700700+ return err
701701+ }
702702+703703+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("app.yoten.feed.comment"))); err != nil {
704704+ return err
705705+ }
706706+ if _, err := cw.WriteString(string("app.yoten.feed.comment")); err != nil {
707707+ return err
708708+ }
709709+710710+ // t.Reply (yoten.FeedComment_Reply) (struct)
711711+ if t.Reply != nil {
712712+713713+ if len("reply") > 1000000 {
714714+ return xerrors.Errorf("Value in field \"reply\" was too long")
715715+ }
716716+717717+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("reply"))); err != nil {
718718+ return err
719719+ }
720720+ if _, err := cw.WriteString(string("reply")); err != nil {
721721+ return err
722722+ }
723723+724724+ if err := t.Reply.MarshalCBOR(cw); err != nil {
725725+ return err
726726+ }
727727+ }
728728+729729+ // t.Subject (string) (string)
730730+ if len("subject") > 1000000 {
731731+ return xerrors.Errorf("Value in field \"subject\" was too long")
732732+ }
733733+734734+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
735735+ return err
736736+ }
737737+ if _, err := cw.WriteString(string("subject")); err != nil {
738738+ return err
739739+ }
740740+741741+ if len(t.Subject) > 1000000 {
742742+ return xerrors.Errorf("Value in field t.Subject was too long")
743743+ }
744744+745745+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil {
746746+ return err
747747+ }
748748+ if _, err := cw.WriteString(string(t.Subject)); err != nil {
749749+ return err
750750+ }
751751+752752+ // t.CreatedAt (string) (string)
753753+ if len("createdAt") > 1000000 {
754754+ return xerrors.Errorf("Value in field \"createdAt\" was too long")
755755+ }
756756+757757+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
758758+ return err
759759+ }
760760+ if _, err := cw.WriteString(string("createdAt")); err != nil {
761761+ return err
762762+ }
763763+764764+ if len(t.CreatedAt) > 1000000 {
765765+ return xerrors.Errorf("Value in field t.CreatedAt was too long")
766766+ }
767767+768768+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
769769+ return err
770770+ }
771771+ if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
772772+ return err
773773+ }
774774+ return nil
775775+}
776776+777777+func (t *FeedComment) UnmarshalCBOR(r io.Reader) (err error) {
778778+ *t = FeedComment{}
779779+780780+ cr := cbg.NewCborReader(r)
781781+782782+ maj, extra, err := cr.ReadHeader()
783783+ if err != nil {
784784+ return err
785785+ }
786786+ defer func() {
787787+ if err == io.EOF {
788788+ err = io.ErrUnexpectedEOF
789789+ }
790790+ }()
791791+792792+ if maj != cbg.MajMap {
793793+ return fmt.Errorf("cbor input should be of type map")
794794+ }
795795+796796+ if extra > cbg.MaxLength {
797797+ return fmt.Errorf("FeedComment: map struct too large (%d)", extra)
798798+ }
799799+800800+ n := extra
801801+802802+ nameBuf := make([]byte, 9)
803803+ for i := uint64(0); i < n; i++ {
804804+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
805805+ if err != nil {
806806+ return err
807807+ }
808808+809809+ if !ok {
810810+ // Field doesn't exist on this type, so ignore it
811811+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
812812+ return err
813813+ }
814814+ continue
815815+ }
816816+817817+ switch string(nameBuf[:nameLen]) {
818818+ // t.Body (string) (string)
819819+ case "body":
820820+821821+ {
822822+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
823823+ if err != nil {
824824+ return err
825825+ }
826826+827827+ t.Body = string(sval)
828828+ }
829829+ // t.LexiconTypeID (string) (string)
830830+ case "$type":
831831+832832+ {
833833+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
834834+ if err != nil {
835835+ return err
836836+ }
837837+838838+ t.LexiconTypeID = string(sval)
839839+ }
840840+ // t.Reply (yoten.FeedComment_Reply) (struct)
841841+ case "reply":
842842+843843+ {
844844+845845+ b, err := cr.ReadByte()
846846+ if err != nil {
847847+ return err
848848+ }
849849+ if b != cbg.CborNull[0] {
850850+ if err := cr.UnreadByte(); err != nil {
851851+ return err
852852+ }
853853+ t.Reply = new(FeedComment_Reply)
854854+ if err := t.Reply.UnmarshalCBOR(cr); err != nil {
855855+ return xerrors.Errorf("unmarshaling t.Reply pointer: %w", err)
856856+ }
857857+ }
858858+859859+ }
860860+ // t.Subject (string) (string)
861861+ case "subject":
862862+863863+ {
864864+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
865865+ if err != nil {
866866+ return err
867867+ }
868868+869869+ t.Subject = string(sval)
870870+ }
871871+ // t.CreatedAt (string) (string)
872872+ case "createdAt":
873873+874874+ {
875875+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
876876+ if err != nil {
877877+ return err
878878+ }
879879+880880+ t.CreatedAt = string(sval)
881881+ }
882882+883883+ default:
884884+ // Field doesn't exist on this type, so ignore it
885885+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
886886+ return err
887887+ }
888888+ }
889889+ }
890890+891891+ return nil
892892+}
893893+func (t *FeedComment_Reply) MarshalCBOR(w io.Writer) error {
894894+ if t == nil {
895895+ _, err := w.Write(cbg.CborNull)
896896+ return err
897897+ }
898898+899899+ cw := cbg.NewCborWriter(w)
900900+901901+ if _, err := cw.Write([]byte{162}); err != nil {
902902+ return err
903903+ }
904904+905905+ // t.Root (string) (string)
906906+ if len("root") > 1000000 {
907907+ return xerrors.Errorf("Value in field \"root\" was too long")
908908+ }
909909+910910+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("root"))); err != nil {
911911+ return err
912912+ }
913913+ if _, err := cw.WriteString(string("root")); err != nil {
914914+ return err
915915+ }
916916+917917+ if len(t.Root) > 1000000 {
918918+ return xerrors.Errorf("Value in field t.Root was too long")
919919+ }
920920+921921+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Root))); err != nil {
922922+ return err
923923+ }
924924+ if _, err := cw.WriteString(string(t.Root)); err != nil {
925925+ return err
926926+ }
927927+928928+ // t.Parent (string) (string)
929929+ if len("parent") > 1000000 {
930930+ return xerrors.Errorf("Value in field \"parent\" was too long")
931931+ }
932932+933933+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("parent"))); err != nil {
934934+ return err
935935+ }
936936+ if _, err := cw.WriteString(string("parent")); err != nil {
937937+ return err
938938+ }
939939+940940+ if len(t.Parent) > 1000000 {
941941+ return xerrors.Errorf("Value in field t.Parent was too long")
942942+ }
943943+944944+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Parent))); err != nil {
945945+ return err
946946+ }
947947+ if _, err := cw.WriteString(string(t.Parent)); err != nil {
948948+ return err
949949+ }
950950+ return nil
951951+}
952952+953953+func (t *FeedComment_Reply) UnmarshalCBOR(r io.Reader) (err error) {
954954+ *t = FeedComment_Reply{}
955955+956956+ cr := cbg.NewCborReader(r)
957957+958958+ maj, extra, err := cr.ReadHeader()
959959+ if err != nil {
960960+ return err
961961+ }
962962+ defer func() {
963963+ if err == io.EOF {
964964+ err = io.ErrUnexpectedEOF
965965+ }
966966+ }()
967967+968968+ if maj != cbg.MajMap {
969969+ return fmt.Errorf("cbor input should be of type map")
970970+ }
971971+972972+ if extra > cbg.MaxLength {
973973+ return fmt.Errorf("FeedComment_Reply: map struct too large (%d)", extra)
974974+ }
975975+976976+ n := extra
977977+978978+ nameBuf := make([]byte, 6)
979979+ for i := uint64(0); i < n; i++ {
980980+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
981981+ if err != nil {
982982+ return err
983983+ }
984984+985985+ if !ok {
986986+ // Field doesn't exist on this type, so ignore it
987987+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
988988+ return err
989989+ }
990990+ continue
991991+ }
992992+993993+ switch string(nameBuf[:nameLen]) {
994994+ // t.Root (string) (string)
995995+ case "root":
996996+997997+ {
998998+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
999999+ if err != nil {
10001000+ return err
10011001+ }
10021002+10031003+ t.Root = string(sval)
10041004+ }
10051005+ // t.Parent (string) (string)
10061006+ case "parent":
10071007+10081008+ {
10091009+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
10101010+ if err != nil {
10111011+ return err
10121012+ }
10131013+10141014+ t.Parent = string(sval)
10151015+ }
10161016+10171017+ default:
10181018+ // Field doesn't exist on this type, so ignore it
10191019+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
10201020+ return err
10211021+ }
10221022+ }
10231023+ }
10241024+10251025+ return nil
10261026+}
6511027func (t *FeedReaction) MarshalCBOR(w io.Writer) error {
6521028 if t == nil {
6531029 _, err := w.Write(cbg.CborNull)
+35
api/yoten/feedcomment.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+package yoten
44+55+// schema: app.yoten.feed.comment
66+77+import (
88+ "github.com/bluesky-social/indigo/lex/util"
99+)
1010+1111+const (
1212+ FeedCommentNSID = "app.yoten.feed.comment"
1313+)
1414+1515+func init() {
1616+ util.RegisterType("app.yoten.feed.comment", &FeedComment{})
1717+} //
1818+// RECORDTYPE: FeedComment
1919+type FeedComment struct {
2020+ LexiconTypeID string `json:"$type,const=app.yoten.feed.comment" cborgen:"$type,const=app.yoten.feed.comment"`
2121+ Body string `json:"body" cborgen:"body"`
2222+ CreatedAt string `json:"createdAt" cborgen:"createdAt"`
2323+ // reply: Indicates that this comment is a reply to another comment.
2424+ Reply *FeedComment_Reply `json:"reply,omitempty" cborgen:"reply,omitempty"`
2525+ // subject: A reference to the study session being commented on.
2626+ Subject string `json:"subject" cborgen:"subject"`
2727+}
2828+2929+// Indicates that this comment is a reply to another comment.
3030+type FeedComment_Reply struct {
3131+ // parent: A reference to the specific comment being replied to.
3232+ Parent string `json:"parent" cborgen:"parent"`
3333+ // root: A reference to the original study session (the root of the conversation).
3434+ Root string `json:"root" cborgen:"root"`
3535+}
+2
cmd/gen.go
···2828 yoten.ActivityDef{},
2929 yoten.GraphFollow{},
3030 yoten.FeedReaction{},
3131+ yoten.FeedComment{},
3232+ yoten.FeedComment_Reply{},
3133 }
32343335 for name, rt := range AllLexTypes() {
···2929 return nil, fmt.Errorf("failed to open db: %w", err)
3030 }
3131 _, err = db.Exec(`
3232- pragma journal_mode = WAL;
3333- pragma synchronous = normal;
3434- pragma foreign_keys = on;
3535- pragma temp_store = memory;
3636- pragma mmap_size = 30000000000;
3737- pragma page_size = 32768;
3838- pragma auto_vacuum = incremental;
3939- pragma busy_timeout = 5000;
3232+ pragma journal_mode = WAL;
3333+ pragma synchronous = normal;
3434+ pragma foreign_keys = on;
3535+ pragma temp_store = memory;
3636+ pragma mmap_size = 30000000000;
3737+ pragma page_size = 32768;
3838+ pragma auto_vacuum = incremental;
3939+ pragma busy_timeout = 5000;
40404141- create table if not exists oauth_requests (
4242- id integer primary key autoincrement,
4343- auth_server_iss text not null,
4444- state text not null,
4545- did text not null,
4646- handle text not null,
4747- pds_url text not null,
4848- pkce_verifier text not null,
4949- dpop_auth_server_nonce text not null,
5050- dpop_private_jwk text not null
5151- );
4141+ create table if not exists oauth_requests (
4242+ id integer primary key autoincrement,
4343+ auth_server_iss text not null,
4444+ state text not null,
4545+ did text not null,
4646+ handle text not null,
4747+ pds_url text not null,
4848+ pkce_verifier text not null,
4949+ dpop_auth_server_nonce text not null,
5050+ dpop_private_jwk text not null
5151+ );
52525353- create table if not exists oauth_sessions (
5454- id integer primary key autoincrement,
5555- did text not null,
5656- handle text not null,
5757- pds_url text not null,
5858- auth_server_iss text not null,
5959- access_jwt text not null,
6060- refresh_jwt text not null,
6161- dpop_pds_nonce text,
6262- dpop_auth_server_nonce text not null,
6363- dpop_private_jwk text not null,
6464- expiry text not null
6565- );
5353+ create table if not exists oauth_sessions (
5454+ id integer primary key autoincrement,
5555+ did text not null,
5656+ handle text not null,
5757+ pds_url text not null,
5858+ auth_server_iss text not null,
5959+ access_jwt text not null,
6060+ refresh_jwt text not null,
6161+ dpop_pds_nonce text,
6262+ dpop_auth_server_nonce text not null,
6363+ dpop_private_jwk text not null,
6464+ expiry text not null
6565+ );
66666767- create table if not exists profiles (
6868- -- id
6969- id integer primary key autoincrement,
7070- did text not null,
6767+ create table if not exists profiles (
6868+ -- id
6969+ id integer primary key autoincrement,
7070+ did text not null,
71717272- -- data
7373- display_name text not null,
7474- description text,
7575- location text,
7676- xp integer not null default 0, -- total accumulated xp
7777- level integer not null default 0,
7878- created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
7272+ -- data
7373+ display_name text not null,
7474+ description text,
7575+ location text,
7676+ xp integer not null default 0, -- total accumulated xp
7777+ level integer not null default 0,
7878+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
79798080- -- constraints
8181- unique(did)
8282- );
8080+ -- constraints
8181+ unique(did)
8282+ );
83838484- create table if not exists profile_languages (
8585- -- id
8686- did text not null,
8484+ create table if not exists profile_languages (
8585+ -- id
8686+ did text not null,
87878888- -- data
8989- language_code text not null,
8888+ -- data
8989+ language_code text not null,
90909191- -- constraints
9292- primary key (did, language_code),
9393- check (length(language_code) = 2),
9494- foreign key (did) references profiles(did) on delete cascade
9595- );
9191+ -- constraints
9292+ primary key (did, language_code),
9393+ check (length(language_code) = 2),
9494+ foreign key (did) references profiles(did) on delete cascade
9595+ );
96969797- create table if not exists study_sessions (
9898- -- id
9999- did text not null,
100100- rkey text not null,
9797+ create table if not exists study_sessions (
9898+ -- id
9999+ did text not null,
100100+ rkey text not null,
101101102102- -- data
103103- activity_id integer not null,
104104- resource_id integer,
105105- description text,
106106- duration integer not null,
107107- language_code text not null,
108108- date text not null,
109109- created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
102102+ -- data
103103+ activity_id integer not null,
104104+ resource_id integer,
105105+ description text,
106106+ duration integer not null,
107107+ language_code text not null,
108108+ date text not null,
109109+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
110110111111- -- constraints
112112- check (length(language_code) = 2),
113113- foreign key (did) references profiles(did) on delete cascade,
114114- foreign key (activity_id) references activities(id) on delete restrict,
115115- foreign key (resource_id) references resources(id) on delete set null,
116116- primary key (did, rkey)
117117- );
111111+ -- constraints
112112+ check (length(language_code) = 2),
113113+ foreign key (did) references profiles(did) on delete cascade,
114114+ foreign key (activity_id) references activities(id) on delete restrict,
115115+ foreign key (resource_id) references resources(id) on delete set null,
116116+ primary key (did, rkey)
117117+ );
118118119119- create table if not exists categories (
120120- id integer primary key, -- Matches StudySessionCategory iota
121121- name text not null unique
122122- );
119119+ create table if not exists categories (
120120+ id integer primary key, -- Matches StudySessionCategory iota
121121+ name text not null unique
122122+ );
123123124124- create table if not exists activities (
125125- id integer primary key autoincrement,
124124+ create table if not exists activities (
125125+ id integer primary key autoincrement,
126126127127- did text,
128128- rkey text,
127127+ did text,
128128+ rkey text,
129129130130- name text not null,
131131- description text,
132132- status integer not null default 0 check(status in (0, 1)),
133133- created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
130130+ name text not null,
131131+ description text,
132132+ status integer not null default 0 check(status in (0, 1)),
133133+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
134134+135135+ foreign key (did) references profiles(did) on delete cascade,
136136+ unique (did, rkey)
137137+ );
138138+139139+ create table if not exists activity_categories (
140140+ activity_id integer not null,
141141+ category_id integer not null,
142142+143143+ foreign key (activity_id) references activities(id) on delete cascade,
144144+ foreign key (category_id) references categories(id) on delete cascade,
145145+ primary key (activity_id, category_id)
146146+ );
147147+148148+ create table if not exists follows (
149149+ user_did text not null,
150150+ subject_did text not null,
134151135135- foreign key (did) references profiles(did) on delete cascade,
136136- unique (did, rkey)
137137- );
152152+ rkey text not null,
153153+ followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
138154139139- create table if not exists activity_categories (
140140- activity_id integer not null,
141141- category_id integer not null,
155155+ primary key (user_did, subject_did),
156156+ check (user_did <> subject_did)
157157+ );
142158143143- foreign key (activity_id) references activities(id) on delete cascade,
144144- foreign key (category_id) references categories(id) on delete cascade,
145145- primary key (activity_id, category_id)
146146- );
159159+ create table if not exists xp_events (
160160+ id integer primary key autoincrement,
147161148148- create table if not exists follows (
149149- user_did text not null,
150150- subject_did text not null,
162162+ did text not null,
163163+ session_rkey text not null,
164164+ xp_gained integer not null,
165165+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
151166152152- rkey text not null,
153153- followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
167167+ foreign key (did) references profiles (did),
168168+ foreign key (did, session_rkey) references study_sessions (did, rkey),
169169+ unique (did, session_rkey)
170170+ );
154171155155- primary key (user_did, subject_did),
156156- check (user_did <> subject_did)
157157- );
172172+ create table if not exists study_session_reactions (
173173+ id integer primary key autoincrement,
158174159159- create table if not exists xp_events (
160160- id integer primary key autoincrement,
175175+ did text not null,
176176+ rkey text not null,
161177162162- did text not null,
163163- session_rkey text not null,
164164- xp_gained integer not null,
165165- created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
178178+ session_did text not null,
179179+ session_rkey text not null,
180180+ reaction_id integer not null,
181181+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
166182167167- foreign key (did) references profiles (did),
168168- foreign key (did, session_rkey) references study_sessions (did, rkey),
169169- unique (did, session_rkey)
170170- );
183183+ foreign key (did) references profiles (did),
184184+ foreign key (session_did, session_rkey) references study_sessions (did, rkey),
185185+ unique (did, session_did, session_rkey, reaction_id)
186186+ );
171187172172- create table if not exists study_session_reactions (
173173- id integer primary key autoincrement,
188188+ create table if not exists notifications (
189189+ id integer primary key autoincrement,
174190175175- did text not null,
176176- rkey text not null,
191191+ recipient_did text not null,
192192+ actor_did text not null,
193193+ subject_uri text not null,
177194178178- session_did text not null,
179179- session_rkey text not null,
180180- reaction_id integer not null,
181181- created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
195195+ state text not null default 'unread' check(state in ('unread', 'read')),
196196+ type text not null check(type in ('follow', 'reaction')),
182197183183- foreign key (did) references profiles (did),
184184- foreign key (session_did, session_rkey) references study_sessions (did, rkey),
185185- unique (did, session_did, session_rkey, reaction_id)
186186- );
198198+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
187199188188- create table if not exists notifications (
189189- id integer primary key autoincrement,
200200+ foreign key (recipient_did) references profiles(did) on delete cascade,
201201+ foreign key (actor_did) references profiles(did) on delete cascade
202202+ );
190203191191- recipient_did text not null,
192192- actor_did text not null,
193193- subject_uri text not null,
204204+ create table if not exists resources (
205205+ id integer primary key autoincrement,
194206195195- state text not null default 'unread' check(state in ('unread', 'read')),
196196- type text not null check(type in ('follow', 'reaction')),
207207+ did text not null,
208208+ rkey text not null,
197209198198- created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
210210+ title text not null,
211211+ type text not null,
212212+ author text not null,
213213+ link text,
214214+ description text not null,
215215+ status integer not null default 0 check(status in (0, 1)),
216216+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
199217200200- foreign key (recipient_did) references profiles(did) on delete cascade,
201201- foreign key (actor_did) references profiles(did) on delete cascade
202202- );
218218+ foreign key (did) references profiles (did) on delete cascade,
219219+ unique (did, rkey)
220220+ );
203221204204- create table if not exists resources (
205205- id integer primary key autoincrement,
222222+ create table if not exists comments (
223223+ id integer primary key autoincrement,
206224207207- did text not null,
208208- rkey text not null,
225225+ did text not null,
226226+ rkey text not null,
209227210210- title text not null,
211211- type text not null,
212212- author text not null,
213213- link text,
214214- description text not null,
215215- status integer not null default 0 check(status in (0, 1)),
216216- created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
228228+ study_session_uri text not null,
229229+ parent_comment_uri text,
230230+ body text not null,
231231+ is_deleted boolean not null default false,
232232+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
217233218218- foreign key (did) references profiles (did) on delete cascade,
219219- unique (did, rkey)
220220- );
234234+ foreign key (did) references profiles(did) on delete cascade
235235+ unique (did, rkey)
236236+ );
221237222222- create table if not exists _jetstream (
223223- id integer primary key autoincrement,
224224- last_time_us integer not null
225225- );
238238+ create table if not exists _jetstream (
239239+ id integer primary key autoincrement,
240240+ last_time_us integer not null
241241+ );
226242227227- create table if not exists migrations (
228228- id integer primary key autoincrement,
229229- name text unique
230230- );
231231- `)
243243+ create table if not exists migrations (
244244+ id integer primary key autoincrement,
245245+ name text unique
246246+ );
247247+ `)
232248 if err != nil {
233249 return nil, fmt.Errorf("failed to execute db create statement: %w", err)
234250 }
+1
internal/server/views/new-study-session.templ
···500500 stopAndLog() {
501501 this.pause();
502502 const form = this.$root;
503503+ this.timerState = 'stopped';
503504504505 let durationSeconds = form.querySelector('input[name="duration_seconds"]');
505506 if (!durationSeconds) {
+50
lexicons/feed/comment.json
···11+{
22+ "lexicon": 1,
33+ "id": "app.yoten.feed.comment",
44+ "needsCbor": true,
55+ "needsType": true,
66+ "defs": {
77+ "main": {
88+ "type": "record",
99+ "description": "A declaration of a Yōten comment.",
1010+ "key": "tid",
1111+ "record": {
1212+ "type": "object",
1313+ "required": ["subject", "body", "createdAt"],
1414+ "properties": {
1515+ "body": {
1616+ "type": "string",
1717+ "minLength": 1,
1818+ "maxLength": 256
1919+ },
2020+ "subject": {
2121+ "type": "string",
2222+ "format": "at-uri",
2323+ "description": "A reference to the study session being commented on."
2424+ },
2525+ "reply": {
2626+ "type": "object",
2727+ "description": "Indicates that this comment is a reply to another comment.",
2828+ "required": ["root", "parent"],
2929+ "properties": {
3030+ "root": {
3131+ "type": "string",
3232+ "format": "at-uri",
3333+ "description": "A reference to the original study session (the root of the conversation)."
3434+ },
3535+ "parent": {
3636+ "type": "string",
3737+ "format": "at-uri",
3838+ "description": "A reference to the specific comment being replied to."
3939+ }
4040+ }
4141+ },
4242+ "createdAt": {
4343+ "type": "string",
4444+ "format": "datetime"
4545+ }
4646+ }
4747+ }
4848+ }
4949+ }
5050+}