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