···
13
+
"github.com/bluesky-social/indigo/atproto/syntax"
14
+
"tangled.sh/tangled.sh/core/api/tangled"
17
+
type String struct {
28
+
func (s *String) StringAt() syntax.ATURI {
29
+
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", s.Did, tangled.StringNSID, s.Rkey))
32
+
type StringStats struct {
37
+
func (s String) Stats() StringStats {
38
+
lineCount, err := countLines(strings.NewReader(s.Contents))
45
+
LineCount: uint64(lineCount),
46
+
ByteCount: uint64(len(s.Contents)),
50
+
func (s String) Validate() error {
53
+
if !strings.Contains(s.Filename, ".") {
54
+
err = errors.Join(err, fmt.Errorf("missing filename extension"))
57
+
if strings.HasSuffix(s.Filename, ".") {
58
+
err = errors.Join(err, fmt.Errorf("filename ends with `.`"))
61
+
if utf8.RuneCountInString(s.Filename) > 140 {
62
+
err = errors.Join(err, fmt.Errorf("filename too long"))
65
+
if utf8.RuneCountInString(s.Description) > 280 {
66
+
err = errors.Join(err, fmt.Errorf("description too long"))
69
+
if len(s.Contents) == 0 {
70
+
err = errors.Join(err, fmt.Errorf("contents is empty"))
76
+
func (s *String) AsRecord() tangled.String {
77
+
return tangled.String{
78
+
Filename: s.Filename,
79
+
Description: s.Description,
80
+
Contents: s.Contents,
81
+
CreatedAt: s.Created.Format(time.RFC3339),
85
+
func StringFromRecord(did, rkey string, record tangled.String) String {
86
+
created, err := time.Parse(record.CreatedAt, time.RFC3339)
88
+
created = time.Now()
91
+
Did: syntax.DID(did),
93
+
Filename: record.Filename,
94
+
Description: record.Description,
95
+
Contents: record.Contents,
100
+
func AddString(e Execer, s String) error {
102
+
`insert into strings (
111
+
values (?, ?, ?, ?, ?, ?, null)
112
+
on conflict(did, rkey) do update set
113
+
filename = excluded.filename,
114
+
description = excluded.description,
115
+
content = excluded.content,
118
+
strings.content != excluded.content
119
+
or strings.filename != excluded.filename
120
+
or strings.description != excluded.description then ?
121
+
else strings.edited
128
+
s.Created.Format(time.RFC3339),
129
+
time.Now().Format(time.RFC3339),
134
+
func GetStrings(e Execer, filters ...filter) ([]String, error) {
137
+
var conditions []string
139
+
for _, filter := range filters {
140
+
conditions = append(conditions, filter.Condition())
141
+
args = append(args, filter.Arg()...)
145
+
if conditions != nil {
146
+
whereClause = " where " + strings.Join(conditions, " and ")
149
+
query := fmt.Sprintf(`select
161
+
rows, err := e.Query(query, args...)
170
+
var createdAt string
171
+
var editedAt sql.NullString
173
+
if err := rows.Scan(
185
+
s.Created, err = time.Parse(time.RFC3339, createdAt)
187
+
s.Created = time.Now()
190
+
if editedAt.Valid {
191
+
e, err := time.Parse(time.RFC3339, editedAt.String)
198
+
all = append(all, s)
201
+
if err := rows.Err(); err != nil {
208
+
func DeleteString(e Execer, filters ...filter) error {
209
+
var conditions []string
211
+
for _, filter := range filters {
212
+
conditions = append(conditions, filter.Condition())
213
+
args = append(args, filter.Arg()...)
217
+
if conditions != nil {
218
+
whereClause = " where " + strings.Join(conditions, " and ")
221
+
query := fmt.Sprintf(`delete from strings %s`, whereClause)
223
+
_, err := e.Exec(query, args...)
227
+
func countLines(r io.Reader) (int, error) {
228
+
buf := make([]byte, 32*1024)
234
+
c, err := r.Read(buf)
238
+
count += bytes.Count(buf[:c], nl)
241
+
case err == io.EOF:
242
+
/* handle last line not having a newline at the end */
243
+
if bufLen >= 1 && buf[(bufLen-1)%(32*1024)] != '\n' {