internal/store/releases.go
133 lines · 3320 bytes
1package store
2
3import (
4 "database/sql"
5 "errors"
6 "fmt"
7)
8
9type Release struct {
10 ID int64
11 RepoID int64
12 Tag string
13 Title string
14 Notes string
15 Author string
16 CreatedAt string
17 Assets []ReleaseAsset
18}
19
20type ReleaseAsset struct {
21 ID int64
22 Name string
23 Size int64
24 SHA256 string
25 UploadedAt string
26}
27
28func (s *Store) CreateRelease(repoID int64, tag, title, notes string, authorID int64) (int64, error) {
29 res, err := s.DB.Exec(
30 "INSERT INTO releases (repo_id, tag, title, notes, author_id) VALUES (?, ?, ?, ?, ?)",
31 repoID, tag, title, notes, authorID)
32 if err != nil {
33 if isUniqueErr(err) {
34 return 0, fmt.Errorf("a release for tag %q already exists", tag)
35 }
36 return 0, err
37 }
38 return res.LastInsertId()
39}
40
41const releaseSelect = `
42 SELECT r.id, r.repo_id, r.tag, r.title, r.notes, COALESCE(u.username, ''), r.created_at
43 FROM releases r LEFT JOIN users u ON u.id = r.author_id`
44
45func (s *Store) releaseAssets(rel *Release) error {
46 rows, err := s.DB.Query(
47 "SELECT id, name, size, sha256, uploaded_at FROM release_assets WHERE release_id = ? ORDER BY name",
48 rel.ID)
49 if err != nil {
50 return err
51 }
52 defer rows.Close()
53 for rows.Next() {
54 var a ReleaseAsset
55 if err := rows.Scan(&a.ID, &a.Name, &a.Size, &a.SHA256, &a.UploadedAt); err != nil {
56 return err
57 }
58 rel.Assets = append(rel.Assets, a)
59 }
60 return rows.Err()
61}
62
63func (s *Store) ReleaseByTag(repoID int64, tag string) (Release, error) {
64 var r Release
65 err := s.DB.QueryRow(releaseSelect+" WHERE r.repo_id = ? AND r.tag = ?", repoID, tag).
66 Scan(&r.ID, &r.RepoID, &r.Tag, &r.Title, &r.Notes, &r.Author, &r.CreatedAt)
67 if errors.Is(err, sql.ErrNoRows) {
68 return r, ErrNotFound
69 }
70 if err != nil {
71 return r, err
72 }
73 return r, s.releaseAssets(&r)
74}
75
76// ListReleases returns releases newest-first, assets included.
77func (s *Store) ListReleases(repoID int64) ([]Release, error) {
78 rows, err := s.DB.Query(releaseSelect+" WHERE r.repo_id = ? ORDER BY r.created_at DESC", repoID)
79 if err != nil {
80 return nil, err
81 }
82 defer rows.Close()
83 var out []Release
84 for rows.Next() {
85 var r Release
86 if err := rows.Scan(&r.ID, &r.RepoID, &r.Tag, &r.Title, &r.Notes, &r.Author, &r.CreatedAt); err != nil {
87 return nil, err
88 }
89 out = append(out, r)
90 }
91 if err := rows.Err(); err != nil {
92 return nil, err
93 }
94 for i := range out {
95 if err := s.releaseAssets(&out[i]); err != nil {
96 return nil, err
97 }
98 }
99 return out, nil
100}
101
102func (s *Store) DeleteRelease(id int64) error {
103 res, err := s.DB.Exec("DELETE FROM releases WHERE id = ?", id)
104 if err != nil {
105 return err
106 }
107 if n, _ := res.RowsAffected(); n == 0 {
108 return ErrNotFound
109 }
110 return nil
111}
112
113func (s *Store) AddReleaseAsset(releaseID int64, name string, size int64, sha256 string) error {
114 _, err := s.DB.Exec(
115 "INSERT INTO release_assets (release_id, name, size, sha256) VALUES (?, ?, ?, ?)",
116 releaseID, name, size, sha256)
117 if isUniqueErr(err) {
118 return fmt.Errorf("asset %q already exists on this release", name)
119 }
120 return err
121}
122
123func (s *Store) RemoveReleaseAsset(releaseID int64, name string) error {
124 res, err := s.DB.Exec(
125 "DELETE FROM release_assets WHERE release_id = ? AND name = ?", releaseID, name)
126 if err != nil {
127 return err
128 }
129 if n, _ := res.RowsAffected(); n == 0 {
130 return ErrNotFound
131 }
132 return nil
133}