+376
api/yoten/cbor_gen.go
+376
api/yoten/cbor_gen.go
···
648
648
649
649
return nil
650
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
+
}
651
1027
func (t *FeedReaction) MarshalCBOR(w io.Writer) error {
652
1028
if t == nil {
653
1029
_, err := w.Write(cbg.CborNull)
+35
api/yoten/feedcomment.go
+35
api/yoten/feedcomment.go
···
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
+2
cmd/gen.go
+62
internal/db/comment.go
+62
internal/db/comment.go
···
1
+
package db
2
+
3
+
import (
4
+
"fmt"
5
+
"time"
6
+
7
+
"github.com/bluesky-social/indigo/atproto/syntax"
8
+
"yoten.app/api/yoten"
9
+
"yoten.app/internal/types"
10
+
)
11
+
12
+
type CommentWithBskyProfile struct {
13
+
Comment
14
+
BskyProfile types.BskyProfile
15
+
}
16
+
17
+
type Comment struct {
18
+
ID int
19
+
Did string
20
+
Rkey string
21
+
StudySessionUri syntax.ATURI
22
+
ParentCommentUri *syntax.ATURI
23
+
Body string
24
+
IsDeleted bool
25
+
CreatedAt time.Time
26
+
}
27
+
28
+
func (c Comment) CommentAt() syntax.ATURI {
29
+
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", c.Did, yoten.FeedCommentNSID, c.Rkey))
30
+
}
31
+
32
+
func UpsertComment(e Execer, comment Comment) error {
33
+
_, err := e.Exec(`
34
+
insert into study_sessions (
35
+
id,
36
+
did,
37
+
rkey,
38
+
study_session_uri,
39
+
parent_comment_uri,
40
+
body,
41
+
is_deleted,
42
+
created_at
43
+
)
44
+
values ()
45
+
on conflict(did, rkey) do update set
46
+
is_deleted = excluded.is_deleted,
47
+
body = excluded.body`,
48
+
comment.ID,
49
+
comment.Did,
50
+
comment.Rkey,
51
+
comment.StudySessionUri.String(),
52
+
comment.ParentCommentUri.String(),
53
+
comment.Body,
54
+
comment.IsDeleted,
55
+
comment.CreatedAt.Format(time.RFC3339),
56
+
)
57
+
if err != nil {
58
+
return fmt.Errorf("failed to insert or update comment: %w", err)
59
+
}
60
+
61
+
return nil
62
+
}
+177
-161
internal/db/db.go
+177
-161
internal/db/db.go
···
29
29
return nil, fmt.Errorf("failed to open db: %w", err)
30
30
}
31
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;
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;
40
40
41
-
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
-
);
41
+
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
+
);
52
52
53
-
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
-
);
53
+
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
+
);
66
66
67
-
create table if not exists profiles (
68
-
-- id
69
-
id integer primary key autoincrement,
70
-
did text not null,
67
+
create table if not exists profiles (
68
+
-- id
69
+
id integer primary key autoincrement,
70
+
did text not null,
71
71
72
-
-- 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')),
72
+
-- 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')),
79
79
80
-
-- constraints
81
-
unique(did)
82
-
);
80
+
-- constraints
81
+
unique(did)
82
+
);
83
83
84
-
create table if not exists profile_languages (
85
-
-- id
86
-
did text not null,
84
+
create table if not exists profile_languages (
85
+
-- id
86
+
did text not null,
87
87
88
-
-- data
89
-
language_code text not null,
88
+
-- data
89
+
language_code text not null,
90
90
91
-
-- constraints
92
-
primary key (did, language_code),
93
-
check (length(language_code) = 2),
94
-
foreign key (did) references profiles(did) on delete cascade
95
-
);
91
+
-- constraints
92
+
primary key (did, language_code),
93
+
check (length(language_code) = 2),
94
+
foreign key (did) references profiles(did) on delete cascade
95
+
);
96
96
97
-
create table if not exists study_sessions (
98
-
-- id
99
-
did text not null,
100
-
rkey text not null,
97
+
create table if not exists study_sessions (
98
+
-- id
99
+
did text not null,
100
+
rkey text not null,
101
101
102
-
-- 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')),
102
+
-- 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')),
110
110
111
-
-- 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
-
);
111
+
-- 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
+
);
118
118
119
-
create table if not exists categories (
120
-
id integer primary key, -- Matches StudySessionCategory iota
121
-
name text not null unique
122
-
);
119
+
create table if not exists categories (
120
+
id integer primary key, -- Matches StudySessionCategory iota
121
+
name text not null unique
122
+
);
123
123
124
-
create table if not exists activities (
125
-
id integer primary key autoincrement,
124
+
create table if not exists activities (
125
+
id integer primary key autoincrement,
126
126
127
-
did text,
128
-
rkey text,
127
+
did text,
128
+
rkey text,
129
129
130
-
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')),
130
+
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,
134
151
135
-
foreign key (did) references profiles(did) on delete cascade,
136
-
unique (did, rkey)
137
-
);
152
+
rkey text not null,
153
+
followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
138
154
139
-
create table if not exists activity_categories (
140
-
activity_id integer not null,
141
-
category_id integer not null,
155
+
primary key (user_did, subject_did),
156
+
check (user_did <> subject_did)
157
+
);
142
158
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
-
);
159
+
create table if not exists xp_events (
160
+
id integer primary key autoincrement,
147
161
148
-
create table if not exists follows (
149
-
user_did text not null,
150
-
subject_did text not null,
162
+
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')),
151
166
152
-
rkey text not null,
153
-
followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
167
+
foreign key (did) references profiles (did),
168
+
foreign key (did, session_rkey) references study_sessions (did, rkey),
169
+
unique (did, session_rkey)
170
+
);
154
171
155
-
primary key (user_did, subject_did),
156
-
check (user_did <> subject_did)
157
-
);
172
+
create table if not exists study_session_reactions (
173
+
id integer primary key autoincrement,
158
174
159
-
create table if not exists xp_events (
160
-
id integer primary key autoincrement,
175
+
did text not null,
176
+
rkey text not null,
161
177
162
-
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')),
178
+
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')),
166
182
167
-
foreign key (did) references profiles (did),
168
-
foreign key (did, session_rkey) references study_sessions (did, rkey),
169
-
unique (did, session_rkey)
170
-
);
183
+
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
+
);
171
187
172
-
create table if not exists study_session_reactions (
173
-
id integer primary key autoincrement,
188
+
create table if not exists notifications (
189
+
id integer primary key autoincrement,
174
190
175
-
did text not null,
176
-
rkey text not null,
191
+
recipient_did text not null,
192
+
actor_did text not null,
193
+
subject_uri text not null,
177
194
178
-
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')),
195
+
state text not null default 'unread' check(state in ('unread', 'read')),
196
+
type text not null check(type in ('follow', 'reaction')),
182
197
183
-
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
-
);
198
+
created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
187
199
188
-
create table if not exists notifications (
189
-
id integer primary key autoincrement,
200
+
foreign key (recipient_did) references profiles(did) on delete cascade,
201
+
foreign key (actor_did) references profiles(did) on delete cascade
202
+
);
190
203
191
-
recipient_did text not null,
192
-
actor_did text not null,
193
-
subject_uri text not null,
204
+
create table if not exists resources (
205
+
id integer primary key autoincrement,
194
206
195
-
state text not null default 'unread' check(state in ('unread', 'read')),
196
-
type text not null check(type in ('follow', 'reaction')),
207
+
did text not null,
208
+
rkey text not null,
197
209
198
-
created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
210
+
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')),
199
217
200
-
foreign key (recipient_did) references profiles(did) on delete cascade,
201
-
foreign key (actor_did) references profiles(did) on delete cascade
202
-
);
218
+
foreign key (did) references profiles (did) on delete cascade,
219
+
unique (did, rkey)
220
+
);
203
221
204
-
create table if not exists resources (
205
-
id integer primary key autoincrement,
222
+
create table if not exists comments (
223
+
id integer primary key autoincrement,
206
224
207
-
did text not null,
208
-
rkey text not null,
225
+
did text not null,
226
+
rkey text not null,
209
227
210
-
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')),
228
+
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')),
217
233
218
-
foreign key (did) references profiles (did) on delete cascade,
219
-
unique (did, rkey)
220
-
);
234
+
foreign key (did) references profiles(did) on delete cascade
235
+
unique (did, rkey)
236
+
);
221
237
222
-
create table if not exists _jetstream (
223
-
id integer primary key autoincrement,
224
-
last_time_us integer not null
225
-
);
238
+
create table if not exists _jetstream (
239
+
id integer primary key autoincrement,
240
+
last_time_us integer not null
241
+
);
226
242
227
-
create table if not exists migrations (
228
-
id integer primary key autoincrement,
229
-
name text unique
230
-
);
231
-
`)
243
+
create table if not exists migrations (
244
+
id integer primary key autoincrement,
245
+
name text unique
246
+
);
247
+
`)
232
248
if err != nil {
233
249
return nil, fmt.Errorf("failed to execute db create statement: %w", err)
234
250
}
+1
internal/server/views/new-study-session.templ
+1
internal/server/views/new-study-session.templ
+50
lexicons/feed/comment.json
+50
lexicons/feed/comment.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.yoten.feed.comment",
4
+
"needsCbor": true,
5
+
"needsType": true,
6
+
"defs": {
7
+
"main": {
8
+
"type": "record",
9
+
"description": "A declaration of a Yōten comment.",
10
+
"key": "tid",
11
+
"record": {
12
+
"type": "object",
13
+
"required": ["subject", "body", "createdAt"],
14
+
"properties": {
15
+
"body": {
16
+
"type": "string",
17
+
"minLength": 1,
18
+
"maxLength": 256
19
+
},
20
+
"subject": {
21
+
"type": "string",
22
+
"format": "at-uri",
23
+
"description": "A reference to the study session being commented on."
24
+
},
25
+
"reply": {
26
+
"type": "object",
27
+
"description": "Indicates that this comment is a reply to another comment.",
28
+
"required": ["root", "parent"],
29
+
"properties": {
30
+
"root": {
31
+
"type": "string",
32
+
"format": "at-uri",
33
+
"description": "A reference to the original study session (the root of the conversation)."
34
+
},
35
+
"parent": {
36
+
"type": "string",
37
+
"format": "at-uri",
38
+
"description": "A reference to the specific comment being replied to."
39
+
}
40
+
}
41
+
},
42
+
"createdAt": {
43
+
"type": "string",
44
+
"format": "datetime"
45
+
}
46
+
}
47
+
}
48
+
}
49
+
}
50
+
}