forked from tangled.org/core
Monorepo for Tangled

lexicons: issue, issue state and comment

Custom lexicon for issues, issue state (open, closed) and issue comments.

The case with issue_at is a bit weird since we have a circular
dependency: the issue record requires the issue_id, and the issue entry
in the db requires the issue_at.

To resolve this we write to the db without the issue_at, fetch the
issue_id, create the issue record on the PDS, and then update the
issue_at (with SetIssueAt). It's not great, but whatever.

+980
api/tangled/cbor_gen.go
··· 858 858 859 859 return nil 860 860 } 861 + func (t *RepoIssue) MarshalCBOR(w io.Writer) error { 862 + if t == nil { 863 + _, err := w.Write(cbg.CborNull) 864 + return err 865 + } 866 + 867 + cw := cbg.NewCborWriter(w) 868 + fieldCount := 7 869 + 870 + if t.Body == nil { 871 + fieldCount-- 872 + } 873 + 874 + if t.CreatedAt == nil { 875 + fieldCount-- 876 + } 877 + 878 + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 879 + return err 880 + } 881 + 882 + // t.Body (string) (string) 883 + if t.Body != nil { 884 + 885 + if len("body") > 1000000 { 886 + return xerrors.Errorf("Value in field \"body\" was too long") 887 + } 888 + 889 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("body"))); err != nil { 890 + return err 891 + } 892 + if _, err := cw.WriteString(string("body")); err != nil { 893 + return err 894 + } 895 + 896 + if t.Body == nil { 897 + if _, err := cw.Write(cbg.CborNull); err != nil { 898 + return err 899 + } 900 + } else { 901 + if len(*t.Body) > 1000000 { 902 + return xerrors.Errorf("Value in field t.Body was too long") 903 + } 904 + 905 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Body))); err != nil { 906 + return err 907 + } 908 + if _, err := cw.WriteString(string(*t.Body)); err != nil { 909 + return err 910 + } 911 + } 912 + } 913 + 914 + // t.Repo (string) (string) 915 + if len("repo") > 1000000 { 916 + return xerrors.Errorf("Value in field \"repo\" was too long") 917 + } 918 + 919 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repo"))); err != nil { 920 + return err 921 + } 922 + if _, err := cw.WriteString(string("repo")); err != nil { 923 + return err 924 + } 925 + 926 + if len(t.Repo) > 1000000 { 927 + return xerrors.Errorf("Value in field t.Repo was too long") 928 + } 929 + 930 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Repo))); err != nil { 931 + return err 932 + } 933 + if _, err := cw.WriteString(string(t.Repo)); err != nil { 934 + return err 935 + } 936 + 937 + // t.LexiconTypeID (string) (string) 938 + if len("$type") > 1000000 { 939 + return xerrors.Errorf("Value in field \"$type\" was too long") 940 + } 941 + 942 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil { 943 + return err 944 + } 945 + if _, err := cw.WriteString(string("$type")); err != nil { 946 + return err 947 + } 948 + 949 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.repo.issue"))); err != nil { 950 + return err 951 + } 952 + if _, err := cw.WriteString(string("sh.tangled.repo.issue")); err != nil { 953 + return err 954 + } 955 + 956 + // t.Owner (string) (string) 957 + if len("owner") > 1000000 { 958 + return xerrors.Errorf("Value in field \"owner\" was too long") 959 + } 960 + 961 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("owner"))); err != nil { 962 + return err 963 + } 964 + if _, err := cw.WriteString(string("owner")); err != nil { 965 + return err 966 + } 967 + 968 + if len(t.Owner) > 1000000 { 969 + return xerrors.Errorf("Value in field t.Owner was too long") 970 + } 971 + 972 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Owner))); err != nil { 973 + return err 974 + } 975 + if _, err := cw.WriteString(string(t.Owner)); err != nil { 976 + return err 977 + } 978 + 979 + // t.Title (string) (string) 980 + if len("title") > 1000000 { 981 + return xerrors.Errorf("Value in field \"title\" was too long") 982 + } 983 + 984 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("title"))); err != nil { 985 + return err 986 + } 987 + if _, err := cw.WriteString(string("title")); err != nil { 988 + return err 989 + } 990 + 991 + if len(t.Title) > 1000000 { 992 + return xerrors.Errorf("Value in field t.Title was too long") 993 + } 994 + 995 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Title))); err != nil { 996 + return err 997 + } 998 + if _, err := cw.WriteString(string(t.Title)); err != nil { 999 + return err 1000 + } 1001 + 1002 + // t.IssueId (int64) (int64) 1003 + if len("issueId") > 1000000 { 1004 + return xerrors.Errorf("Value in field \"issueId\" was too long") 1005 + } 1006 + 1007 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("issueId"))); err != nil { 1008 + return err 1009 + } 1010 + if _, err := cw.WriteString(string("issueId")); err != nil { 1011 + return err 1012 + } 1013 + 1014 + if t.IssueId >= 0 { 1015 + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.IssueId)); err != nil { 1016 + return err 1017 + } 1018 + } else { 1019 + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.IssueId-1)); err != nil { 1020 + return err 1021 + } 1022 + } 1023 + 1024 + // t.CreatedAt (string) (string) 1025 + if t.CreatedAt != nil { 1026 + 1027 + if len("createdAt") > 1000000 { 1028 + return xerrors.Errorf("Value in field \"createdAt\" was too long") 1029 + } 1030 + 1031 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil { 1032 + return err 1033 + } 1034 + if _, err := cw.WriteString(string("createdAt")); err != nil { 1035 + return err 1036 + } 1037 + 1038 + if t.CreatedAt == nil { 1039 + if _, err := cw.Write(cbg.CborNull); err != nil { 1040 + return err 1041 + } 1042 + } else { 1043 + if len(*t.CreatedAt) > 1000000 { 1044 + return xerrors.Errorf("Value in field t.CreatedAt was too long") 1045 + } 1046 + 1047 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.CreatedAt))); err != nil { 1048 + return err 1049 + } 1050 + if _, err := cw.WriteString(string(*t.CreatedAt)); err != nil { 1051 + return err 1052 + } 1053 + } 1054 + } 1055 + return nil 1056 + } 1057 + 1058 + func (t *RepoIssue) UnmarshalCBOR(r io.Reader) (err error) { 1059 + *t = RepoIssue{} 1060 + 1061 + cr := cbg.NewCborReader(r) 1062 + 1063 + maj, extra, err := cr.ReadHeader() 1064 + if err != nil { 1065 + return err 1066 + } 1067 + defer func() { 1068 + if err == io.EOF { 1069 + err = io.ErrUnexpectedEOF 1070 + } 1071 + }() 1072 + 1073 + if maj != cbg.MajMap { 1074 + return fmt.Errorf("cbor input should be of type map") 1075 + } 1076 + 1077 + if extra > cbg.MaxLength { 1078 + return fmt.Errorf("RepoIssue: map struct too large (%d)", extra) 1079 + } 1080 + 1081 + n := extra 1082 + 1083 + nameBuf := make([]byte, 9) 1084 + for i := uint64(0); i < n; i++ { 1085 + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 1086 + if err != nil { 1087 + return err 1088 + } 1089 + 1090 + if !ok { 1091 + // Field doesn't exist on this type, so ignore it 1092 + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { 1093 + return err 1094 + } 1095 + continue 1096 + } 1097 + 1098 + switch string(nameBuf[:nameLen]) { 1099 + // t.Body (string) (string) 1100 + case "body": 1101 + 1102 + { 1103 + b, err := cr.ReadByte() 1104 + if err != nil { 1105 + return err 1106 + } 1107 + if b != cbg.CborNull[0] { 1108 + if err := cr.UnreadByte(); err != nil { 1109 + return err 1110 + } 1111 + 1112 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1113 + if err != nil { 1114 + return err 1115 + } 1116 + 1117 + t.Body = (*string)(&sval) 1118 + } 1119 + } 1120 + // t.Repo (string) (string) 1121 + case "repo": 1122 + 1123 + { 1124 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1125 + if err != nil { 1126 + return err 1127 + } 1128 + 1129 + t.Repo = string(sval) 1130 + } 1131 + // t.LexiconTypeID (string) (string) 1132 + case "$type": 1133 + 1134 + { 1135 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1136 + if err != nil { 1137 + return err 1138 + } 1139 + 1140 + t.LexiconTypeID = string(sval) 1141 + } 1142 + // t.Owner (string) (string) 1143 + case "owner": 1144 + 1145 + { 1146 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1147 + if err != nil { 1148 + return err 1149 + } 1150 + 1151 + t.Owner = string(sval) 1152 + } 1153 + // t.Title (string) (string) 1154 + case "title": 1155 + 1156 + { 1157 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1158 + if err != nil { 1159 + return err 1160 + } 1161 + 1162 + t.Title = string(sval) 1163 + } 1164 + // t.IssueId (int64) (int64) 1165 + case "issueId": 1166 + { 1167 + maj, extra, err := cr.ReadHeader() 1168 + if err != nil { 1169 + return err 1170 + } 1171 + var extraI int64 1172 + switch maj { 1173 + case cbg.MajUnsignedInt: 1174 + extraI = int64(extra) 1175 + if extraI < 0 { 1176 + return fmt.Errorf("int64 positive overflow") 1177 + } 1178 + case cbg.MajNegativeInt: 1179 + extraI = int64(extra) 1180 + if extraI < 0 { 1181 + return fmt.Errorf("int64 negative overflow") 1182 + } 1183 + extraI = -1 - extraI 1184 + default: 1185 + return fmt.Errorf("wrong type for int64 field: %d", maj) 1186 + } 1187 + 1188 + t.IssueId = int64(extraI) 1189 + } 1190 + // t.CreatedAt (string) (string) 1191 + case "createdAt": 1192 + 1193 + { 1194 + b, err := cr.ReadByte() 1195 + if err != nil { 1196 + return err 1197 + } 1198 + if b != cbg.CborNull[0] { 1199 + if err := cr.UnreadByte(); err != nil { 1200 + return err 1201 + } 1202 + 1203 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1204 + if err != nil { 1205 + return err 1206 + } 1207 + 1208 + t.CreatedAt = (*string)(&sval) 1209 + } 1210 + } 1211 + 1212 + default: 1213 + // Field doesn't exist on this type, so ignore it 1214 + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { 1215 + return err 1216 + } 1217 + } 1218 + } 1219 + 1220 + return nil 1221 + } 1222 + func (t *RepoIssueState) MarshalCBOR(w io.Writer) error { 1223 + if t == nil { 1224 + _, err := w.Write(cbg.CborNull) 1225 + return err 1226 + } 1227 + 1228 + cw := cbg.NewCborWriter(w) 1229 + fieldCount := 3 1230 + 1231 + if t.State == nil { 1232 + fieldCount-- 1233 + } 1234 + 1235 + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 1236 + return err 1237 + } 1238 + 1239 + // t.LexiconTypeID (string) (string) 1240 + if len("$type") > 1000000 { 1241 + return xerrors.Errorf("Value in field \"$type\" was too long") 1242 + } 1243 + 1244 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil { 1245 + return err 1246 + } 1247 + if _, err := cw.WriteString(string("$type")); err != nil { 1248 + return err 1249 + } 1250 + 1251 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.repo.issue.state"))); err != nil { 1252 + return err 1253 + } 1254 + if _, err := cw.WriteString(string("sh.tangled.repo.issue.state")); err != nil { 1255 + return err 1256 + } 1257 + 1258 + // t.Issue (string) (string) 1259 + if len("issue") > 1000000 { 1260 + return xerrors.Errorf("Value in field \"issue\" was too long") 1261 + } 1262 + 1263 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("issue"))); err != nil { 1264 + return err 1265 + } 1266 + if _, err := cw.WriteString(string("issue")); err != nil { 1267 + return err 1268 + } 1269 + 1270 + if len(t.Issue) > 1000000 { 1271 + return xerrors.Errorf("Value in field t.Issue was too long") 1272 + } 1273 + 1274 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Issue))); err != nil { 1275 + return err 1276 + } 1277 + if _, err := cw.WriteString(string(t.Issue)); err != nil { 1278 + return err 1279 + } 1280 + 1281 + // t.State (string) (string) 1282 + if t.State != nil { 1283 + 1284 + if len("state") > 1000000 { 1285 + return xerrors.Errorf("Value in field \"state\" was too long") 1286 + } 1287 + 1288 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("state"))); err != nil { 1289 + return err 1290 + } 1291 + if _, err := cw.WriteString(string("state")); err != nil { 1292 + return err 1293 + } 1294 + 1295 + if t.State == nil { 1296 + if _, err := cw.Write(cbg.CborNull); err != nil { 1297 + return err 1298 + } 1299 + } else { 1300 + if len(*t.State) > 1000000 { 1301 + return xerrors.Errorf("Value in field t.State was too long") 1302 + } 1303 + 1304 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.State))); err != nil { 1305 + return err 1306 + } 1307 + if _, err := cw.WriteString(string(*t.State)); err != nil { 1308 + return err 1309 + } 1310 + } 1311 + } 1312 + return nil 1313 + } 1314 + 1315 + func (t *RepoIssueState) UnmarshalCBOR(r io.Reader) (err error) { 1316 + *t = RepoIssueState{} 1317 + 1318 + cr := cbg.NewCborReader(r) 1319 + 1320 + maj, extra, err := cr.ReadHeader() 1321 + if err != nil { 1322 + return err 1323 + } 1324 + defer func() { 1325 + if err == io.EOF { 1326 + err = io.ErrUnexpectedEOF 1327 + } 1328 + }() 1329 + 1330 + if maj != cbg.MajMap { 1331 + return fmt.Errorf("cbor input should be of type map") 1332 + } 1333 + 1334 + if extra > cbg.MaxLength { 1335 + return fmt.Errorf("RepoIssueState: map struct too large (%d)", extra) 1336 + } 1337 + 1338 + n := extra 1339 + 1340 + nameBuf := make([]byte, 5) 1341 + for i := uint64(0); i < n; i++ { 1342 + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 1343 + if err != nil { 1344 + return err 1345 + } 1346 + 1347 + if !ok { 1348 + // Field doesn't exist on this type, so ignore it 1349 + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { 1350 + return err 1351 + } 1352 + continue 1353 + } 1354 + 1355 + switch string(nameBuf[:nameLen]) { 1356 + // t.LexiconTypeID (string) (string) 1357 + case "$type": 1358 + 1359 + { 1360 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1361 + if err != nil { 1362 + return err 1363 + } 1364 + 1365 + t.LexiconTypeID = string(sval) 1366 + } 1367 + // t.Issue (string) (string) 1368 + case "issue": 1369 + 1370 + { 1371 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1372 + if err != nil { 1373 + return err 1374 + } 1375 + 1376 + t.Issue = string(sval) 1377 + } 1378 + // t.State (string) (string) 1379 + case "state": 1380 + 1381 + { 1382 + b, err := cr.ReadByte() 1383 + if err != nil { 1384 + return err 1385 + } 1386 + if b != cbg.CborNull[0] { 1387 + if err := cr.UnreadByte(); err != nil { 1388 + return err 1389 + } 1390 + 1391 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1392 + if err != nil { 1393 + return err 1394 + } 1395 + 1396 + t.State = (*string)(&sval) 1397 + } 1398 + } 1399 + 1400 + default: 1401 + // Field doesn't exist on this type, so ignore it 1402 + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { 1403 + return err 1404 + } 1405 + } 1406 + } 1407 + 1408 + return nil 1409 + } 1410 + func (t *RepoIssueComment) MarshalCBOR(w io.Writer) error { 1411 + if t == nil { 1412 + _, err := w.Write(cbg.CborNull) 1413 + return err 1414 + } 1415 + 1416 + cw := cbg.NewCborWriter(w) 1417 + fieldCount := 7 1418 + 1419 + if t.Body == nil { 1420 + fieldCount-- 1421 + } 1422 + 1423 + if t.CommentId == nil { 1424 + fieldCount-- 1425 + } 1426 + 1427 + if t.CreatedAt == nil { 1428 + fieldCount-- 1429 + } 1430 + 1431 + if t.Owner == nil { 1432 + fieldCount-- 1433 + } 1434 + 1435 + if t.Repo == nil { 1436 + fieldCount-- 1437 + } 1438 + 1439 + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 1440 + return err 1441 + } 1442 + 1443 + // t.Body (string) (string) 1444 + if t.Body != nil { 1445 + 1446 + if len("body") > 1000000 { 1447 + return xerrors.Errorf("Value in field \"body\" was too long") 1448 + } 1449 + 1450 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("body"))); err != nil { 1451 + return err 1452 + } 1453 + if _, err := cw.WriteString(string("body")); err != nil { 1454 + return err 1455 + } 1456 + 1457 + if t.Body == nil { 1458 + if _, err := cw.Write(cbg.CborNull); err != nil { 1459 + return err 1460 + } 1461 + } else { 1462 + if len(*t.Body) > 1000000 { 1463 + return xerrors.Errorf("Value in field t.Body was too long") 1464 + } 1465 + 1466 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Body))); err != nil { 1467 + return err 1468 + } 1469 + if _, err := cw.WriteString(string(*t.Body)); err != nil { 1470 + return err 1471 + } 1472 + } 1473 + } 1474 + 1475 + // t.Repo (string) (string) 1476 + if t.Repo != nil { 1477 + 1478 + if len("repo") > 1000000 { 1479 + return xerrors.Errorf("Value in field \"repo\" was too long") 1480 + } 1481 + 1482 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repo"))); err != nil { 1483 + return err 1484 + } 1485 + if _, err := cw.WriteString(string("repo")); err != nil { 1486 + return err 1487 + } 1488 + 1489 + if t.Repo == nil { 1490 + if _, err := cw.Write(cbg.CborNull); err != nil { 1491 + return err 1492 + } 1493 + } else { 1494 + if len(*t.Repo) > 1000000 { 1495 + return xerrors.Errorf("Value in field t.Repo was too long") 1496 + } 1497 + 1498 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Repo))); err != nil { 1499 + return err 1500 + } 1501 + if _, err := cw.WriteString(string(*t.Repo)); err != nil { 1502 + return err 1503 + } 1504 + } 1505 + } 1506 + 1507 + // t.LexiconTypeID (string) (string) 1508 + if len("$type") > 1000000 { 1509 + return xerrors.Errorf("Value in field \"$type\" was too long") 1510 + } 1511 + 1512 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil { 1513 + return err 1514 + } 1515 + if _, err := cw.WriteString(string("$type")); err != nil { 1516 + return err 1517 + } 1518 + 1519 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.repo.issue.comment"))); err != nil { 1520 + return err 1521 + } 1522 + if _, err := cw.WriteString(string("sh.tangled.repo.issue.comment")); err != nil { 1523 + return err 1524 + } 1525 + 1526 + // t.Issue (string) (string) 1527 + if len("issue") > 1000000 { 1528 + return xerrors.Errorf("Value in field \"issue\" was too long") 1529 + } 1530 + 1531 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("issue"))); err != nil { 1532 + return err 1533 + } 1534 + if _, err := cw.WriteString(string("issue")); err != nil { 1535 + return err 1536 + } 1537 + 1538 + if len(t.Issue) > 1000000 { 1539 + return xerrors.Errorf("Value in field t.Issue was too long") 1540 + } 1541 + 1542 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Issue))); err != nil { 1543 + return err 1544 + } 1545 + if _, err := cw.WriteString(string(t.Issue)); err != nil { 1546 + return err 1547 + } 1548 + 1549 + // t.Owner (string) (string) 1550 + if t.Owner != nil { 1551 + 1552 + if len("owner") > 1000000 { 1553 + return xerrors.Errorf("Value in field \"owner\" was too long") 1554 + } 1555 + 1556 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("owner"))); err != nil { 1557 + return err 1558 + } 1559 + if _, err := cw.WriteString(string("owner")); err != nil { 1560 + return err 1561 + } 1562 + 1563 + if t.Owner == nil { 1564 + if _, err := cw.Write(cbg.CborNull); err != nil { 1565 + return err 1566 + } 1567 + } else { 1568 + if len(*t.Owner) > 1000000 { 1569 + return xerrors.Errorf("Value in field t.Owner was too long") 1570 + } 1571 + 1572 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Owner))); err != nil { 1573 + return err 1574 + } 1575 + if _, err := cw.WriteString(string(*t.Owner)); err != nil { 1576 + return err 1577 + } 1578 + } 1579 + } 1580 + 1581 + // t.CommentId (int64) (int64) 1582 + if t.CommentId != nil { 1583 + 1584 + if len("commentId") > 1000000 { 1585 + return xerrors.Errorf("Value in field \"commentId\" was too long") 1586 + } 1587 + 1588 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("commentId"))); err != nil { 1589 + return err 1590 + } 1591 + if _, err := cw.WriteString(string("commentId")); err != nil { 1592 + return err 1593 + } 1594 + 1595 + if t.CommentId == nil { 1596 + if _, err := cw.Write(cbg.CborNull); err != nil { 1597 + return err 1598 + } 1599 + } else { 1600 + if *t.CommentId >= 0 { 1601 + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.CommentId)); err != nil { 1602 + return err 1603 + } 1604 + } else { 1605 + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-*t.CommentId-1)); err != nil { 1606 + return err 1607 + } 1608 + } 1609 + } 1610 + 1611 + } 1612 + 1613 + // t.CreatedAt (string) (string) 1614 + if t.CreatedAt != nil { 1615 + 1616 + if len("createdAt") > 1000000 { 1617 + return xerrors.Errorf("Value in field \"createdAt\" was too long") 1618 + } 1619 + 1620 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil { 1621 + return err 1622 + } 1623 + if _, err := cw.WriteString(string("createdAt")); err != nil { 1624 + return err 1625 + } 1626 + 1627 + if t.CreatedAt == nil { 1628 + if _, err := cw.Write(cbg.CborNull); err != nil { 1629 + return err 1630 + } 1631 + } else { 1632 + if len(*t.CreatedAt) > 1000000 { 1633 + return xerrors.Errorf("Value in field t.CreatedAt was too long") 1634 + } 1635 + 1636 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.CreatedAt))); err != nil { 1637 + return err 1638 + } 1639 + if _, err := cw.WriteString(string(*t.CreatedAt)); err != nil { 1640 + return err 1641 + } 1642 + } 1643 + } 1644 + return nil 1645 + } 1646 + 1647 + func (t *RepoIssueComment) UnmarshalCBOR(r io.Reader) (err error) { 1648 + *t = RepoIssueComment{} 1649 + 1650 + cr := cbg.NewCborReader(r) 1651 + 1652 + maj, extra, err := cr.ReadHeader() 1653 + if err != nil { 1654 + return err 1655 + } 1656 + defer func() { 1657 + if err == io.EOF { 1658 + err = io.ErrUnexpectedEOF 1659 + } 1660 + }() 1661 + 1662 + if maj != cbg.MajMap { 1663 + return fmt.Errorf("cbor input should be of type map") 1664 + } 1665 + 1666 + if extra > cbg.MaxLength { 1667 + return fmt.Errorf("RepoIssueComment: map struct too large (%d)", extra) 1668 + } 1669 + 1670 + n := extra 1671 + 1672 + nameBuf := make([]byte, 9) 1673 + for i := uint64(0); i < n; i++ { 1674 + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 1675 + if err != nil { 1676 + return err 1677 + } 1678 + 1679 + if !ok { 1680 + // Field doesn't exist on this type, so ignore it 1681 + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { 1682 + return err 1683 + } 1684 + continue 1685 + } 1686 + 1687 + switch string(nameBuf[:nameLen]) { 1688 + // t.Body (string) (string) 1689 + case "body": 1690 + 1691 + { 1692 + b, err := cr.ReadByte() 1693 + if err != nil { 1694 + return err 1695 + } 1696 + if b != cbg.CborNull[0] { 1697 + if err := cr.UnreadByte(); err != nil { 1698 + return err 1699 + } 1700 + 1701 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1702 + if err != nil { 1703 + return err 1704 + } 1705 + 1706 + t.Body = (*string)(&sval) 1707 + } 1708 + } 1709 + // t.Repo (string) (string) 1710 + case "repo": 1711 + 1712 + { 1713 + b, err := cr.ReadByte() 1714 + if err != nil { 1715 + return err 1716 + } 1717 + if b != cbg.CborNull[0] { 1718 + if err := cr.UnreadByte(); err != nil { 1719 + return err 1720 + } 1721 + 1722 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1723 + if err != nil { 1724 + return err 1725 + } 1726 + 1727 + t.Repo = (*string)(&sval) 1728 + } 1729 + } 1730 + // t.LexiconTypeID (string) (string) 1731 + case "$type": 1732 + 1733 + { 1734 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1735 + if err != nil { 1736 + return err 1737 + } 1738 + 1739 + t.LexiconTypeID = string(sval) 1740 + } 1741 + // t.Issue (string) (string) 1742 + case "issue": 1743 + 1744 + { 1745 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1746 + if err != nil { 1747 + return err 1748 + } 1749 + 1750 + t.Issue = string(sval) 1751 + } 1752 + // t.Owner (string) (string) 1753 + case "owner": 1754 + 1755 + { 1756 + b, err := cr.ReadByte() 1757 + if err != nil { 1758 + return err 1759 + } 1760 + if b != cbg.CborNull[0] { 1761 + if err := cr.UnreadByte(); err != nil { 1762 + return err 1763 + } 1764 + 1765 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1766 + if err != nil { 1767 + return err 1768 + } 1769 + 1770 + t.Owner = (*string)(&sval) 1771 + } 1772 + } 1773 + // t.CommentId (int64) (int64) 1774 + case "commentId": 1775 + { 1776 + 1777 + b, err := cr.ReadByte() 1778 + if err != nil { 1779 + return err 1780 + } 1781 + if b != cbg.CborNull[0] { 1782 + if err := cr.UnreadByte(); err != nil { 1783 + return err 1784 + } 1785 + maj, extra, err := cr.ReadHeader() 1786 + if err != nil { 1787 + return err 1788 + } 1789 + var extraI int64 1790 + switch maj { 1791 + case cbg.MajUnsignedInt: 1792 + extraI = int64(extra) 1793 + if extraI < 0 { 1794 + return fmt.Errorf("int64 positive overflow") 1795 + } 1796 + case cbg.MajNegativeInt: 1797 + extraI = int64(extra) 1798 + if extraI < 0 { 1799 + return fmt.Errorf("int64 negative overflow") 1800 + } 1801 + extraI = -1 - extraI 1802 + default: 1803 + return fmt.Errorf("wrong type for int64 field: %d", maj) 1804 + } 1805 + 1806 + t.CommentId = (*int64)(&extraI) 1807 + } 1808 + } 1809 + // t.CreatedAt (string) (string) 1810 + case "createdAt": 1811 + 1812 + { 1813 + b, err := cr.ReadByte() 1814 + if err != nil { 1815 + return err 1816 + } 1817 + if b != cbg.CborNull[0] { 1818 + if err := cr.UnreadByte(); err != nil { 1819 + return err 1820 + } 1821 + 1822 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1823 + if err != nil { 1824 + return err 1825 + } 1826 + 1827 + t.CreatedAt = (*string)(&sval) 1828 + } 1829 + } 1830 + 1831 + default: 1832 + // Field doesn't exist on this type, so ignore it 1833 + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { 1834 + return err 1835 + } 1836 + } 1837 + } 1838 + 1839 + return nil 1840 + }
+27
api/tangled/issuecomment.go
··· 1 + // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 + 3 + package tangled 4 + 5 + // schema: sh.tangled.repo.issue.comment 6 + 7 + import ( 8 + "github.com/bluesky-social/indigo/lex/util" 9 + ) 10 + 11 + const ( 12 + RepoIssueCommentNSID = "sh.tangled.repo.issue.comment" 13 + ) 14 + 15 + func init() { 16 + util.RegisterType("sh.tangled.repo.issue.comment", &RepoIssueComment{}) 17 + } // 18 + // RECORDTYPE: RepoIssueComment 19 + type RepoIssueComment struct { 20 + LexiconTypeID string `json:"$type,const=sh.tangled.repo.issue.comment" cborgen:"$type,const=sh.tangled.repo.issue.comment"` 21 + Body *string `json:"body,omitempty" cborgen:"body,omitempty"` 22 + CommentId *int64 `json:"commentId,omitempty" cborgen:"commentId,omitempty"` 23 + CreatedAt *string `json:"createdAt,omitempty" cborgen:"createdAt,omitempty"` 24 + Issue string `json:"issue" cborgen:"issue"` 25 + Owner *string `json:"owner,omitempty" cborgen:"owner,omitempty"` 26 + Repo *string `json:"repo,omitempty" cborgen:"repo,omitempty"` 27 + }
+24
api/tangled/issuestate.go
··· 1 + // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 + 3 + package tangled 4 + 5 + // schema: sh.tangled.repo.issue.state 6 + 7 + import ( 8 + "github.com/bluesky-social/indigo/lex/util" 9 + ) 10 + 11 + const ( 12 + RepoIssueStateNSID = "sh.tangled.repo.issue.state" 13 + ) 14 + 15 + func init() { 16 + util.RegisterType("sh.tangled.repo.issue.state", &RepoIssueState{}) 17 + } // 18 + // RECORDTYPE: RepoIssueState 19 + type RepoIssueState struct { 20 + LexiconTypeID string `json:"$type,const=sh.tangled.repo.issue.state" cborgen:"$type,const=sh.tangled.repo.issue.state"` 21 + Issue string `json:"issue" cborgen:"issue"` 22 + // state: state of the issue 23 + State *string `json:"state,omitempty" cborgen:"state,omitempty"` 24 + }
+27
api/tangled/repoissue.go
··· 1 + // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 + 3 + package tangled 4 + 5 + // schema: sh.tangled.repo.issue 6 + 7 + import ( 8 + "github.com/bluesky-social/indigo/lex/util" 9 + ) 10 + 11 + const ( 12 + RepoIssueNSID = "sh.tangled.repo.issue" 13 + ) 14 + 15 + func init() { 16 + util.RegisterType("sh.tangled.repo.issue", &RepoIssue{}) 17 + } // 18 + // RECORDTYPE: RepoIssue 19 + type RepoIssue struct { 20 + LexiconTypeID string `json:"$type,const=sh.tangled.repo.issue" cborgen:"$type,const=sh.tangled.repo.issue"` 21 + Body *string `json:"body,omitempty" cborgen:"body,omitempty"` 22 + CreatedAt *string `json:"createdAt,omitempty" cborgen:"createdAt,omitempty"` 23 + IssueId int64 `json:"issueId" cborgen:"issueId"` 24 + Owner string `json:"owner" cborgen:"owner"` 25 + Repo string `json:"repo" cborgen:"repo"` 26 + Title string `json:"title" cborgen:"title"` 27 + }
+9
api/tangled/stateclosed.go
··· 1 + // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 + 3 + package tangled 4 + 5 + // schema: sh.tangled.repo.issue.state.closed 6 + 7 + const () 8 + 9 + const RepoIssueStateClosed = "sh.tangled.repo.issue.state.closed"
+9
api/tangled/stateopen.go
··· 1 + // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 + 3 + package tangled 4 + 5 + // schema: sh.tangled.repo.issue.state.open 6 + 7 + const () 8 + 9 + const RepoIssueStateOpen = "sh.tangled.repo.issue.state.open"
+3
appview/db/db.go
··· 73 73 issue_id integer not null unique, 74 74 title text not null, 75 75 body text not null, 76 + open integer not null default 1, 76 77 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 78 + issue_at text, 77 79 unique(repo_at, issue_id), 78 80 foreign key (repo_at) references repos(at_uri) on delete cascade 79 81 ); ··· 83 85 issue_id integer not null, 84 86 repo_at text not null, 85 87 comment_id integer not null, 88 + comment_at text not null, 86 89 body text not null, 87 90 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 88 91 unique(issue_id, comment_id),
+63 -11
appview/db/issues.go
··· 1 1 package db 2 2 3 - import "time" 3 + import ( 4 + "database/sql" 5 + "time" 6 + ) 4 7 5 8 type Issue struct { 6 9 RepoAt string 7 10 OwnerDid string 8 11 IssueId int 12 + IssueAt string 9 13 Created *time.Time 10 14 Title string 11 15 Body string ··· 15 19 type Comment struct { 16 20 OwnerDid string 17 21 RepoAt string 22 + CommentAt string 18 23 Issue int 19 24 CommentId int 20 25 Body string 21 26 Created *time.Time 22 27 } 23 28 24 - func (d *DB) NewIssue(issue *Issue) (int, error) { 29 + func (d *DB) NewIssue(issue *Issue) error { 25 30 tx, err := d.db.Begin() 26 31 if err != nil { 27 - return 0, err 32 + return err 28 33 } 29 34 defer tx.Rollback() 30 35 ··· 33 38 values (?, 1) 34 39 `, issue.RepoAt) 35 40 if err != nil { 36 - return 0, err 41 + return err 37 42 } 38 43 39 44 var nextId int ··· 44 49 returning next_issue_id - 1 45 50 `, issue.RepoAt).Scan(&nextId) 46 51 if err != nil { 47 - return 0, err 52 + return err 48 53 } 49 54 50 55 issue.IssueId = nextId ··· 54 59 values (?, ?, ?, ?, ?) 55 60 `, issue.RepoAt, issue.OwnerDid, issue.IssueId, issue.Title, issue.Body) 56 61 if err != nil { 57 - return 0, err 62 + return err 58 63 } 59 64 60 65 if err := tx.Commit(); err != nil { 61 - return 0, err 66 + return err 62 67 } 63 68 64 - return nextId, nil 69 + return nil 70 + } 71 + 72 + func (d *DB) SetIssueAt(repoAt string, issueId int, issueAt string) error { 73 + _, err := d.db.Exec(`update issues set issue_at = ? where repo_at = ? and issue_id = ?`, issueAt, repoAt, issueId) 74 + return err 75 + } 76 + 77 + func (d *DB) GetIssueAt(repoAt string, issueId int) (string, error) { 78 + var issueAt string 79 + err := d.db.QueryRow(`select issue_at from issues where repo_at = ? and issue_id = ?`, repoAt, issueId).Scan(&issueAt) 80 + return issueAt, err 81 + } 82 + 83 + func (d *DB) GetIssueId(repoAt string) (int, error) { 84 + var issueId int 85 + err := d.db.QueryRow(`select next_issue_id from repo_issue_seqs where repo_at = ?`, repoAt).Scan(&issueId) 86 + return issueId - 1, err 87 + } 88 + 89 + func (d *DB) GetIssueOwnerDid(repoAt string, issueId int) (string, error) { 90 + var ownerDid string 91 + err := d.db.QueryRow(`select owner_did from issues where repo_at = ? and issue_id = ?`, repoAt, issueId).Scan(&ownerDid) 92 + return ownerDid, err 65 93 } 66 94 67 95 func (d *DB) GetIssues(repoAt string) ([]Issue, error) { ··· 97 125 return issues, nil 98 126 } 99 127 128 + func (d *DB) GetIssue(repoAt string, issueId int) (*Issue, error) { 129 + query := `select owner_did, created, title, body, open from issues where repo_at = ? and issue_id = ?` 130 + row := d.db.QueryRow(query, repoAt, issueId) 131 + 132 + var issue Issue 133 + var createdAt string 134 + err := row.Scan(&issue.OwnerDid, &createdAt, &issue.Title, &issue.Body, &issue.Open) 135 + if err != nil { 136 + return nil, err 137 + } 138 + 139 + createdTime, err := time.Parse(time.RFC3339, createdAt) 140 + if err != nil { 141 + return nil, err 142 + } 143 + issue.Created = &createdTime 144 + 145 + return &issue, nil 146 + } 147 + 100 148 func (d *DB) GetIssueWithComments(repoAt string, issueId int) (*Issue, []Comment, error) { 101 149 query := `select owner_did, issue_id, created, title, body, open from issues where repo_at = ? and issue_id = ?` 102 150 row := d.db.QueryRow(query, repoAt, issueId) ··· 123 171 } 124 172 125 173 func (d *DB) NewComment(comment *Comment) error { 126 - query := `insert into comments (owner_did, repo_at, issue_id, comment_id, body) values (?, ?, ?, ?, ?)` 174 + query := `insert into comments (owner_did, repo_at, comment_at, issue_id, comment_id, body) values (?, ?, ?, ?, ?, ?)` 127 175 _, err := d.db.Exec( 128 176 query, 129 177 comment.OwnerDid, 130 178 comment.RepoAt, 179 + comment.CommentAt, 131 180 comment.Issue, 132 181 comment.CommentId, 133 182 comment.Body, ··· 138 187 func (d *DB) GetComments(repoAt string, issueId int) ([]Comment, error) { 139 188 var comments []Comment 140 189 141 - rows, err := d.db.Query(`select owner_did, issue_id, comment_id, body, created from comments where repo_at = ? and issue_id = ? order by created asc`, repoAt, issueId) 190 + rows, err := d.db.Query(`select owner_did, issue_id, comment_id, comment_at, body, created from comments where repo_at = ? and issue_id = ? order by created asc`, repoAt, issueId) 191 + if err == sql.ErrNoRows { 192 + return []Comment{}, nil 193 + } 142 194 if err != nil { 143 195 return nil, err 144 196 } ··· 147 199 for rows.Next() { 148 200 var comment Comment 149 201 var createdAt string 150 - err := rows.Scan(&comment.OwnerDid, &comment.Issue, &comment.CommentId, &comment.Body, &createdAt) 202 + err := rows.Scan(&comment.OwnerDid, &comment.Issue, &comment.CommentId, &comment.CommentAt, &comment.Body, &createdAt) 151 203 if err != nil { 152 204 return nil, err 153 205 }
+27 -35
appview/pages/templates/timeline.html
··· 4 4 <h1>Timeline</h1> 5 5 6 6 {{ range .Timeline }} 7 - <div class="relative 8 - px-4 9 - py-2 10 - border-l 11 - border-black 12 - before:content-[''] 13 - before:absolute 14 - before:w-1 15 - before:h-1 16 - before:bg-black 17 - before:rounded-full 18 - before:left-[-2.2px] 19 - before:top-1/2 20 - before:-translate-y-1/2 21 - "> 22 7 {{ if .Repo }} 23 - {{ $userHandle := index $.DidHandleMap .Repo.Did }} 24 - <div class="flex items-center"> 25 - <p class="text-gray-600"> 26 - <a href="/{{ $userHandle }}" class="no-underline hover:underline">{{ $userHandle }}</a> 27 - created 28 - <a href="/{{ $userHandle }}/{{ .Repo.Name }}" class="no-underline hover:underline">{{ .Repo.Name }}</a> 29 - <time class="text-gray-700">{{ .Repo.Created | timeFmt }}</time> 30 - </p> 31 - </div> 8 + <div class="border border-black p-4 m-2 bg-white w-1/2"> 9 + <div class="flex items-center"> 10 + <div class="text-sm text-gray-600"> 11 + {{ .Repo.Did }} created 12 + </div> 13 + div> 14 + <div class="px-3">{{ .Repo.Name }}</div> 15 + </div> 16 + 17 + <time class="text-sm text-gray-700" 18 + >{{ .Repo.Created | timeFmt }}</time 19 + > 20 + </div> 32 21 {{ else if .Follow }} 33 - {{ $userHandle := index $.DidHandleMap .Follow.UserDid }} 34 - {{ $subjectHandle := index $.DidHandleMap .Follow.SubjectDid }} 35 - <div class="flex items-center"> 36 - <p class="text-gray-600"> 37 - <a href="/{{ $userHandle }}" class="no-underline hover:underline">{{ $userHandle }}</a> 38 - followed 39 - <a href="/{{ $subjectHandle }}" class="no-underline hover:underline">{{ $subjectHandle }}</a> 40 - <time class="text-gray-700">{{ .Follow.FollowedAt | timeFmt }}</time> 41 - </p> 42 - </div> 22 + <div class="border border-black p-4 m-2 bg-white w-1/2"> 23 + <div class="flex items-center"> 24 + <div class="text-sm text-gray-600"> 25 + {{ .Follow.UserDid }} followed 26 + </div> 27 + <div class="text-sm text-gray-800"> 28 + {{ .Follow.SubjectDid }} 29 + </div> 30 + </div> 31 + 32 + <time class="text-sm text-gray-700" 33 + >{{ .Follow.FollowedAt | timeFmt }}</time 34 + > 35 + </div> 43 36 {{ end }} 44 - </div> 45 37 {{ end }} 46 38 47 39 {{ end }}
+106 -5
appview/state/repo.go
··· 11 11 "path" 12 12 "strconv" 13 13 "strings" 14 + "time" 14 15 15 16 "github.com/bluesky-social/indigo/atproto/identity" 16 17 securejoin "github.com/cyphar/filepath-securejoin" 17 18 "github.com/go-chi/chi/v5" 19 + "github.com/sotangled/tangled/api/tangled" 18 20 "github.com/sotangled/tangled/appview/auth" 19 21 "github.com/sotangled/tangled/appview/db" 20 22 "github.com/sotangled/tangled/appview/pages" 21 23 "github.com/sotangled/tangled/types" 24 + 25 + comatproto "github.com/bluesky-social/indigo/api/atproto" 26 + lexutil "github.com/bluesky-social/indigo/lex/util" 22 27 ) 23 28 24 29 func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) { ··· 564 569 return 565 570 } 566 571 572 + issue, err := s.db.GetIssue(f.RepoAt, issueIdInt) 573 + if err != nil { 574 + log.Println("failed to get issue", err) 575 + s.pages.Notice(w, "issues", "Failed to close issue. Try again later.") 576 + return 577 + } 578 + 579 + // TODO: make this more granular 567 580 if user.Did == f.OwnerDid() { 581 + 582 + closed := tangled.RepoIssueStateClosed 583 + 584 + client, _ := s.auth.AuthorizedClient(r) 585 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 586 + Collection: tangled.RepoIssueStateNSID, 587 + Repo: issue.OwnerDid, 588 + Rkey: s.TID(), 589 + Record: &lexutil.LexiconTypeDecoder{ 590 + Val: &tangled.RepoIssueState{ 591 + Issue: issue.IssueAt, 592 + State: &closed, 593 + }, 594 + }, 595 + }) 596 + 597 + if err != nil { 598 + log.Println("failed to update issue state", err) 599 + s.pages.Notice(w, "issues", "Failed to close issue. Try again later.") 600 + return 601 + } 602 + 568 603 err := s.db.CloseIssue(f.RepoAt, issueIdInt) 569 604 if err != nil { 570 605 log.Println("failed to close issue", err) 571 606 s.pages.Notice(w, "issues", "Failed to close issue. Try again later.") 572 607 return 573 608 } 609 + 574 610 s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt)) 575 611 return 576 612 } else { ··· 637 673 } 638 674 639 675 commentId := rand.IntN(1000000) 640 - fmt.Println(commentId) 641 - fmt.Println("comment id", commentId) 642 676 643 677 err := s.db.NewComment(&db.Comment{ 644 678 OwnerDid: user.Did, ··· 653 687 return 654 688 } 655 689 690 + createdAt := time.Now().Format(time.RFC3339) 691 + commentIdInt64 := int64(commentId) 692 + ownerDid := user.Did 693 + issueAt, err := s.db.GetIssueAt(f.RepoAt, issueIdInt) 694 + if err != nil { 695 + log.Println("failed to get issue at", err) 696 + s.pages.Notice(w, "issue-comment", "Failed to create comment.") 697 + return 698 + } 699 + 700 + client, _ := s.auth.AuthorizedClient(r) 701 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 702 + Collection: tangled.RepoIssueCommentNSID, 703 + Repo: user.Did, 704 + Rkey: s.TID(), 705 + Record: &lexutil.LexiconTypeDecoder{ 706 + Val: &tangled.RepoIssueComment{ 707 + Repo: &f.RepoAt, 708 + Issue: issueAt, 709 + CommentId: &commentIdInt64, 710 + Owner: &ownerDid, 711 + Body: &body, 712 + CreatedAt: &createdAt, 713 + }, 714 + }, 715 + }) 716 + if err != nil { 717 + log.Println("failed to create comment", err) 718 + s.pages.Notice(w, "issue-comment", "Failed to create comment.") 719 + return 720 + } 721 + 656 722 s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issueIdInt, commentId)) 657 723 return 658 724 } ··· 711 777 body := r.FormValue("body") 712 778 713 779 if title == "" || body == "" { 714 - s.pages.Notice(w, "issue", "Title and body are required") 780 + s.pages.Notice(w, "issues", "Title and body are required") 715 781 return 716 782 } 717 783 718 - issueId, err := s.db.NewIssue(&db.Issue{ 784 + err = s.db.NewIssue(&db.Issue{ 719 785 RepoAt: f.RepoAt, 720 786 Title: title, 721 787 Body: body, ··· 723 789 }) 724 790 if err != nil { 725 791 log.Println("failed to create issue", err) 726 - s.pages.Notice(w, "issue", "Failed to create issue.") 792 + s.pages.Notice(w, "issues", "Failed to create issue.") 793 + return 794 + } 795 + 796 + issueId, err := s.db.GetIssueId(f.RepoAt) 797 + if err != nil { 798 + log.Println("failed to get issue id", err) 799 + s.pages.Notice(w, "issues", "Failed to create issue.") 800 + return 801 + } 802 + 803 + client, _ := s.auth.AuthorizedClient(r) 804 + resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 805 + Collection: tangled.RepoIssueNSID, 806 + Repo: user.Did, 807 + Rkey: s.TID(), 808 + Record: &lexutil.LexiconTypeDecoder{ 809 + Val: &tangled.RepoIssue{ 810 + Repo: f.RepoAt, 811 + Title: title, 812 + Body: &body, 813 + Owner: user.Did, 814 + IssueId: int64(issueId), 815 + }, 816 + }, 817 + }) 818 + if err != nil { 819 + log.Println("failed to create issue", err) 820 + s.pages.Notice(w, "issues", "Failed to create issue.") 821 + return 822 + } 823 + 824 + err = s.db.SetIssueAt(f.RepoAt, issueId, resp.Uri) 825 + if err != nil { 826 + log.Println("failed to set issue at", err) 827 + s.pages.Notice(w, "issues", "Failed to create issue.") 727 828 return 728 829 } 729 830
+3
cmd/gen.go
··· 18 18 shtangled.KnotMember{}, 19 19 shtangled.GraphFollow{}, 20 20 shtangled.Repo{}, 21 + shtangled.RepoIssue{}, 22 + shtangled.RepoIssueState{}, 23 + shtangled.RepoIssueComment{}, 21 24 ); err != nil { 22 25 panic(err) 23 26 }
-1
go.mod
··· 24 24 github.com/russross/blackfriday/v2 v2.1.0 25 25 github.com/sethvargo/go-envconfig v1.1.0 26 26 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 27 - golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 28 27 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 29 28 ) 30 29
-2
go.sum
··· 307 307 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 308 308 golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 309 309 golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 310 - golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= 311 - golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 312 310 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 313 311 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 314 312 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+12
lexicons/issue/closed.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.repo.issue.state.closed", 4 + "needsCbor": true, 5 + "needsType": true, 6 + "defs": { 7 + "main": { 8 + "type": "token", 9 + "description": "closed issue" 10 + } 11 + } 12 + }
+40
lexicons/issue/comment.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.repo.issue.comment", 4 + "needsCbor": true, 5 + "needsType": true, 6 + "defs": { 7 + "main": { 8 + "type": "record", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["issue"], 13 + "properties": { 14 + "issue": { 15 + "type": "string", 16 + "format": "at-uri" 17 + }, 18 + "repo": { 19 + "type": "string", 20 + "format": "at-uri" 21 + }, 22 + "commentId": { 23 + "type": "integer" 24 + }, 25 + "owner": { 26 + "type": "string", 27 + "format": "did" 28 + }, 29 + "body": { 30 + "type": "string" 31 + }, 32 + "createdAt": { 33 + "type": "string", 34 + "format": "datetime" 35 + } 36 + } 37 + } 38 + } 39 + } 40 + }
+39
lexicons/issue/issue.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.repo.issue", 4 + "needsCbor": true, 5 + "needsType": true, 6 + "defs": { 7 + "main": { 8 + "type": "record", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["repo", "issueId", "owner", "title"], 13 + "properties": { 14 + "repo": { 15 + "type": "string", 16 + "format": "at-uri" 17 + }, 18 + "issueId": { 19 + "type": "integer" 20 + }, 21 + "owner": { 22 + "type": "string", 23 + "format": "did" 24 + }, 25 + "title": { 26 + "type": "string" 27 + }, 28 + "body": { 29 + "type": "string" 30 + }, 31 + "createdAt": { 32 + "type": "string", 33 + "format": "datetime" 34 + } 35 + } 36 + } 37 + } 38 + } 39 + }
+12
lexicons/issue/open.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.repo.issue.state.open", 4 + "needsCbor": true, 5 + "needsType": true, 6 + "defs": { 7 + "main": { 8 + "type": "token", 9 + "description": "open issue" 10 + } 11 + } 12 + }
+31
lexicons/issue/state.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.repo.issue.state", 4 + "needsCbor": true, 5 + "needsType": true, 6 + "defs": { 7 + "main": { 8 + "type": "record", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["issue"], 13 + "properties": { 14 + "issue": { 15 + "type": "string", 16 + "format": "at-uri" 17 + }, 18 + "state": { 19 + "type": "string", 20 + "description": "state of the issue", 21 + "knownValues": [ 22 + "sh.tangled.repo.issue.state.open", 23 + "sh.tangled.repo.issue.state.closed" 24 + ], 25 + "default": "sh.tangled.repo.issue.state.open" 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }
+1 -5
lexicons/repo.json
··· 9 9 "key": "tid", 10 10 "record": { 11 11 "type": "object", 12 - "required": [ 13 - "name", 14 - "knot", 15 - "owner" 16 - ], 12 + "required": ["name", "knot", "owner"], 17 13 "properties": { 18 14 "name": { 19 15 "type": "string",