internal/store/releases.go
147 lines · 3695 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
63// UpdateRelease replaces a release's title and notes.
64func (s *Store) UpdateRelease(repoID int64, tag, title, notes string) error {
65 res, err := s.DB.Exec(
66 "UPDATE releases SET title = ?, notes = ? WHERE repo_id = ? AND tag = ?",
67 title, notes, repoID, tag)
68 if err != nil {
69 return err
70 }
71 if n, _ := res.RowsAffected(); n == 0 {
72 return ErrNotFound
73 }
74 return nil
75}
76
77func (s *Store) ReleaseByTag(repoID int64, tag string) (Release, error) {
78 var r Release
79 err := s.DB.QueryRow(releaseSelect+" WHERE r.repo_id = ? AND r.tag = ?", repoID, tag).
80 Scan(&r.ID, &r.RepoID, &r.Tag, &r.Title, &r.Notes, &r.Author, &r.CreatedAt)
81 if errors.Is(err, sql.ErrNoRows) {
82 return r, ErrNotFound
83 }
84 if err != nil {
85 return r, err
86 }
87 return r, s.releaseAssets(&r)
88}
89
90// ListReleases returns releases newest-first, assets included.
91func (s *Store) ListReleases(repoID int64) ([]Release, error) {
92 rows, err := s.DB.Query(releaseSelect+" WHERE r.repo_id = ? ORDER BY r.created_at DESC", repoID)
93 if err != nil {
94 return nil, err
95 }
96 defer rows.Close()
97 var out []Release
98 for rows.Next() {
99 var r Release
100 if err := rows.Scan(&r.ID, &r.RepoID, &r.Tag, &r.Title, &r.Notes, &r.Author, &r.CreatedAt); err != nil {
101 return nil, err
102 }
103 out = append(out, r)
104 }
105 if err := rows.Err(); err != nil {
106 return nil, err
107 }
108 for i := range out {
109 if err := s.releaseAssets(&out[i]); err != nil {
110 return nil, err
111 }
112 }
113 return out, nil
114}
115
116func (s *Store) DeleteRelease(id int64) error {
117 res, err := s.DB.Exec("DELETE FROM releases WHERE id = ?", id)
118 if err != nil {
119 return err
120 }
121 if n, _ := res.RowsAffected(); n == 0 {
122 return ErrNotFound
123 }
124 return nil
125}
126
127func (s *Store) AddReleaseAsset(releaseID int64, name string, size int64, sha256 string) error {
128 _, err := s.DB.Exec(
129 "INSERT INTO release_assets (release_id, name, size, sha256) VALUES (?, ?, ?, ?)",
130 releaseID, name, size, sha256)
131 if isUniqueErr(err) {
132 return fmt.Errorf("asset %q already exists on this release", name)
133 }
134 return err
135}
136
137func (s *Store) RemoveReleaseAsset(releaseID int64, name string) error {
138 res, err := s.DB.Exec(
139 "DELETE FROM release_assets WHERE release_id = ? AND name = ?", releaseID, name)
140 if err != nil {
141 return err
142 }
143 if n, _ := res.RowsAffected(); n == 0 {
144 return ErrNotFound
145 }
146 return nil
147}