appview,lexicons: atprotate the mentions & references #761

merged
opened by boltless.me targeting master from feat/mentions

Storing references parsed from the markdown body in atproto record and DB. There can be lots of reference types considering the from/to types so storing both as AT-URIs

Using sql.Tx more to combine multiple DB query to single recoverable operation.

Note: Pulls don't have mentinos/references yet

Signed-off-by: Seongmin Lee git@boltless.me

+487 -6
api/tangled/cbor_gen.go
··· 6881 } 6882 6883 cw := cbg.NewCborWriter(w) 6884 - fieldCount := 5 6885 6886 if t.Body == nil { 6887 fieldCount-- 6888 } 6889 6890 if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 6891 return err 6892 } ··· 6988 return err 6989 } 6990 6991 // t.CreatedAt (string) (string) 6992 if len("createdAt") > 1000000 { 6993 return xerrors.Errorf("Value in field \"createdAt\" was too long") ··· 7010 if _, err := cw.WriteString(string(t.CreatedAt)); err != nil { 7011 return err 7012 } 7013 return nil 7014 } 7015 ··· 7038 7039 n := extra 7040 7041 - nameBuf := make([]byte, 9) 7042 for i := uint64(0); i < n; i++ { 7043 nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 7044 if err != nil { ··· 7108 7109 t.Title = string(sval) 7110 } 7111 // t.CreatedAt (string) (string) 7112 case "createdAt": 7113 ··· 7119 7120 t.CreatedAt = string(sval) 7121 } 7122 7123 default: 7124 // Field doesn't exist on this type, so ignore it ··· 7137 } 7138 7139 cw := cbg.NewCborWriter(w) 7140 - fieldCount := 5 7141 7142 if t.ReplyTo == nil { 7143 fieldCount-- ··· 7244 } 7245 } 7246 7247 // t.CreatedAt (string) (string) 7248 if len("createdAt") > 1000000 { 7249 return xerrors.Errorf("Value in field \"createdAt\" was too long") ··· 7266 if _, err := cw.WriteString(string(t.CreatedAt)); err != nil { 7267 return err 7268 } 7269 return nil 7270 } 7271 ··· 7294 7295 n := extra 7296 7297 - nameBuf := make([]byte, 9) 7298 for i := uint64(0); i < n; i++ { 7299 nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 7300 if err != nil { ··· 7364 t.ReplyTo = (*string)(&sval) 7365 } 7366 } 7367 // t.CreatedAt (string) (string) 7368 case "createdAt": 7369 ··· 7375 7376 t.CreatedAt = string(sval) 7377 } 7378 7379 default: 7380 // Field doesn't exist on this type, so ignore it ··· 7892 } 7893 7894 cw := cbg.NewCborWriter(w) 7895 7896 - if _, err := cw.Write([]byte{164}); err != nil { 7897 return err 7898 } 7899 ··· 7962 return err 7963 } 7964 7965 // t.CreatedAt (string) (string) 7966 if len("createdAt") > 1000000 { 7967 return xerrors.Errorf("Value in field \"createdAt\" was too long") ··· 7984 if _, err := cw.WriteString(string(t.CreatedAt)); err != nil { 7985 return err 7986 } 7987 return nil 7988 } 7989 ··· 8012 8013 n := extra 8014 8015 - nameBuf := make([]byte, 9) 8016 for i := uint64(0); i < n; i++ { 8017 nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 8018 if err != nil { ··· 8061 8062 t.LexiconTypeID = string(sval) 8063 } 8064 // t.CreatedAt (string) (string) 8065 case "createdAt": 8066 ··· 8072 8073 t.CreatedAt = string(sval) 8074 } 8075 8076 default: 8077 // Field doesn't exist on this type, so ignore it
··· 6881 } 6882 6883 cw := cbg.NewCborWriter(w) 6884 + fieldCount := 7 6885 6886 if t.Body == nil { 6887 fieldCount-- 6888 } 6889 6890 + if t.Mentions == nil { 6891 + fieldCount-- 6892 + } 6893 + 6894 + if t.References == nil { 6895 + fieldCount-- 6896 + } 6897 + 6898 if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 6899 return err 6900 } ··· 6996 return err 6997 } 6998 6999 + // t.Mentions ([]string) (slice) 7000 + if t.Mentions != nil { 7001 + 7002 + if len("mentions") > 1000000 { 7003 + return xerrors.Errorf("Value in field \"mentions\" was too long") 7004 + } 7005 + 7006 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("mentions"))); err != nil { 7007 + return err 7008 + } 7009 + if _, err := cw.WriteString(string("mentions")); err != nil { 7010 + return err 7011 + } 7012 + 7013 + if len(t.Mentions) > 8192 { 7014 + return xerrors.Errorf("Slice value in field t.Mentions was too long") 7015 + } 7016 + 7017 + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Mentions))); err != nil { 7018 + return err 7019 + } 7020 + for _, v := range t.Mentions { 7021 + if len(v) > 1000000 { 7022 + return xerrors.Errorf("Value in field v was too long") 7023 + } 7024 + 7025 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { 7026 + return err 7027 + } 7028 + if _, err := cw.WriteString(string(v)); err != nil { 7029 + return err 7030 + } 7031 + 7032 + } 7033 + } 7034 + 7035 // t.CreatedAt (string) (string) 7036 if len("createdAt") > 1000000 { 7037 return xerrors.Errorf("Value in field \"createdAt\" was too long") ··· 7054 if _, err := cw.WriteString(string(t.CreatedAt)); err != nil { 7055 return err 7056 } 7057 + 7058 + // t.References ([]string) (slice) 7059 + if t.References != nil { 7060 + 7061 + if len("references") > 1000000 { 7062 + return xerrors.Errorf("Value in field \"references\" was too long") 7063 + } 7064 + 7065 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("references"))); err != nil { 7066 + return err 7067 + } 7068 + if _, err := cw.WriteString(string("references")); err != nil { 7069 + return err 7070 + } 7071 + 7072 + if len(t.References) > 8192 { 7073 + return xerrors.Errorf("Slice value in field t.References was too long") 7074 + } 7075 + 7076 + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.References))); err != nil { 7077 + return err 7078 + } 7079 + for _, v := range t.References { 7080 + if len(v) > 1000000 { 7081 + return xerrors.Errorf("Value in field v was too long") 7082 + } 7083 + 7084 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { 7085 + return err 7086 + } 7087 + if _, err := cw.WriteString(string(v)); err != nil { 7088 + return err 7089 + } 7090 + 7091 + } 7092 + } 7093 return nil 7094 } 7095 ··· 7118 7119 n := extra 7120 7121 + nameBuf := make([]byte, 10) 7122 for i := uint64(0); i < n; i++ { 7123 nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 7124 if err != nil { ··· 7188 7189 t.Title = string(sval) 7190 } 7191 + // t.Mentions ([]string) (slice) 7192 + case "mentions": 7193 + 7194 + maj, extra, err = cr.ReadHeader() 7195 + if err != nil { 7196 + return err 7197 + } 7198 + 7199 + if extra > 8192 { 7200 + return fmt.Errorf("t.Mentions: array too large (%d)", extra) 7201 + } 7202 + 7203 + if maj != cbg.MajArray { 7204 + return fmt.Errorf("expected cbor array") 7205 + } 7206 + 7207 + if extra > 0 { 7208 + t.Mentions = make([]string, extra) 7209 + } 7210 + 7211 + for i := 0; i < int(extra); i++ { 7212 + { 7213 + var maj byte 7214 + var extra uint64 7215 + var err error 7216 + _ = maj 7217 + _ = extra 7218 + _ = err 7219 + 7220 + { 7221 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 7222 + if err != nil { 7223 + return err 7224 + } 7225 + 7226 + t.Mentions[i] = string(sval) 7227 + } 7228 + 7229 + } 7230 + } 7231 // t.CreatedAt (string) (string) 7232 case "createdAt": 7233 ··· 7239 7240 t.CreatedAt = string(sval) 7241 } 7242 + // t.References ([]string) (slice) 7243 + case "references": 7244 + 7245 + maj, extra, err = cr.ReadHeader() 7246 + if err != nil { 7247 + return err 7248 + } 7249 + 7250 + if extra > 8192 { 7251 + return fmt.Errorf("t.References: array too large (%d)", extra) 7252 + } 7253 + 7254 + if maj != cbg.MajArray { 7255 + return fmt.Errorf("expected cbor array") 7256 + } 7257 + 7258 + if extra > 0 { 7259 + t.References = make([]string, extra) 7260 + } 7261 + 7262 + for i := 0; i < int(extra); i++ { 7263 + { 7264 + var maj byte 7265 + var extra uint64 7266 + var err error 7267 + _ = maj 7268 + _ = extra 7269 + _ = err 7270 + 7271 + { 7272 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 7273 + if err != nil { 7274 + return err 7275 + } 7276 + 7277 + t.References[i] = string(sval) 7278 + } 7279 + 7280 + } 7281 + } 7282 7283 default: 7284 // Field doesn't exist on this type, so ignore it ··· 7297 } 7298 7299 cw := cbg.NewCborWriter(w) 7300 + fieldCount := 7 7301 + 7302 + if t.Mentions == nil { 7303 + fieldCount-- 7304 + } 7305 + 7306 + if t.References == nil { 7307 + fieldCount-- 7308 + } 7309 7310 if t.ReplyTo == nil { 7311 fieldCount-- ··· 7412 } 7413 } 7414 7415 + // t.Mentions ([]string) (slice) 7416 + if t.Mentions != nil { 7417 + 7418 + if len("mentions") > 1000000 { 7419 + return xerrors.Errorf("Value in field \"mentions\" was too long") 7420 + } 7421 + 7422 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("mentions"))); err != nil { 7423 + return err 7424 + } 7425 + if _, err := cw.WriteString(string("mentions")); err != nil { 7426 + return err 7427 + } 7428 + 7429 + if len(t.Mentions) > 8192 { 7430 + return xerrors.Errorf("Slice value in field t.Mentions was too long") 7431 + } 7432 + 7433 + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Mentions))); err != nil { 7434 + return err 7435 + } 7436 + for _, v := range t.Mentions { 7437 + if len(v) > 1000000 { 7438 + return xerrors.Errorf("Value in field v was too long") 7439 + } 7440 + 7441 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { 7442 + return err 7443 + } 7444 + if _, err := cw.WriteString(string(v)); err != nil { 7445 + return err 7446 + } 7447 + 7448 + } 7449 + } 7450 + 7451 // t.CreatedAt (string) (string) 7452 if len("createdAt") > 1000000 { 7453 return xerrors.Errorf("Value in field \"createdAt\" was too long") ··· 7470 if _, err := cw.WriteString(string(t.CreatedAt)); err != nil { 7471 return err 7472 } 7473 + 7474 + // t.References ([]string) (slice) 7475 + if t.References != nil { 7476 + 7477 + if len("references") > 1000000 { 7478 + return xerrors.Errorf("Value in field \"references\" was too long") 7479 + } 7480 + 7481 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("references"))); err != nil { 7482 + return err 7483 + } 7484 + if _, err := cw.WriteString(string("references")); err != nil { 7485 + return err 7486 + } 7487 + 7488 + if len(t.References) > 8192 { 7489 + return xerrors.Errorf("Slice value in field t.References was too long") 7490 + } 7491 + 7492 + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.References))); err != nil { 7493 + return err 7494 + } 7495 + for _, v := range t.References { 7496 + if len(v) > 1000000 { 7497 + return xerrors.Errorf("Value in field v was too long") 7498 + } 7499 + 7500 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { 7501 + return err 7502 + } 7503 + if _, err := cw.WriteString(string(v)); err != nil { 7504 + return err 7505 + } 7506 + 7507 + } 7508 + } 7509 return nil 7510 } 7511 ··· 7534 7535 n := extra 7536 7537 + nameBuf := make([]byte, 10) 7538 for i := uint64(0); i < n; i++ { 7539 nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 7540 if err != nil { ··· 7604 t.ReplyTo = (*string)(&sval) 7605 } 7606 } 7607 + // t.Mentions ([]string) (slice) 7608 + case "mentions": 7609 + 7610 + maj, extra, err = cr.ReadHeader() 7611 + if err != nil { 7612 + return err 7613 + } 7614 + 7615 + if extra > 8192 { 7616 + return fmt.Errorf("t.Mentions: array too large (%d)", extra) 7617 + } 7618 + 7619 + if maj != cbg.MajArray { 7620 + return fmt.Errorf("expected cbor array") 7621 + } 7622 + 7623 + if extra > 0 { 7624 + t.Mentions = make([]string, extra) 7625 + } 7626 + 7627 + for i := 0; i < int(extra); i++ { 7628 + { 7629 + var maj byte 7630 + var extra uint64 7631 + var err error 7632 + _ = maj 7633 + _ = extra 7634 + _ = err 7635 + 7636 + { 7637 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 7638 + if err != nil { 7639 + return err 7640 + } 7641 + 7642 + t.Mentions[i] = string(sval) 7643 + } 7644 + 7645 + } 7646 + } 7647 // t.CreatedAt (string) (string) 7648 case "createdAt": 7649 ··· 7655 7656 t.CreatedAt = string(sval) 7657 } 7658 + // t.References ([]string) (slice) 7659 + case "references": 7660 + 7661 + maj, extra, err = cr.ReadHeader() 7662 + if err != nil { 7663 + return err 7664 + } 7665 + 7666 + if extra > 8192 { 7667 + return fmt.Errorf("t.References: array too large (%d)", extra) 7668 + } 7669 + 7670 + if maj != cbg.MajArray { 7671 + return fmt.Errorf("expected cbor array") 7672 + } 7673 + 7674 + if extra > 0 { 7675 + t.References = make([]string, extra) 7676 + } 7677 + 7678 + for i := 0; i < int(extra); i++ { 7679 + { 7680 + var maj byte 7681 + var extra uint64 7682 + var err error 7683 + _ = maj 7684 + _ = extra 7685 + _ = err 7686 + 7687 + { 7688 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 7689 + if err != nil { 7690 + return err 7691 + } 7692 + 7693 + t.References[i] = string(sval) 7694 + } 7695 + 7696 + } 7697 + } 7698 7699 default: 7700 // Field doesn't exist on this type, so ignore it ··· 8212 } 8213 8214 cw := cbg.NewCborWriter(w) 8215 + fieldCount := 6 8216 8217 + if t.Mentions == nil { 8218 + fieldCount-- 8219 + } 8220 + 8221 + if t.References == nil { 8222 + fieldCount-- 8223 + } 8224 + 8225 + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 8226 return err 8227 } 8228 ··· 8291 return err 8292 } 8293 8294 + // t.Mentions ([]string) (slice) 8295 + if t.Mentions != nil { 8296 + 8297 + if len("mentions") > 1000000 { 8298 + return xerrors.Errorf("Value in field \"mentions\" was too long") 8299 + } 8300 + 8301 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("mentions"))); err != nil { 8302 + return err 8303 + } 8304 + if _, err := cw.WriteString(string("mentions")); err != nil { 8305 + return err 8306 + } 8307 + 8308 + if len(t.Mentions) > 8192 { 8309 + return xerrors.Errorf("Slice value in field t.Mentions was too long") 8310 + } 8311 + 8312 + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Mentions))); err != nil { 8313 + return err 8314 + } 8315 + for _, v := range t.Mentions { 8316 + if len(v) > 1000000 { 8317 + return xerrors.Errorf("Value in field v was too long") 8318 + } 8319 + 8320 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { 8321 + return err 8322 + } 8323 + if _, err := cw.WriteString(string(v)); err != nil { 8324 + return err 8325 + } 8326 + 8327 + } 8328 + } 8329 + 8330 // t.CreatedAt (string) (string) 8331 if len("createdAt") > 1000000 { 8332 return xerrors.Errorf("Value in field \"createdAt\" was too long") ··· 8349 if _, err := cw.WriteString(string(t.CreatedAt)); err != nil { 8350 return err 8351 } 8352 + 8353 + // t.References ([]string) (slice) 8354 + if t.References != nil { 8355 + 8356 + if len("references") > 1000000 { 8357 + return xerrors.Errorf("Value in field \"references\" was too long") 8358 + } 8359 + 8360 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("references"))); err != nil { 8361 + return err 8362 + } 8363 + if _, err := cw.WriteString(string("references")); err != nil { 8364 + return err 8365 + } 8366 + 8367 + if len(t.References) > 8192 { 8368 + return xerrors.Errorf("Slice value in field t.References was too long") 8369 + } 8370 + 8371 + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.References))); err != nil { 8372 + return err 8373 + } 8374 + for _, v := range t.References { 8375 + if len(v) > 1000000 { 8376 + return xerrors.Errorf("Value in field v was too long") 8377 + } 8378 + 8379 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { 8380 + return err 8381 + } 8382 + if _, err := cw.WriteString(string(v)); err != nil { 8383 + return err 8384 + } 8385 + 8386 + } 8387 + } 8388 return nil 8389 } 8390 ··· 8413 8414 n := extra 8415 8416 + nameBuf := make([]byte, 10) 8417 for i := uint64(0); i < n; i++ { 8418 nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 8419 if err != nil { ··· 8462 8463 t.LexiconTypeID = string(sval) 8464 } 8465 + // t.Mentions ([]string) (slice) 8466 + case "mentions": 8467 + 8468 + maj, extra, err = cr.ReadHeader() 8469 + if err != nil { 8470 + return err 8471 + } 8472 + 8473 + if extra > 8192 { 8474 + return fmt.Errorf("t.Mentions: array too large (%d)", extra) 8475 + } 8476 + 8477 + if maj != cbg.MajArray { 8478 + return fmt.Errorf("expected cbor array") 8479 + } 8480 + 8481 + if extra > 0 { 8482 + t.Mentions = make([]string, extra) 8483 + } 8484 + 8485 + for i := 0; i < int(extra); i++ { 8486 + { 8487 + var maj byte 8488 + var extra uint64 8489 + var err error 8490 + _ = maj 8491 + _ = extra 8492 + _ = err 8493 + 8494 + { 8495 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 8496 + if err != nil { 8497 + return err 8498 + } 8499 + 8500 + t.Mentions[i] = string(sval) 8501 + } 8502 + 8503 + } 8504 + } 8505 // t.CreatedAt (string) (string) 8506 case "createdAt": 8507 ··· 8513 8514 t.CreatedAt = string(sval) 8515 } 8516 + // t.References ([]string) (slice) 8517 + case "references": 8518 + 8519 + maj, extra, err = cr.ReadHeader() 8520 + if err != nil { 8521 + return err 8522 + } 8523 + 8524 + if extra > 8192 { 8525 + return fmt.Errorf("t.References: array too large (%d)", extra) 8526 + } 8527 + 8528 + if maj != cbg.MajArray { 8529 + return fmt.Errorf("expected cbor array") 8530 + } 8531 + 8532 + if extra > 0 { 8533 + t.References = make([]string, extra) 8534 + } 8535 + 8536 + for i := 0; i < int(extra); i++ { 8537 + { 8538 + var maj byte 8539 + var extra uint64 8540 + var err error 8541 + _ = maj 8542 + _ = extra 8543 + _ = err 8544 + 8545 + { 8546 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 8547 + if err != nil { 8548 + return err 8549 + } 8550 + 8551 + t.References[i] = string(sval) 8552 + } 8553 + 8554 + } 8555 + } 8556 8557 default: 8558 // Field doesn't exist on this type, so ignore it
+7 -5
api/tangled/issuecomment.go
··· 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" cborgen:"body"` 22 - CreatedAt string `json:"createdAt" cborgen:"createdAt"` 23 - Issue string `json:"issue" cborgen:"issue"` 24 - ReplyTo *string `json:"replyTo,omitempty" cborgen:"replyTo,omitempty"` 25 }
··· 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" cborgen:"body"` 22 + CreatedAt string `json:"createdAt" cborgen:"createdAt"` 23 + Issue string `json:"issue" cborgen:"issue"` 24 + Mentions []string `json:"mentions,omitempty" cborgen:"mentions,omitempty"` 25 + References []string `json:"references,omitempty" cborgen:"references,omitempty"` 26 + ReplyTo *string `json:"replyTo,omitempty" cborgen:"replyTo,omitempty"` 27 }
+6 -4
api/tangled/pullcomment.go
··· 17 } // 18 // RECORDTYPE: RepoPullComment 19 type RepoPullComment struct { 20 - LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull.comment" cborgen:"$type,const=sh.tangled.repo.pull.comment"` 21 - Body string `json:"body" cborgen:"body"` 22 - CreatedAt string `json:"createdAt" cborgen:"createdAt"` 23 - Pull string `json:"pull" cborgen:"pull"` 24 }
··· 17 } // 18 // RECORDTYPE: RepoPullComment 19 type RepoPullComment struct { 20 + LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull.comment" cborgen:"$type,const=sh.tangled.repo.pull.comment"` 21 + Body string `json:"body" cborgen:"body"` 22 + CreatedAt string `json:"createdAt" cborgen:"createdAt"` 23 + Mentions []string `json:"mentions,omitempty" cborgen:"mentions,omitempty"` 24 + Pull string `json:"pull" cborgen:"pull"` 25 + References []string `json:"references,omitempty" cborgen:"references,omitempty"` 26 }
+7 -5
api/tangled/repoissue.go
··· 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" cborgen:"createdAt"` 23 - Repo string `json:"repo" cborgen:"repo"` 24 - Title string `json:"title" cborgen:"title"` 25 }
··· 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" cborgen:"createdAt"` 23 + Mentions []string `json:"mentions,omitempty" cborgen:"mentions,omitempty"` 24 + References []string `json:"references,omitempty" cborgen:"references,omitempty"` 25 + Repo string `json:"repo" cborgen:"repo"` 26 + Title string `json:"title" cborgen:"title"` 27 }
+9
appview/db/db.go
··· 561 email_notifications integer not null default 0 562 ); 563 564 create table if not exists migrations ( 565 id integer primary key autoincrement, 566 name text unique ··· 571 create index if not exists idx_notifications_recipient_read on notifications(recipient_did, read); 572 create index if not exists idx_stars_created on stars(created); 573 create index if not exists idx_stars_repo_at_created on stars(repo_at, created); 574 `) 575 if err != nil { 576 return nil, err
··· 561 email_notifications integer not null default 0 562 ); 563 564 + create table if not exists reference_links ( 565 + id integer primary key autoincrement, 566 + from_at text not null, 567 + to_at text not null, 568 + unique (from_at, to_at) 569 + ); 570 + 571 create table if not exists migrations ( 572 id integer primary key autoincrement, 573 name text unique ··· 578 create index if not exists idx_notifications_recipient_read on notifications(recipient_did, read); 579 create index if not exists idx_stars_created on stars(created); 580 create index if not exists idx_stars_repo_at_created on stars(repo_at, created); 581 + create index if not exists idx_references_from_at on reference_links(from_at); 582 + create index if not exists idx_references_to_at on reference_links(to_at); 583 `) 584 if err != nil { 585 return nil, err
+73 -18
appview/db/issues.go
··· 10 "time" 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "tangled.org/core/appview/models" 14 "tangled.org/core/appview/pagination" 15 ) ··· 69 returning rowid, issue_id 70 `, issue.RepoAt, issue.Did, issue.Rkey, newIssueId, issue.Title, issue.Body) 71 72 - return row.Scan(&issue.Id, &issue.IssueId) 73 } 74 75 func updateIssue(tx *sql.Tx, issue *models.Issue) error { ··· 79 set title = ?, body = ?, edited = ? 80 where did = ? and rkey = ? 81 `, issue.Title, issue.Body, time.Now().Format(time.RFC3339), issue.Did, issue.Rkey) 82 - return err 83 } 84 85 func GetIssuesPaginated(e Execer, page pagination.Page, filters ...filter) ([]models.Issue, error) { ··· 234 } 235 } 236 237 var issues []models.Issue 238 for _, i := range issueMap { 239 issues = append(issues, *i) ··· 323 return ids, nil 324 } 325 326 - func AddIssueComment(e Execer, c models.IssueComment) (int64, error) { 327 - result, err := e.Exec( 328 `insert into issue_comments ( 329 did, 330 rkey, ··· 363 return 0, err 364 } 365 366 return id, nil 367 } 368 ··· 386 } 387 388 func GetIssueComments(e Execer, filters ...filter) ([]models.IssueComment, error) { 389 - var comments []models.IssueComment 390 391 var conditions []string 392 var args []any ··· 465 comment.ReplyTo = &replyTo.V 466 } 467 468 - comments = append(comments, comment) 469 } 470 471 if err = rows.Err(); err != nil { 472 return nil, err 473 } 474 475 return comments, nil 476 } 477 478 - func DeleteIssues(e Execer, filters ...filter) error { 479 - var conditions []string 480 - var args []any 481 - for _, filter := range filters { 482 - conditions = append(conditions, filter.Condition()) 483 - args = append(args, filter.Arg()...) 484 } 485 486 - whereClause := "" 487 - if conditions != nil { 488 - whereClause = " where " + strings.Join(conditions, " and ") 489 } 490 491 - query := fmt.Sprintf(`delete from issues %s`, whereClause) 492 - _, err := e.Exec(query, args...) 493 - return err 494 } 495 496 func CloseIssues(e Execer, filters ...filter) error {
··· 10 "time" 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 + "tangled.org/core/api/tangled" 14 "tangled.org/core/appview/models" 15 "tangled.org/core/appview/pagination" 16 ) ··· 70 returning rowid, issue_id 71 `, issue.RepoAt, issue.Did, issue.Rkey, newIssueId, issue.Title, issue.Body) 72 73 + err = row.Scan(&issue.Id, &issue.IssueId) 74 + if err != nil { 75 + return fmt.Errorf("scan row: %w", err) 76 + } 77 + 78 + if err := putReferences(tx, issue.AtUri(), issue.References); err != nil { 79 + return fmt.Errorf("put reference_links: %w", err) 80 + } 81 + return nil 82 } 83 84 func updateIssue(tx *sql.Tx, issue *models.Issue) error { ··· 88 set title = ?, body = ?, edited = ? 89 where did = ? and rkey = ? 90 `, issue.Title, issue.Body, time.Now().Format(time.RFC3339), issue.Did, issue.Rkey) 91 + if err != nil { 92 + return err 93 + } 94 + 95 + if err := putReferences(tx, issue.AtUri(), issue.References); err != nil { 96 + return fmt.Errorf("put reference_links: %w", err) 97 + } 98 + return nil 99 } 100 101 func GetIssuesPaginated(e Execer, page pagination.Page, filters ...filter) ([]models.Issue, error) { ··· 250 } 251 } 252 253 + // collect references for each issue 254 + allReferencs, err := GetReferencesAll(e, FilterIn("from_at", issueAts)) 255 + if err != nil { 256 + return nil, fmt.Errorf("failed to query reference_links: %w", err) 257 + } 258 + for issueAt, references := range allReferencs { 259 + if issue, ok := issueMap[issueAt.String()]; ok { 260 + issue.References = references 261 + } 262 + } 263 + 264 var issues []models.Issue 265 for _, i := range issueMap { 266 issues = append(issues, *i) ··· 350 return ids, nil 351 } 352 353 + func AddIssueComment(tx *sql.Tx, c models.IssueComment) (int64, error) { 354 + result, err := tx.Exec( 355 `insert into issue_comments ( 356 did, 357 rkey, ··· 390 return 0, err 391 } 392 393 + if err := putReferences(tx, c.AtUri(), c.References); err != nil { 394 + return 0, fmt.Errorf("put reference_links: %w", err) 395 + } 396 + 397 return id, nil 398 } 399 ··· 417 } 418 419 func GetIssueComments(e Execer, filters ...filter) ([]models.IssueComment, error) { 420 + commentMap := make(map[string]*models.IssueComment) 421 422 var conditions []string 423 var args []any ··· 496 comment.ReplyTo = &replyTo.V 497 } 498 499 + atUri := comment.AtUri().String() 500 + commentMap[atUri] = &comment 501 } 502 503 if err = rows.Err(); err != nil { 504 return nil, err 505 } 506 507 + // collect references for each comments 508 + commentAts := slices.Collect(maps.Keys(commentMap)) 509 + allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts)) 510 + if err != nil { 511 + return nil, fmt.Errorf("failed to query reference_links: %w", err) 512 + } 513 + for commentAt, references := range allReferencs { 514 + if comment, ok := commentMap[commentAt.String()]; ok { 515 + comment.References = references 516 + } 517 + } 518 + 519 + var comments []models.IssueComment 520 + for _, c := range commentMap { 521 + comments = append(comments, *c) 522 + } 523 + 524 + sort.Slice(comments, func(i, j int) bool { 525 + return comments[i].Created.After(comments[j].Created) 526 + }) 527 + 528 return comments, nil 529 } 530 531 + func DeleteIssues(tx *sql.Tx, did, rkey string) error { 532 + _, err := tx.Exec( 533 + `delete from issues 534 + where did = ? and rkey = ?`, 535 + did, 536 + rkey, 537 + ) 538 + if err != nil { 539 + return fmt.Errorf("delete issue: %w", err) 540 } 541 542 + uri := syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", did, tangled.RepoIssueNSID, rkey)) 543 + err = deleteReferences(tx, uri) 544 + if err != nil { 545 + return fmt.Errorf("delete reference_links: %w", err) 546 } 547 548 + return nil 549 } 550 551 func CloseIssues(e Execer, filters ...filter) error {
+31 -5
appview/db/pulls.go
··· 432 submissionIds := slices.Collect(maps.Keys(submissionMap)) 433 comments, err := GetPullComments(e, FilterIn("submission_id", submissionIds)) 434 if err != nil { 435 - return nil, err 436 } 437 for _, comment := range comments { 438 if submission, ok := submissionMap[comment.SubmissionId]; ok { ··· 492 } 493 defer rows.Close() 494 495 - var comments []models.PullComment 496 for rows.Next() { 497 var comment models.PullComment 498 var createdAt string ··· 514 comment.Created = t 515 } 516 517 - comments = append(comments, comment) 518 } 519 520 if err := rows.Err(); err != nil { 521 return nil, err 522 } 523 524 return comments, nil 525 } 526 ··· 600 return pulls, nil 601 } 602 603 - func NewPullComment(e Execer, comment *models.PullComment) (int64, error) { 604 query := `insert into pull_comments (owner_did, repo_at, submission_id, comment_at, pull_id, body) values (?, ?, ?, ?, ?, ?)` 605 - res, err := e.Exec( 606 query, 607 comment.OwnerDid, 608 comment.RepoAt, ··· 620 return 0, err 621 } 622 623 return i, nil 624 } 625
··· 432 submissionIds := slices.Collect(maps.Keys(submissionMap)) 433 comments, err := GetPullComments(e, FilterIn("submission_id", submissionIds)) 434 if err != nil { 435 + return nil, fmt.Errorf("failed to get pull comments: %w", err) 436 } 437 for _, comment := range comments { 438 if submission, ok := submissionMap[comment.SubmissionId]; ok { ··· 492 } 493 defer rows.Close() 494 495 + commentMap := make(map[string]*models.PullComment) 496 for rows.Next() { 497 var comment models.PullComment 498 var createdAt string ··· 514 comment.Created = t 515 } 516 517 + atUri := comment.AtUri().String() 518 + commentMap[atUri] = &comment 519 } 520 521 if err := rows.Err(); err != nil { 522 return nil, err 523 } 524 525 + // collect references for each comments 526 + commentAts := slices.Collect(maps.Keys(commentMap)) 527 + allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts)) 528 + if err != nil { 529 + return nil, fmt.Errorf("failed to query reference_links: %w", err) 530 + } 531 + for commentAt, references := range allReferencs { 532 + if comment, ok := commentMap[commentAt.String()]; ok { 533 + comment.References = references 534 + } 535 + } 536 + 537 + var comments []models.PullComment 538 + for _, c := range commentMap { 539 + comments = append(comments, *c) 540 + } 541 + 542 + sort.Slice(comments, func(i, j int) bool { 543 + return comments[i].Created.Before(comments[j].Created) 544 + }) 545 + 546 return comments, nil 547 } 548 ··· 622 return pulls, nil 623 } 624 625 + func NewPullComment(tx *sql.Tx, comment *models.PullComment) (int64, error) { 626 query := `insert into pull_comments (owner_did, repo_at, submission_id, comment_at, pull_id, body) values (?, ?, ?, ?, ?, ?)` 627 + res, err := tx.Exec( 628 query, 629 comment.OwnerDid, 630 comment.RepoAt, ··· 642 return 0, err 643 } 644 645 + if err := putReferences(tx, comment.AtUri(), comment.References); err != nil { 646 + return 0, fmt.Errorf("put reference_links: %w", err) 647 + } 648 + 649 return i, nil 650 } 651
+93 -14
appview/db/reference.go
··· 10 "tangled.org/core/appview/models" 11 ) 12 13 - // FindReferences resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs. 14 // It will ignore missing refLinks. 15 - func FindReferences(e Execer, refLinks []models.ReferenceLink) ([]syntax.ATURI, error) { 16 var ( 17 issueRefs []models.ReferenceLink 18 pullRefs []models.ReferenceLink ··· 27 } 28 issueUris, err := findIssueReferences(e, issueRefs) 29 if err != nil { 30 - return nil, err 31 } 32 pullUris, err := findPullReferences(e, pullRefs) 33 if err != nil { 34 - return nil, err 35 } 36 37 return append(issueUris, pullUris...), nil ··· 101 } 102 uris = append(uris, uri) 103 } 104 return uris, nil 105 } 106 ··· 120 ) 121 select 122 p.owner_did, p.rkey, 123 - c.owner_did, c.rkey 124 from input inp 125 join repos r 126 on r.did = inp.owner_did ··· 146 for rows.Next() { 147 // Scan rows 148 var pullOwner, pullRkey string 149 - var commentOwner, commentRkey sql.NullString 150 var uri syntax.ATURI 151 - if err := rows.Scan(&pullOwner, &pullRkey, &commentOwner, &commentRkey); err != nil { 152 return nil, err 153 } 154 - if commentOwner.Valid && commentRkey.Valid { 155 - uri = syntax.ATURI(fmt.Sprintf( 156 - "at://%s/%s/%s", 157 - commentOwner.String, 158 - tangled.RepoPullCommentNSID, 159 - commentRkey.String, 160 - )) 161 } else { 162 uri = syntax.ATURI(fmt.Sprintf( 163 "at://%s/%s/%s", ··· 170 } 171 return uris, nil 172 }
··· 10 "tangled.org/core/appview/models" 11 ) 12 13 + // ValidateReferenceLinks resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs. 14 // It will ignore missing refLinks. 15 + func ValidateReferenceLinks(e Execer, refLinks []models.ReferenceLink) ([]syntax.ATURI, error) { 16 var ( 17 issueRefs []models.ReferenceLink 18 pullRefs []models.ReferenceLink ··· 27 } 28 issueUris, err := findIssueReferences(e, issueRefs) 29 if err != nil { 30 + return nil, fmt.Errorf("find issue references: %w", err) 31 } 32 pullUris, err := findPullReferences(e, pullRefs) 33 if err != nil { 34 + return nil, fmt.Errorf("find pull references: %w", err) 35 } 36 37 return append(issueUris, pullUris...), nil ··· 101 } 102 uris = append(uris, uri) 103 } 104 + if err := rows.Err(); err != nil { 105 + return nil, fmt.Errorf("iterate rows: %w", err) 106 + } 107 + 108 return uris, nil 109 } 110 ··· 124 ) 125 select 126 p.owner_did, p.rkey, 127 + c.comment_at 128 from input inp 129 join repos r 130 on r.did = inp.owner_did ··· 150 for rows.Next() { 151 // Scan rows 152 var pullOwner, pullRkey string 153 + var commentUri sql.NullString 154 var uri syntax.ATURI 155 + if err := rows.Scan(&pullOwner, &pullRkey, &commentUri); err != nil { 156 return nil, err 157 } 158 + if commentUri.Valid { 159 + // no-op 160 + uri = syntax.ATURI(commentUri.String) 161 } else { 162 uri = syntax.ATURI(fmt.Sprintf( 163 "at://%s/%s/%s", ··· 170 } 171 return uris, nil 172 } 173 + 174 + func putReferences(tx *sql.Tx, fromAt syntax.ATURI, references []syntax.ATURI) error { 175 + fmt.Println("insering references", references) 176 + err := deleteReferences(tx, fromAt) 177 + if err != nil { 178 + return fmt.Errorf("delete old reference_links: %w", err) 179 + } 180 + if len(references) == 0 { 181 + return nil 182 + } 183 + 184 + values := make([]string, 0, len(references)) 185 + args := make([]any, 0, len(references)*2) 186 + for _, ref := range references { 187 + values = append(values, "(?, ?)") 188 + args = append(args, fromAt, ref) 189 + } 190 + _, err = tx.Exec( 191 + fmt.Sprintf( 192 + `insert into reference_links (from_at, to_at) 193 + values %s`, 194 + strings.Join(values, ","), 195 + ), 196 + args..., 197 + ) 198 + if err != nil { 199 + return fmt.Errorf("insert new reference_links: %w", err) 200 + } 201 + return nil 202 + } 203 + 204 + func deleteReferences(tx *sql.Tx, fromAt syntax.ATURI) error { 205 + _, err := tx.Exec(`delete from reference_links where from_at = ?`, fromAt) 206 + return err 207 + } 208 + 209 + func GetReferencesAll(e Execer, filters ...filter) (map[syntax.ATURI][]syntax.ATURI, error) { 210 + var ( 211 + conditions []string 212 + args []any 213 + ) 214 + for _, filter := range filters { 215 + conditions = append(conditions, filter.Condition()) 216 + args = append(args, filter.Arg()...) 217 + } 218 + 219 + whereClause := "" 220 + if conditions != nil { 221 + whereClause = " where " + strings.Join(conditions, " and ") 222 + } 223 + 224 + rows, err := e.Query( 225 + fmt.Sprintf( 226 + `select from_at, to_at from reference_links %s`, 227 + whereClause, 228 + ), 229 + args..., 230 + ) 231 + if err != nil { 232 + return nil, fmt.Errorf("query reference_links: %w", err) 233 + } 234 + defer rows.Close() 235 + 236 + result := make(map[syntax.ATURI][]syntax.ATURI) 237 + 238 + for rows.Next() { 239 + var from, to syntax.ATURI 240 + if err := rows.Scan(&from, &to); err != nil { 241 + return nil, fmt.Errorf("scan row: %w", err) 242 + } 243 + 244 + result[from] = append(result[from], to) 245 + } 246 + if err := rows.Err(); err != nil { 247 + return nil, fmt.Errorf("iterate rows: %w", err) 248 + } 249 + 250 + return result, nil 251 + }
+22 -5
appview/ingester.go
··· 841 return nil 842 843 case jmodels.CommitOperationDelete: 844 if err := db.DeleteIssues( 845 - ddb, 846 - db.FilterEq("did", did), 847 - db.FilterEq("rkey", rkey), 848 ); err != nil { 849 l.Error("failed to delete", "err", err) 850 return fmt.Errorf("failed to delete issue record: %w", err) 851 } 852 853 return nil 854 } ··· 888 return fmt.Errorf("failed to validate comment: %w", err) 889 } 890 891 - _, err = db.AddIssueComment(ddb, *comment) 892 if err != nil { 893 return fmt.Errorf("failed to create issue comment: %w", err) 894 } 895 896 - return nil 897 898 case jmodels.CommitOperationDelete: 899 if err := db.DeleteIssueComments(
··· 841 return nil 842 843 case jmodels.CommitOperationDelete: 844 + tx, err := ddb.BeginTx(ctx, nil) 845 + if err != nil { 846 + l.Error("failed to begin transaction", "err", err) 847 + return err 848 + } 849 + defer tx.Rollback() 850 + 851 if err := db.DeleteIssues( 852 + tx, 853 + did, 854 + rkey, 855 ); err != nil { 856 l.Error("failed to delete", "err", err) 857 return fmt.Errorf("failed to delete issue record: %w", err) 858 } 859 + if err := tx.Commit(); err != nil { 860 + l.Error("failed to commit txn", "err", err) 861 + return err 862 + } 863 864 return nil 865 } ··· 899 return fmt.Errorf("failed to validate comment: %w", err) 900 } 901 902 + tx, err := ddb.Begin() 903 + if err != nil { 904 + return fmt.Errorf("failed to start transaction: %w", err) 905 + } 906 + defer tx.Rollback() 907 + 908 + _, err = db.AddIssueComment(tx, *comment) 909 if err != nil { 910 return fmt.Errorf("failed to create issue comment: %w", err) 911 } 912 913 + return tx.Commit() 914 915 case jmodels.CommitOperationDelete: 916 if err := db.DeleteIssueComments(
+55 -19
appview/issues/issues.go
··· 241 } 242 l = l.With("did", issue.Did, "rkey", issue.Rkey) 243 244 // delete from PDS 245 client, err := rp.oauth.AuthorizedClient(r) 246 if err != nil { ··· 261 } 262 263 // delete from db 264 - if err := db.DeleteIssues(rp.db, db.FilterEq("id", issue.Id)); err != nil { 265 l.Error("failed to delete issue", "err", err) 266 rp.pages.Notice(w, noticeId, "Failed to delete issue.") 267 return 268 } 269 270 rp.notifier.DeleteIssue(r.Context(), issue) 271 ··· 402 replyTo = &replyToUri 403 } 404 405 - mentions, _ := rp.refResolver.Resolve(r.Context(), body) 406 407 comment := models.IssueComment{ 408 - Did: user.Did, 409 - Rkey: tid.TID(), 410 - IssueAt: issue.AtUri().String(), 411 - ReplyTo: replyTo, 412 - Body: body, 413 - Created: time.Now(), 414 } 415 if err = rp.validator.ValidateIssueComment(&comment); err != nil { 416 l.Error("failed to validate comment", "err", err) ··· 447 } 448 }() 449 450 - commentId, err := db.AddIssueComment(rp.db, comment) 451 if err != nil { 452 l.Error("failed to create comment", "err", err) 453 rp.pages.Notice(w, "issue-comment", "Failed to create comment.") 454 return 455 } 456 457 // reset atUri to make rollback a no-op 458 atUri = "" ··· 569 newComment.Edited = &now 570 record := newComment.AsRecord() 571 572 - _, err = db.AddIssueComment(rp.db, newComment) 573 if err != nil { 574 l.Error("failed to perferom update-description query", "err", err) 575 rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.") 576 return 577 } 578 579 // rkey is optional, it was introduced later 580 if newComment.Rkey != "" { ··· 881 }) 882 case http.MethodPost: 883 body := r.FormValue("body") 884 - mentions, _ := rp.refResolver.Resolve(r.Context(), body) 885 886 issue := &models.Issue{ 887 - RepoAt: f.RepoAt(), 888 - Rkey: tid.TID(), 889 - Title: r.FormValue("title"), 890 - Body: body, 891 - Open: true, 892 - Did: user.Did, 893 - Created: time.Now(), 894 - Repo: &f.Repo, 895 } 896 897 if err := rp.validator.ValidateIssue(issue); err != nil {
··· 241 } 242 l = l.With("did", issue.Did, "rkey", issue.Rkey) 243 244 + tx, err := rp.db.Begin() 245 + if err != nil { 246 + l.Error("failed to start transaction", "err", err) 247 + rp.pages.Notice(w, "issue-comment", "Failed to create comment, try again later.") 248 + return 249 + } 250 + defer tx.Rollback() 251 + 252 // delete from PDS 253 client, err := rp.oauth.AuthorizedClient(r) 254 if err != nil { ··· 269 } 270 271 // delete from db 272 + if err := db.DeleteIssues(tx, issue.Did, issue.Rkey); err != nil { 273 l.Error("failed to delete issue", "err", err) 274 rp.pages.Notice(w, noticeId, "Failed to delete issue.") 275 return 276 } 277 + tx.Commit() 278 279 rp.notifier.DeleteIssue(r.Context(), issue) 280 ··· 411 replyTo = &replyToUri 412 } 413 414 + mentions, references := rp.refResolver.Resolve(r.Context(), body) 415 416 comment := models.IssueComment{ 417 + Did: user.Did, 418 + Rkey: tid.TID(), 419 + IssueAt: issue.AtUri().String(), 420 + ReplyTo: replyTo, 421 + Body: body, 422 + Created: time.Now(), 423 + Mentions: mentions, 424 + References: references, 425 } 426 if err = rp.validator.ValidateIssueComment(&comment); err != nil { 427 l.Error("failed to validate comment", "err", err) ··· 458 } 459 }() 460 461 + tx, err := rp.db.Begin() 462 + if err != nil { 463 + l.Error("failed to start transaction", "err", err) 464 + rp.pages.Notice(w, "issue-comment", "Failed to create comment, try again later.") 465 + return 466 + } 467 + defer tx.Rollback() 468 + 469 + commentId, err := db.AddIssueComment(tx, comment) 470 if err != nil { 471 l.Error("failed to create comment", "err", err) 472 rp.pages.Notice(w, "issue-comment", "Failed to create comment.") 473 return 474 } 475 + err = tx.Commit() 476 + if err != nil { 477 + l.Error("failed to commit transaction", "err", err) 478 + rp.pages.Notice(w, "issue-comment", "Failed to create comment, try again later.") 479 + return 480 + } 481 482 // reset atUri to make rollback a no-op 483 atUri = "" ··· 594 newComment.Edited = &now 595 record := newComment.AsRecord() 596 597 + tx, err := rp.db.Begin() 598 + if err != nil { 599 + l.Error("failed to start transaction", "err", err) 600 + rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.") 601 + return 602 + } 603 + defer tx.Rollback() 604 + 605 + _, err = db.AddIssueComment(tx, newComment) 606 if err != nil { 607 l.Error("failed to perferom update-description query", "err", err) 608 rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.") 609 return 610 } 611 + tx.Commit() 612 613 // rkey is optional, it was introduced later 614 if newComment.Rkey != "" { ··· 915 }) 916 case http.MethodPost: 917 body := r.FormValue("body") 918 + mentions, references := rp.refResolver.Resolve(r.Context(), body) 919 920 issue := &models.Issue{ 921 + RepoAt: f.RepoAt(), 922 + Rkey: tid.TID(), 923 + Title: r.FormValue("title"), 924 + Body: body, 925 + Open: true, 926 + Did: user.Did, 927 + Created: time.Now(), 928 + Mentions: mentions, 929 + References: references, 930 + Repo: &f.Repo, 931 } 932 933 if err := rp.validator.ValidateIssue(issue); err != nil {
+64 -28
appview/models/issue.go
··· 10 ) 11 12 type Issue struct { 13 - Id int64 14 - Did string 15 - Rkey string 16 - RepoAt syntax.ATURI 17 - IssueId int 18 - Created time.Time 19 - Edited *time.Time 20 - Deleted *time.Time 21 - Title string 22 - Body string 23 - Open bool 24 25 // optionally, populate this when querying for reverse mappings 26 // like comment counts, parent repo etc. ··· 34 } 35 36 func (i *Issue) AsRecord() tangled.RepoIssue { 37 return tangled.RepoIssue{ 38 - Repo: i.RepoAt.String(), 39 - Title: i.Title, 40 - Body: &i.Body, 41 - CreatedAt: i.Created.Format(time.RFC3339), 42 } 43 } 44 ··· 161 } 162 163 type IssueComment struct { 164 - Id int64 165 - Did string 166 - Rkey string 167 - IssueAt string 168 - ReplyTo *string 169 - Body string 170 - Created time.Time 171 - Edited *time.Time 172 - Deleted *time.Time 173 } 174 175 func (i *IssueComment) AtUri() syntax.ATURI { ··· 177 } 178 179 func (i *IssueComment) AsRecord() tangled.RepoIssueComment { 180 return tangled.RepoIssueComment{ 181 - Body: i.Body, 182 - Issue: i.IssueAt, 183 - CreatedAt: i.Created.Format(time.RFC3339), 184 - ReplyTo: i.ReplyTo, 185 } 186 } 187 ··· 205 return nil, err 206 } 207 208 comment := IssueComment{ 209 Did: ownerDid, 210 Rkey: rkey, ··· 212 IssueAt: record.Issue, 213 ReplyTo: record.ReplyTo, 214 Created: created, 215 } 216 217 return &comment, nil
··· 10 ) 11 12 type Issue struct { 13 + Id int64 14 + Did string 15 + Rkey string 16 + RepoAt syntax.ATURI 17 + IssueId int 18 + Created time.Time 19 + Edited *time.Time 20 + Deleted *time.Time 21 + Title string 22 + Body string 23 + Open bool 24 + Mentions []syntax.DID 25 + References []syntax.ATURI 26 27 // optionally, populate this when querying for reverse mappings 28 // like comment counts, parent repo etc. ··· 36 } 37 38 func (i *Issue) AsRecord() tangled.RepoIssue { 39 + mentions := make([]string, len(i.Mentions)) 40 + for i, did := range i.Mentions { 41 + mentions[i] = string(did) 42 + } 43 + references := make([]string, len(i.References)) 44 + for i, uri := range i.References { 45 + references[i] = string(uri) 46 + } 47 return tangled.RepoIssue{ 48 + Repo: i.RepoAt.String(), 49 + Title: i.Title, 50 + Body: &i.Body, 51 + Mentions: mentions, 52 + References: references, 53 + CreatedAt: i.Created.Format(time.RFC3339), 54 } 55 } 56 ··· 173 } 174 175 type IssueComment struct { 176 + Id int64 177 + Did string 178 + Rkey string 179 + IssueAt string 180 + ReplyTo *string 181 + Body string 182 + Created time.Time 183 + Edited *time.Time 184 + Deleted *time.Time 185 + Mentions []syntax.DID 186 + References []syntax.ATURI 187 } 188 189 func (i *IssueComment) AtUri() syntax.ATURI { ··· 191 } 192 193 func (i *IssueComment) AsRecord() tangled.RepoIssueComment { 194 + mentions := make([]string, len(i.Mentions)) 195 + for i, did := range i.Mentions { 196 + mentions[i] = string(did) 197 + } 198 + references := make([]string, len(i.References)) 199 + for i, uri := range i.References { 200 + references[i] = string(uri) 201 + } 202 return tangled.RepoIssueComment{ 203 + Body: i.Body, 204 + Issue: i.IssueAt, 205 + CreatedAt: i.Created.Format(time.RFC3339), 206 + ReplyTo: i.ReplyTo, 207 + Mentions: mentions, 208 + References: references, 209 } 210 } 211 ··· 229 return nil, err 230 } 231 232 + i := record 233 + mentions := make([]syntax.DID, len(record.Mentions)) 234 + for i, did := range record.Mentions { 235 + mentions[i] = syntax.DID(did) 236 + } 237 + references := make([]syntax.ATURI, len(record.References)) 238 + for i, uri := range i.References { 239 + references[i] = syntax.ATURI(uri) 240 + } 241 + 242 comment := IssueComment{ 243 Did: ownerDid, 244 Rkey: rkey, ··· 246 IssueAt: record.Issue, 247 ReplyTo: record.ReplyTo, 248 Created: created, 249 + Mentions: mentions, 250 + References: references, 251 } 252 253 return &comment, nil
+26
appview/models/pull.go
··· 147 // content 148 Body string 149 150 // meta 151 Created time.Time 152 } 153 154 func (p *Pull) LastRoundNumber() int { 155 return len(p.Submissions) - 1 156 }
··· 147 // content 148 Body string 149 150 + // meta 151 + Mentions []syntax.DID 152 + References []syntax.ATURI 153 + 154 // meta 155 Created time.Time 156 } 157 158 + func (p *PullComment) AtUri() syntax.ATURI { 159 + return syntax.ATURI(p.CommentAt) 160 + } 161 + 162 + // func (p *PullComment) AsRecord() tangled.RepoPullComment { 163 + // mentions := make([]string, len(p.Mentions)) 164 + // for i, did := range p.Mentions { 165 + // mentions[i] = string(did) 166 + // } 167 + // references := make([]string, len(p.References)) 168 + // for i, uri := range p.References { 169 + // references[i] = string(uri) 170 + // } 171 + // return tangled.RepoPullComment{ 172 + // Pull: p.PullAt, 173 + // Body: p.Body, 174 + // Mentions: mentions, 175 + // References: references, 176 + // CreatedAt: p.Created.Format(time.RFC3339), 177 + // } 178 + // } 179 + 180 func (p *Pull) LastRoundNumber() int { 181 return len(p.Submissions) - 1 182 }
+3 -1
appview/pulls/pulls.go
··· 733 return 734 } 735 736 - mentions, _ := s.refResolver.Resolve(r.Context(), body) 737 738 // Start a transaction 739 tx, err := s.db.BeginTx(r.Context(), nil) ··· 777 Body: body, 778 CommentAt: atResp.Uri, 779 SubmissionId: pull.Submissions[roundNumber].ID, 780 } 781 782 // Create the pull comment in the database with the commentAt field
··· 733 return 734 } 735 736 + mentions, references := s.refResolver.Resolve(r.Context(), body) 737 738 // Start a transaction 739 tx, err := s.db.BeginTx(r.Context(), nil) ··· 777 Body: body, 778 CommentAt: atResp.Uri, 779 SubmissionId: pull.Submissions[roundNumber].ID, 780 + Mentions: mentions, 781 + References: references, 782 } 783 784 // Create the pull comment in the database with the commentAt field
+2 -2
appview/refresolver/resolver.go
··· 34 } 35 36 func (r *Resolver) Resolve(ctx context.Context, source string) ([]syntax.DID, []syntax.ATURI) { 37 - l := r.logger.With("method", "find_references") 38 rawMentions, rawRefs := markup.FindReferences(r.config.Core.AppviewHost, source) 39 l.Debug("found possible references", "mentions", rawMentions, "refs", rawRefs) 40 idents := r.idResolver.ResolveIdents(ctx, rawMentions) ··· 55 rawRef.Handle = string(ident.DID) 56 resolvedRefs = append(resolvedRefs, rawRef) 57 } 58 - aturiRefs, err := db.FindReferences(r.execer, resolvedRefs) 59 if err != nil { 60 l.Error("failed running query", "err", err) 61 }
··· 34 } 35 36 func (r *Resolver) Resolve(ctx context.Context, source string) ([]syntax.DID, []syntax.ATURI) { 37 + l := r.logger.With("method", "Resolve") 38 rawMentions, rawRefs := markup.FindReferences(r.config.Core.AppviewHost, source) 39 l.Debug("found possible references", "mentions", rawMentions, "refs", rawRefs) 40 idents := r.idResolver.ResolveIdents(ctx, rawMentions) ··· 55 rawRef.Handle = string(ident.DID) 56 resolvedRefs = append(resolvedRefs, rawRef) 57 } 58 + aturiRefs, err := db.ValidateReferenceLinks(r.execer, resolvedRefs) 59 if err != nil { 60 l.Error("failed running query", "err", err) 61 }
+14
lexicons/issue/comment.json
··· 29 "replyTo": { 30 "type": "string", 31 "format": "at-uri" 32 } 33 } 34 }
··· 29 "replyTo": { 30 "type": "string", 31 "format": "at-uri" 32 + }, 33 + "mentions": { 34 + "type": "array", 35 + "items": { 36 + "type": "string", 37 + "format": "did" 38 + } 39 + }, 40 + "references": { 41 + "type": "array", 42 + "items": { 43 + "type": "string", 44 + "format": "at-uri" 45 + } 46 } 47 } 48 }
+14
lexicons/issue/issue.json
··· 24 "createdAt": { 25 "type": "string", 26 "format": "datetime" 27 } 28 } 29 }
··· 24 "createdAt": { 25 "type": "string", 26 "format": "datetime" 27 + }, 28 + "mentions": { 29 + "type": "array", 30 + "items": { 31 + "type": "string", 32 + "format": "did" 33 + } 34 + }, 35 + "references": { 36 + "type": "array", 37 + "items": { 38 + "type": "string", 39 + "format": "at-uri" 40 + } 41 } 42 } 43 }
+14
lexicons/pulls/comment.json
··· 25 "createdAt": { 26 "type": "string", 27 "format": "datetime" 28 } 29 } 30 }
··· 25 "createdAt": { 26 "type": "string", 27 "format": "datetime" 28 + }, 29 + "mentions": { 30 + "type": "array", 31 + "items": { 32 + "type": "string", 33 + "format": "did" 34 + } 35 + }, 36 + "references": { 37 + "type": "array", 38 + "items": { 39 + "type": "string", 40 + "format": "at-uri" 41 + } 42 } 43 } 44 }