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