1 package database
2
3 import (
4 "context"
5 "log"
6 "strings"
7
8 . "codeberg.org/tslocum/sriracha/model"
9 "github.com/jackc/pgx/v5"
10 )
11
12
13 var boardCache []*Board
14
15 func (db *DB) setBoardAttributes(b *Board) {
16 rows, err := db.conn.Query(context.Background(), "SELECT upload FROM board_upload WHERE board = $1", b.ID)
17 if err != nil {
18 log.Fatalf("failed to select board uploads: %s", err)
19 }
20 b.Uploads = nil
21 for rows.Next() {
22 var mimeType string
23 err := rows.Scan(&mimeType)
24 if err != nil {
25 log.Fatalf("failed to select board uploads: %s", err)
26 }
27 for _, u := range db.config.UploadTypes() {
28 if u.MIME == mimeType {
29 b.Uploads = append(b.Uploads, u.MIME)
30 break
31 }
32 }
33 }
34
35 rows, err = db.conn.Query(context.Background(), "SELECT embed FROM board_embed WHERE board = $1", b.ID)
36 if err != nil {
37 log.Fatalf("failed to select board embeds: %s", err)
38 }
39 b.Embeds = nil
40 for rows.Next() {
41 var name string
42 err := rows.Scan(&name)
43 if err != nil {
44 log.Fatalf("failed to select board embeds: %s", err)
45 }
46 b.Embeds = append(b.Embeds, name)
47 }
48 }
49
50 func (db *DB) AddBoard(b *Board) {
51 boardCache = nil
52
53 var reports int
54 if b.Reports {
55 reports = 1
56 }
57 var oekaki int
58 if b.Oekaki {
59 oekaki = 1
60 }
61 var backlinks int
62 if b.Backlinks {
63 backlinks = 1
64 }
65 var gallery int
66 if b.Gallery {
67 gallery = 1
68 }
69 _, err := db.conn.Exec(context.Background(), "INSERT INTO board VALUES (DEFAULT, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39)",
70 b.Dir,
71 b.Name,
72 b.Description,
73 b.Type,
74 b.Lock,
75 b.Approval,
76 reports,
77 b.Style,
78 b.Locale,
79 b.Delay,
80 b.MinName,
81 b.MaxName,
82 b.MinEmail,
83 b.MaxEmail,
84 b.MinSubject,
85 b.MaxSubject,
86 b.MinMessage,
87 b.MaxMessage,
88 b.MinSizeThread,
89 b.MaxSizeThread,
90 b.MinSizeReply,
91 b.MaxSizeReply,
92 b.ThumbWidth,
93 b.ThumbHeight,
94 b.DefaultName,
95 b.WordBreak,
96 b.Truncate,
97 b.Threads,
98 b.Replies,
99 b.MaxThreads,
100 b.MaxReplies,
101 oekaki,
102 strings.Join(b.Rules, "|||"),
103 b.Hide,
104 backlinks,
105 b.Instances,
106 b.Identifiers,
107 b.Files,
108 gallery,
109 )
110 if err != nil {
111 log.Fatalf("failed to insert board: %s", err)
112 }
113 err = db.conn.QueryRow(context.Background(), "SELECT id FROM board WHERE dir = $1", b.Dir).Scan(&b.ID)
114 if err != nil || b.ID == 0 {
115 log.Fatalf("failed to select id of inserted board: %s", err)
116 }
117 for _, upload := range b.Uploads {
118 _, err := db.conn.Exec(context.Background(), "INSERT INTO board_upload VALUES ($1, $2)", b.ID, upload)
119 if err != nil {
120 log.Fatalf("failed to insert board uploads: %s", err)
121 }
122 }
123 for _, embed := range b.Embeds {
124 _, err := db.conn.Exec(context.Background(), "INSERT INTO board_embed VALUES ($1, $2)", b.ID, embed)
125 if err != nil {
126 log.Fatalf("failed to insert board embeds: %s", err)
127 }
128 }
129 }
130
131 func (db *DB) AllBoards() []*Board {
132 if boardCache != nil {
133 return boardCache
134 }
135 boardCache = []*Board{}
136
137 rows, err := db.conn.Query(context.Background(), "SELECT * FROM board ORDER BY dir ASC")
138 if err != nil {
139 log.Fatalf("failed to select all boards: %s", err)
140 }
141 for rows.Next() {
142 b := &Board{}
143 err := scanBoard(b, rows)
144 if err != nil {
145 log.Fatalf("failed to select all boards: %s", err)
146 }
147 boardCache = append(boardCache, b)
148 }
149 for _, b := range boardCache {
150 db.setBoardAttributes(b)
151 }
152
153 return boardCache
154 }
155
156 func (db *DB) BoardByID(id int) *Board {
157
158 for _, b := range db.AllBoards() {
159 if b.ID == id {
160 return b
161 }
162 }
163 return nil
164 }
165
166 func (db *DB) BoardByDir(dir string) *Board {
167
168 for _, b := range db.AllBoards() {
169 if b.Dir == dir {
170 return b
171 }
172 }
173 return nil
174 }
175
176 func (db *DB) UniqueUserPosts(b *Board) int {
177 var count int
178 var err error
179 if b == nil {
180 err = db.conn.QueryRow(context.Background(), "SELECT COUNT(DISTINCT ip) FROM post").Scan(&count)
181 } else {
182 err = db.conn.QueryRow(context.Background(), "SELECT COUNT(DISTINCT ip) FROM post WHERE board = $1", b.ID).Scan(&count)
183 }
184 if err == pgx.ErrNoRows {
185 return 0
186 } else if err != nil {
187 log.Fatalf("failed to select unique user posts: %s", err)
188 }
189 return count
190 }
191
192 func (db *DB) UpdateBoard(b *Board) {
193 if b.ID <= 0 {
194 log.Fatalf("invalid board ID %d", b.ID)
195 }
196 boardCache = nil
197
198 var reports int
199 if b.Reports {
200 reports = 1
201 }
202 var oekaki int
203 if b.Oekaki {
204 oekaki = 1
205 }
206 var backlinks int
207 if b.Backlinks {
208 backlinks = 1
209 }
210 var gallery int
211 if b.Gallery {
212 gallery = 1
213 }
214 _, err := db.conn.Exec(context.Background(), "UPDATE board SET dir = $1, name = $2, description = $3, type = $4, lock = $5, approval = $6, reports = $7, style = $8, locale = $9, delay = $10, minname = $11, maxname = $12, minemail = $13, maxemail = $14, minsubject = $15, maxsubject = $16, minmessage = $17, maxmessage = $18, minsizethread = $19, maxsizethread = $20, minsizereply = $21, maxsizereply = $22, thumbwidth = $23, thumbheight = $24, defaultname = $25, wordbreak = $26, truncate = $27, threads = $28, replies = $29, maxthreads = $30, maxreplies = $31, oekaki = $32, rules = $33, hide = $34, backlinks = $35, instances = $36, identifiers = $37, files = $38, gallery = $39 WHERE id = $40",
215 b.Dir,
216 b.Name,
217 b.Description,
218 b.Type,
219 b.Lock,
220 b.Approval,
221 reports,
222 b.Style,
223 b.Locale,
224 b.Delay,
225 b.MinName,
226 b.MaxName,
227 b.MinEmail,
228 b.MaxEmail,
229 b.MinSubject,
230 b.MaxSubject,
231 b.MinMessage,
232 b.MaxMessage,
233 b.MinSizeThread,
234 b.MaxSizeThread,
235 b.MinSizeReply,
236 b.MaxSizeReply,
237 b.ThumbWidth,
238 b.ThumbHeight,
239 b.DefaultName,
240 b.WordBreak,
241 b.Truncate,
242 b.Threads,
243 b.Replies,
244 b.MaxThreads,
245 b.MaxReplies,
246 oekaki,
247 strings.Join(b.Rules, "|||"),
248 b.Hide,
249 backlinks,
250 b.Instances,
251 b.Identifiers,
252 b.Files,
253 gallery,
254 b.ID,
255 )
256 if err != nil {
257 log.Fatalf("failed to update board: %s", err)
258 }
259
260 _, err = db.conn.Exec(context.Background(), "DELETE FROM board_upload WHERE board = $1", b.ID)
261 if err != nil {
262 log.Fatalf("failed to delete board uploads: %s", err)
263 }
264 for _, upload := range b.Uploads {
265 _, err := db.conn.Exec(context.Background(), "INSERT INTO board_upload VALUES ($1, $2)", b.ID, upload)
266 if err != nil {
267 log.Fatalf("failed to insert board uploads: %s", err)
268 }
269 }
270
271 _, err = db.conn.Exec(context.Background(), "DELETE FROM board_embed WHERE board = $1", b.ID)
272 if err != nil {
273 log.Fatalf("failed to delete board embeds: %s", err)
274 }
275 for _, embed := range b.Embeds {
276 _, err := db.conn.Exec(context.Background(), "INSERT INTO board_embed VALUES ($1, $2)", b.ID, embed)
277 if err != nil {
278 log.Fatalf("failed to insert board embeds: %s", err)
279 }
280 }
281 }
282
283 func (db *DB) DeleteBoard(id int) {
284 if id == 0 {
285 return
286 }
287 boardCache = nil
288
289 _, err := db.conn.Exec(context.Background(), "DELETE FROM board WHERE id = $1", id)
290 if err != nil {
291 log.Fatalf("failed to delete board: %s", err)
292 }
293 db.DeleteSubscriptionsByBoard(id)
294 }
295
296 func (db *DB) ClearBoardCache() {
297 boardCache = nil
298 }
299
300 func scanBoard(b *Board, row pgx.Row) error {
301 var (
302 reports int
303 oekaki int
304 rules string
305 backlinks int
306 gallery int
307 )
308 err := row.Scan(
309 &b.ID,
310 &b.Dir,
311 &b.Name,
312 &b.Description,
313 &b.Type,
314 &b.Lock,
315 &b.Approval,
316 &reports,
317 &b.Style,
318 &b.Locale,
319 &b.Delay,
320 &b.MinName,
321 &b.MaxName,
322 &b.MinEmail,
323 &b.MaxEmail,
324 &b.MinSubject,
325 &b.MaxSubject,
326 &b.MinMessage,
327 &b.MaxMessage,
328 &b.MinSizeThread,
329 &b.MaxSizeThread,
330 &b.MinSizeReply,
331 &b.MaxSizeReply,
332 &b.ThumbWidth,
333 &b.ThumbHeight,
334 &b.DefaultName,
335 &b.WordBreak,
336 &b.Truncate,
337 &b.Threads,
338 &b.Replies,
339 &b.MaxThreads,
340 &b.MaxReplies,
341 &oekaki,
342 &rules,
343 &b.Hide,
344 &backlinks,
345 &b.Instances,
346 &b.Identifiers,
347 &b.Files,
348 &gallery,
349 )
350 if err != nil {
351 return err
352 }
353 b.Reports = reports == 1
354 b.Oekaki = oekaki == 1
355 if rules != "" {
356 b.Rules = strings.Split(rules, "|||")
357 }
358 b.Backlinks = backlinks == 1
359 b.Gallery = gallery == 1
360 return nil
361 }
362
View as plain text