Eric Bower
·
13 Dec 24
db.go
1package db
2
3import (
4 "database/sql"
5 "database/sql/driver"
6 "encoding/json"
7 "errors"
8 "regexp"
9 "time"
10)
11
12var ErrNameTaken = errors.New("username has already been claimed")
13var ErrNameDenied = errors.New("username is on the denylist")
14var ErrNameInvalid = errors.New("username has invalid characters in it")
15var ErrPublicKeyTaken = errors.New("public key is already associated with another user")
16
17type PublicKey struct {
18 ID string `json:"id"`
19 UserID string `json:"user_id"`
20 Name string `json:"name"`
21 Key string `json:"key"`
22 CreatedAt *time.Time `json:"created_at"`
23}
24
25type User struct {
26 ID string `json:"id"`
27 Name string `json:"name"`
28 PublicKey *PublicKey `json:"public_key,omitempty"`
29 CreatedAt *time.Time `json:"created_at"`
30}
31
32type PostData struct {
33 ImgPath string `json:"img_path"`
34 LastDigest *time.Time `json:"last_digest"`
35 Attempts int `json:"attempts"`
36}
37
38// Make the Attrs struct implement the driver.Valuer interface. This method
39// simply returns the JSON-encoded representation of the struct.
40func (p PostData) Value() (driver.Value, error) {
41 return json.Marshal(p)
42}
43
44// Make the Attrs struct implement the sql.Scanner interface. This method
45// simply decodes a JSON-encoded value into the struct fields.
46func (p *PostData) Scan(value interface{}) error {
47 b, ok := value.([]byte)
48 if !ok {
49 return errors.New("type assertion to []byte failed")
50 }
51
52 return json.Unmarshal(b, &p)
53}
54
55type Project struct {
56 ID string `json:"id"`
57 UserID string `json:"user_id"`
58 Name string `json:"name"`
59 ProjectDir string `json:"project_dir"`
60 Username string `json:"username"`
61 Acl ProjectAcl `json:"acl"`
62 Blocked string `json:"blocked"`
63 CreatedAt *time.Time `json:"created_at"`
64 UpdatedAt *time.Time `json:"updated_at"`
65}
66
67type ProjectAcl struct {
68 Type string `json:"type"`
69 Data []string `json:"data"`
70}
71
72// Make the Attrs struct implement the driver.Valuer interface. This method
73// simply returns the JSON-encoded representation of the struct.
74func (p ProjectAcl) Value() (driver.Value, error) {
75 return json.Marshal(p)
76}
77
78// Make the Attrs struct implement the sql.Scanner interface. This method
79// simply decodes a JSON-encoded value into the struct fields.
80func (p *ProjectAcl) Scan(value interface{}) error {
81 b, ok := value.([]byte)
82 if !ok {
83 return errors.New("type assertion to []byte failed")
84 }
85
86 return json.Unmarshal(b, &p)
87}
88
89type FeedItemData struct {
90 Title string `json:"title"`
91 Description string `json:"description"`
92 Content string `json:"content"`
93 Link string `json:"link"`
94 PublishedAt *time.Time `json:"published_at"`
95}
96
97// Make the Attrs struct implement the driver.Valuer interface. This method
98// simply returns the JSON-encoded representation of the struct.
99func (p FeedItemData) Value() (driver.Value, error) {
100 return json.Marshal(p)
101}
102
103// Make the Attrs struct implement the sql.Scanner interface. This method
104// simply decodes a JSON-encoded value into the struct fields.
105func (p *FeedItemData) Scan(value interface{}) error {
106 b, ok := value.([]byte)
107 if !ok {
108 return errors.New("type assertion to []byte failed")
109 }
110 return json.Unmarshal(b, &p)
111}
112
113type Post struct {
114 ID string `json:"id"`
115 UserID string `json:"user_id"`
116 Filename string `json:"filename"`
117 Slug string `json:"slug"`
118 Title string `json:"title"`
119 Text string `json:"text"`
120 Description string `json:"description"`
121 CreatedAt *time.Time `json:"created_at"`
122 PublishAt *time.Time `json:"publish_at"`
123 Username string `json:"username"`
124 UpdatedAt *time.Time `json:"updated_at"`
125 ExpiresAt *time.Time `json:"expires_at"`
126 Hidden bool `json:"hidden"`
127 Views int `json:"views"`
128 Space string `json:"space"`
129 Shasum string `json:"shasum"`
130 FileSize int `json:"file_size"`
131 MimeType string `json:"mime_type"`
132 Data PostData `json:"data"`
133 Tags []string `json:"tags"`
134}
135
136type Paginate[T any] struct {
137 Data []T
138 Total int
139}
140
141type Analytics struct {
142 TotalUsers int
143 UsersLastMonth int
144 TotalPosts int
145 PostsLastMonth int
146 UsersWithPost int
147}
148
149type VisitInterval struct {
150 Interval *time.Time `json:"interval"`
151 Visitors int `json:"visitors"`
152}
153
154type VisitUrl struct {
155 Url string `json:"url"`
156 Count int `json:"count"`
157}
158
159type SummaryOpts struct {
160 Interval string
161 Origin time.Time
162 Host string
163 Path string
164 UserID string
165}
166
167type SummaryVisits struct {
168 Intervals []*VisitInterval `json:"intervals"`
169 TopUrls []*VisitUrl `json:"top_urls"`
170 NotFoundUrls []*VisitUrl `json:"not_found_urls"`
171 TopReferers []*VisitUrl `json:"top_referers"`
172}
173
174type PostAnalytics struct {
175 ID string
176 PostID string
177 Views int
178 UpdateAt *time.Time
179}
180
181type AnalyticsVisits struct {
182 ID string `json:"id"`
183 UserID string `json:"user_id"`
184 ProjectID string `json:"project_id"`
185 PostID string `json:"post_id"`
186 Namespace string `json:"namespace"`
187 Host string `json:"host"`
188 Path string `json:"path"`
189 IpAddress string `json:"ip_address"`
190 UserAgent string `json:"user_agent"`
191 Referer string `json:"referer"`
192 Status int `json:"status"`
193 ContentType string `json:"content_type"`
194}
195
196type Pager struct {
197 Num int
198 Page int
199}
200
201type FeedItem struct {
202 ID string
203 PostID string
204 GUID string
205 Data FeedItemData
206 CreatedAt *time.Time
207}
208
209type Token struct {
210 ID string `json:"id"`
211 UserID string `json:"user_id"`
212 Name string `json:"name"`
213 CreatedAt *time.Time `json:"created_at"`
214 ExpiresAt *time.Time `json:"expires_at"`
215}
216
217type FeatureFlag struct {
218 ID string `json:"id"`
219 UserID string `json:"user_id"`
220 PaymentHistoryID string `json:"payment_history_id"`
221 Name string `json:"name"`
222 CreatedAt *time.Time `json:"created_at"`
223 ExpiresAt *time.Time `json:"expires_at"`
224 Data FeatureFlagData `json:"data"`
225}
226
227func NewFeatureFlag(userID, name string, storageMax uint64, fileMax int64, specialFileMax int64) *FeatureFlag {
228 return &FeatureFlag{
229 UserID: userID,
230 Name: name,
231 Data: FeatureFlagData{
232 StorageMax: storageMax,
233 FileMax: fileMax,
234 SpecialFileMax: specialFileMax,
235 },
236 }
237}
238
239func (ff *FeatureFlag) FindStorageMax(defaultSize uint64) uint64 {
240 if ff.Data.StorageMax == 0 {
241 return defaultSize
242 }
243 return ff.Data.StorageMax
244}
245
246func (ff *FeatureFlag) FindFileMax(defaultSize int64) int64 {
247 if ff.Data.FileMax == 0 {
248 return defaultSize
249 }
250 return ff.Data.FileMax
251}
252
253func (ff *FeatureFlag) FindSpecialFileMax(defaultSize int64) int64 {
254 if ff.Data.SpecialFileMax == 0 {
255 return defaultSize
256 }
257 return ff.Data.SpecialFileMax
258}
259
260func (ff *FeatureFlag) IsValid() bool {
261 if ff.ExpiresAt.IsZero() {
262 return false
263 }
264 return ff.ExpiresAt.After(time.Now())
265}
266
267type FeatureFlagData struct {
268 StorageMax uint64 `json:"storage_max"`
269 FileMax int64 `json:"file_max"`
270 SpecialFileMax int64 `json:"special_file_max"`
271}
272
273// Make the Attrs struct implement the driver.Valuer interface. This method
274// simply returns the JSON-encoded representation of the struct.
275func (p FeatureFlagData) Value() (driver.Value, error) {
276 return json.Marshal(p)
277}
278
279// Make the Attrs struct implement the sql.Scanner interface. This method
280// simply decodes a JSON-encoded value into the struct fields.
281func (p *FeatureFlagData) Scan(value interface{}) error {
282 b, ok := value.([]byte)
283 if !ok {
284 return errors.New("type assertion to []byte failed")
285 }
286 return json.Unmarshal(b, &p)
287}
288
289type PaymentHistoryData struct {
290 Notes string `json:"notes"`
291 TxID string `json:"tx_id"`
292}
293
294// Make the Attrs struct implement the driver.Valuer interface. This method
295// simply returns the JSON-encoded representation of the struct.
296func (p PaymentHistoryData) Value() (driver.Value, error) {
297 return json.Marshal(p)
298}
299
300// Make the Attrs struct implement the sql.Scanner interface. This method
301// simply decodes a JSON-encoded value into the struct fields.
302func (p *PaymentHistoryData) Scan(value interface{}) error {
303 b, ok := value.([]byte)
304 if !ok {
305 return errors.New("type assertion to []byte failed")
306 }
307 return json.Unmarshal(b, &p)
308}
309
310type ErrMultiplePublicKeys struct{}
311
312func (m *ErrMultiplePublicKeys) Error() string {
313 return "there are multiple users with this public key, you must provide the username when using SSH: `ssh <user>@<domain>`\n"
314}
315
316var NameValidator = regexp.MustCompile("^[a-zA-Z0-9]{1,50}$")
317var DenyList = []string{
318 "admin",
319 "abuse",
320 "cgi",
321 "ops",
322 "help",
323 "spec",
324 "root",
325 "new",
326 "create",
327 "www",
328 "public",
329}
330
331type DB interface {
332 RegisterUser(name, pubkey, comment string) (*User, error)
333 RemoveUsers(userIDs []string) error
334 UpdatePublicKey(pubkeyID, name string) (*PublicKey, error)
335 InsertPublicKey(userID, pubkey, name string, tx *sql.Tx) error
336 FindPublicKeyForKey(pubkey string) (*PublicKey, error)
337 FindPublicKey(pubkeyID string) (*PublicKey, error)
338 FindKeysForUser(user *User) ([]*PublicKey, error)
339 RemoveKeys(pubkeyIDs []string) error
340
341 FindSiteAnalytics(space string) (*Analytics, error)
342
343 FindUsers() ([]*User, error)
344 FindUserForName(name string) (*User, error)
345 FindUserForNameAndKey(name string, pubkey string) (*User, error)
346 FindUserForKey(name string, pubkey string) (*User, error)
347 FindUser(userID string) (*User, error)
348 ValidateName(name string) (bool, error)
349 SetUserName(userID string, name string) error
350
351 FindUserForToken(token string) (*User, error)
352 FindTokensForUser(userID string) ([]*Token, error)
353 InsertToken(userID, name string) (string, error)
354 UpsertToken(userID, name string) (string, error)
355 FindTokenByName(userID, name string) (string, error)
356 RemoveToken(tokenID string) error
357
358 FindPosts() ([]*Post, error)
359 FindPost(postID string) (*Post, error)
360 FindPostsForUser(pager *Pager, userID string, space string) (*Paginate[*Post], error)
361 FindAllPostsForUser(userID string, space string) ([]*Post, error)
362 FindPostsBeforeDate(date *time.Time, space string) ([]*Post, error)
363 FindExpiredPosts(space string) ([]*Post, error)
364 FindUpdatedPostsForUser(userID string, space string) ([]*Post, error)
365 FindPostWithFilename(filename string, userID string, space string) (*Post, error)
366 FindPostWithSlug(slug string, userID string, space string) (*Post, error)
367 FindAllPosts(pager *Pager, space string) (*Paginate[*Post], error)
368 FindAllUpdatedPosts(pager *Pager, space string) (*Paginate[*Post], error)
369 InsertPost(post *Post) (*Post, error)
370 UpdatePost(post *Post) (*Post, error)
371 RemovePosts(postIDs []string) error
372
373 ReplaceTagsForPost(tags []string, postID string) error
374 FindUserPostsByTag(pager *Pager, tag, userID, space string) (*Paginate[*Post], error)
375 FindPostsByTag(pager *Pager, tag, space string) (*Paginate[*Post], error)
376 FindPopularTags(space string) ([]string, error)
377 FindTagsForPost(postID string) ([]string, error)
378
379 ReplaceAliasesForPost(aliases []string, postID string) error
380
381 InsertVisit(view *AnalyticsVisits) error
382 VisitSummary(opts *SummaryOpts) (*SummaryVisits, error)
383 FindVisitSiteList(opts *SummaryOpts) ([]*VisitUrl, error)
384
385 AddPicoPlusUser(username, email, paymentType, txId string) error
386 FindFeatureForUser(userID string, feature string) (*FeatureFlag, error)
387 FindFeaturesForUser(userID string) ([]*FeatureFlag, error)
388 HasFeatureForUser(userID string, feature string) bool
389 FindTotalSizeForUser(userID string) (int, error)
390 InsertFeature(userID, name string, expiresAt time.Time) (*FeatureFlag, error)
391 RemoveFeature(userID, names string) error
392
393 InsertFeedItems(postID string, items []*FeedItem) error
394 FindFeedItemsByPostID(postID string) ([]*FeedItem, error)
395
396 InsertProject(userID, name, projectDir string) (string, error)
397 UpdateProject(userID, name string) error
398 UpdateProjectAcl(userID, name string, acl ProjectAcl) error
399 LinkToProject(userID, projectID, projectDir string, commit bool) error
400 RemoveProject(projectID string) error
401 FindProjectByName(userID, name string) (*Project, error)
402 FindProjectLinks(userID, name string) ([]*Project, error)
403 FindProjectsByUser(userID string) ([]*Project, error)
404 FindProjectsByPrefix(userID, name string) ([]*Project, error)
405 FindAllProjects(page *Pager, by string) (*Paginate[*Project], error)
406
407 Close() error
408}