Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Go
at sl/md-editor
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377package db
import ( "database/sql" "errors" "fmt" "slices" "strings" "time"
"github.com/bluesky-social/indigo/atproto/syntax" lexutil "github.com/bluesky-social/indigo/lex/util" "github.com/ipfs/go-cid" "tangled.org/core/appview/models" "tangled.org/core/orm")
func AddString(d *DB, s models.String) error { tx, err := d.Begin() if err != nil { return fmt.Errorf("starting transaction: %w", err) } defer tx.Rollback() res, err := tx.Exec( `insert into strings ( did, rkey, cid, title, description, file_name, file_content, created ) values (?, ?, ?, ?, ?, ?, ?, ?) on conflict(did, rkey) do update set cid = excluded.cid, title = excluded.title, description = excluded.description, file_name = excluded.file_name, file_content= excluded.file_content, created = excluded.created, edited = case when strings.cid is not null then ? else strings.edited end where strings.cid is not excluded.cid`, s.Did, s.Rkey, s.Cid, s.Title, s.Description, s.FileName, s.FileContent, s.Created.Format(time.RFC3339), time.Now().Format(time.RFC3339), ) if err != nil { return fmt.Errorf("inserting string: %w", err) }
num, err := res.RowsAffected() if err != nil { return fmt.Errorf("calculating affected rows: %w", err) } if num == 0 { return nil }
_, err = tx.Exec(`delete from string_files where at_uri = ?`, s.AtUri()) if err != nil { return fmt.Errorf("deleting old files: %w", err) }
// legacy single-file strings carry their content in file_name/file_content // and have no rows in string_files; nothing more to insert. if len(s.Files) == 0 { if err := tx.Commit(); err != nil { return fmt.Errorf("commiting transaction: %w", err) } return nil }
vals := make([]string, len(s.Files)) args := make([]any, 0, len(s.Files)*8) for i, file := range s.Files { vals[i] = "(?, ?, ?, ?, ?)" args = append(args, s.AtUri(), file.Name, file.Content.Ref.String(), file.Content.Size, file.Content.MimeType, ) } _, err = tx.Exec( fmt.Sprintf( `insert into string_files ( at_uri, name, content_ref, content_size, content_mimetype ) values %s`, strings.Join(vals, ","), ), args..., ) if err != nil { return fmt.Errorf("inserting files: %w", err) }
if err := tx.Commit(); err != nil { return fmt.Errorf("commiting transaction: %w", err) } return nil}
func GetString(e Execer, filters ...orm.Filter) (models.String, error) { strings, err := GetStrings(e, 0, filters...) if err != nil { return models.String{}, err } if len(strings) != 1 { return models.String{}, sql.ErrNoRows } return strings[0], nil}
func GetStrings(e Execer, limit int, filters ...orm.Filter) ([]models.String, error) { stringMap := make(map[syntax.ATURI]*models.String)
var conditions []string var args []any for _, filter := range filters { conditions = append(conditions, filter.Condition()) args = append(args, filter.Arg()...) }
whereClause := "" if conditions != nil { whereClause = " where " + strings.Join(conditions, " and ") }
limitClause := "" if limit != 0 { limitClause = fmt.Sprintf(" limit %d ", limit) }
query := fmt.Sprintf( `select did, rkey, cid, title, description, file_name, file_content, created, edited from strings %s order by created desc %s`, whereClause, limitClause, )
rows, err := e.Query(query, args...)
if err != nil { return nil, err } defer rows.Close()
for rows.Next() { var s models.String var createdAt string var cid, title, description, editedAt sql.Null[string]
if err := rows.Scan( &s.Did, &s.Rkey, &cid, &title, &description, &s.FileName, &s.FileContent, &createdAt, &editedAt, ); err != nil { return nil, err }
if cid.Valid { s.Cid = new(syntax.CID) *s.Cid = syntax.CID(cid.V) }
if title.Valid { s.Title = new(string) *s.Title = title.V }
if description.Valid { s.Description = new(string) *s.Description = description.V }
s.Created, err = time.Parse(time.RFC3339, createdAt) if err != nil { s.Created = time.Now() }
if editedAt.Valid { e, err := time.Parse(time.RFC3339, editedAt.V) if err != nil { e = time.Now() } s.Edited = &e }
s.Stats = &models.StringStats{} stringMap[s.AtUri()] = &s }
if err := rows.Err(); err != nil { return nil, err }
// if no strings, return early if len(stringMap) == 0 { return nil, nil }
// build IN clause for related queries inClause := strings.TrimSuffix(strings.Repeat("?, ", len(stringMap)), ", ") args = make([]any, len(stringMap)) i := 0 for _, s := range stringMap { args[i] = s.AtUri() i++ }
// get files { rows, err := e.Query( fmt.Sprintf( `select at_uri, name, content_ref, content_size, content_mimetype from string_files where at_uri in (%s) order by at_uri, id`, inClause, ), args..., ) if err != nil { return nil, fmt.Errorf("failed to execute string_files query: %w", err) } defer rows.Close()
for rows.Next() { var stringAt syntax.ATURI var file models.String_File
var contentRef string if err := rows.Scan( &stringAt, &file.Name, &contentRef, &file.Content.Size, &file.Content.MimeType, ); err != nil { return nil, fmt.Errorf("failed to execute string_files query: %w", err) }
file.Content.Ref = lexutil.LexLink(cid.MustParse(contentRef))
if s, ok := stringMap[stringAt]; ok { s.Files = append(s.Files, file) } } if err = rows.Err(); err != nil { return nil, fmt.Errorf("failed to execute string_files query: %w", err) } }
// get star counts { rows, err := e.Query( fmt.Sprintf( `select subject, count(1) from stars where subject_type = 'string' and subject in (%s) group by subject`, inClause, ), args..., ) if err != nil { return nil, fmt.Errorf("failed to execute star-count query: %w", err) } defer rows.Close()
for rows.Next() { var stringAt syntax.ATURI var count int if err := rows.Scan(&stringAt, &count); err != nil { continue } if s, ok := stringMap[stringAt]; ok { s.Stats.StarCount = count } } if err = rows.Err(); err != nil { return nil, fmt.Errorf("failed to execute star-count query: %w", err) } }
var all []models.String for _, s := range stringMap { all = append(all, *s) }
// sort by created timestamp (desc) slices.SortFunc(all, func(a, b models.String) int { if a.Created.After(b.Created) { return -1 } return 1 })
return all, nil}
func CountStrings(e Execer, filters ...orm.Filter) (int64, error) { var conditions []string var args []any for _, filter := range filters { conditions = append(conditions, filter.Condition()) args = append(args, filter.Arg()...) }
whereClause := "" if conditions != nil { whereClause = " where " + strings.Join(conditions, " and ") }
repoQuery := fmt.Sprintf(`select count(1) from strings %s`, whereClause) var count int64 err := e.QueryRow(repoQuery, args...).Scan(&count)
if !errors.Is(err, sql.ErrNoRows) && err != nil { return 0, err }
return count, nil}
func DeleteString(e Execer, filters ...orm.Filter) error { var conditions []string var args []any for _, filter := range filters { conditions = append(conditions, filter.Condition()) args = append(args, filter.Arg()...) }
whereClause := "" if conditions != nil { whereClause = " where " + strings.Join(conditions, " and ") }
query := fmt.Sprintf(`delete from strings %s`, whereClause)
_, err := e.Exec(query, args...) return err}