• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Freegle / Iznik / 8650

30 Apr 2026 10:37PM UTC coverage: 72.301% (-0.002%) from 72.303%
8650

Pull #307

circleci

edwh
Merge remote-tracking branch 'origin/master' into fix/stdmsg-delete-and-config
Pull Request #307: fix: standard message delete 404 and group standard message config not persisting

13645 of 20593 branches covered (66.26%)

Branch coverage included in aggregate %.

19 of 27 new or added lines in 2 files covered. (70.37%)

79 existing lines in 4 files now uncovered.

98377 of 134346 relevant lines covered (73.23%)

22.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.95
/iznik-server-go/stdmsg/stdmsg.go
1
package stdmsg
2

3
import (
4
        "strings"
5
        "strconv"
6

7
        "github.com/freegle/iznik-server-go/auth"
8
        "github.com/freegle/iznik-server-go/database"
9
        "github.com/freegle/iznik-server-go/user"
10
        "github.com/gofiber/fiber/v2"
11
)
12

13
type StdMsg struct {
14
        ID           uint64  `json:"id" gorm:"primary_key"`
15
        Configid     uint64  `json:"configid"`
16
        Title        string  `json:"title"`
17
        Action       string  `json:"action"`
18
        Subjpref     string  `json:"subjpref"`
19
        Subjsuff     string  `json:"subjsuff"`
20
        Body         string  `json:"body"`
21
        Rarelyused   int     `json:"rarelyused"`
22
        Autosend     int     `json:"autosend"`
23
        Newmodstatus string  `json:"newmodstatus"`
24
        Newdelstatus string  `json:"newdelstatus"`
25
        Edittext     string  `json:"edittext"`
26
        Insert       *string `json:"insert"`
27
}
28

29
// canModifyConfig checks if user can modify the parent config.
30
func canModifyConfig(myid uint64, configid uint64) bool {
4✔
31
        if auth.IsAdminOrSupport(myid) {
4✔
32
                return true
×
33
        }
×
34

35
        var createdby *uint64
4✔
36
        var protected int
4✔
37
        database.DBConn.Raw("SELECT createdby FROM mod_configs WHERE id = ?", configid).Scan(&createdby)
4✔
38
        database.DBConn.Raw("SELECT protected FROM mod_configs WHERE id = ?", configid).Scan(&protected)
4✔
39

4✔
40
        if createdby != nil && *createdby == myid {
7✔
41
                return true
3✔
42
        }
3✔
43
        if protected == 0 {
1✔
44
                return true
×
45
        }
×
46
        return false
1✔
47
}
48

49

50
// GetStdMsg handles GET /stdmsg.
51
//
52
// @Summary Get standard message
53
// @Tags stdmsg
54
// @Produce json
55
// @Param id query integer true "StdMsg ID"
56
// @Success 200 {object} map[string]interface{}
57
// @Router /api/stdmsg [get]
58
func GetStdMsg(c *fiber.Ctx) error {
2✔
59
        id, _ := strconv.ParseUint(c.Query("id", "0"), 10, 64)
2✔
60
        if id == 0 {
3✔
61
                return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"ret": 2, "status": "Invalid stdmsg id"})
1✔
62
        }
1✔
63

64
        db := database.DBConn
1✔
65
        var msg StdMsg
1✔
66
        db.Raw("SELECT * FROM mod_stdmsgs WHERE id = ?", id).Scan(&msg)
1✔
67
        if msg.ID == 0 {
1✔
68
                return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"ret": 2, "status": "Invalid stdmsg id"})
×
69
        }
×
70

71
        return c.JSON(fiber.Map{
1✔
72
                "ret":    0,
1✔
73
                "status": "Success",
1✔
74
                "stdmsg": msg,
1✔
75
        })
1✔
76
}
77

78
// PostStdMsg handles POST /stdmsg to create a new standard message.
79
//
80
// @Summary Create standard message
81
// @Tags stdmsg
82
// @Accept json
83
// @Produce json
84
// @Security BearerAuth
85
// @Router /api/stdmsg [post]
86
func PostStdMsg(c *fiber.Ctx) error {
2✔
87
        myid := user.WhoAmI(c)
2✔
88
        if myid == 0 {
2✔
89
                return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"ret": 1, "status": "Not logged in"})
×
90
        }
×
91

92
        if !auth.IsSystemMod(myid) {
2✔
93
                return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"ret": 4, "status": "Don't have rights to create configs"})
×
94
        }
×
95

96
        type CreateRequest struct {
2✔
97
                Configid     uint64  `json:"configid"`
2✔
98
                Title        string  `json:"title"`
2✔
99
                Action       string  `json:"action"`
2✔
100
                Subjpref     string  `json:"subjpref"`
2✔
101
                Subjsuff     string  `json:"subjsuff"`
2✔
102
                Body         string  `json:"body"`
2✔
103
                Rarelyused   int     `json:"rarelyused"`
2✔
104
                Autosend     int     `json:"autosend"`
2✔
105
                Newmodstatus string  `json:"newmodstatus"`
2✔
106
                Newdelstatus string  `json:"newdelstatus"`
2✔
107
                Edittext     string  `json:"edittext"`
2✔
108
                Insert       *string `json:"insert"`
2✔
109
        }
2✔
110

2✔
111
        var req CreateRequest
2✔
112
        if strings.Contains(c.Get("Content-Type"), "application/json") {
4✔
113
                c.BodyParser(&req)
2✔
114
        }
2✔
115
        if req.Title == "" {
3✔
116
                req.Title = c.FormValue("title", c.Query("title", ""))
1✔
117
        }
1✔
118
        if req.Configid == 0 {
2✔
119
                req.Configid, _ = strconv.ParseUint(c.FormValue("configid", c.Query("configid", "0")), 10, 64)
×
120
        }
×
121

122
        if req.Title == "" {
3✔
123
                return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"ret": 3, "status": "Must supply title"})
1✔
124
        }
1✔
125
        if req.Configid == 0 {
1✔
126
                return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"ret": 3, "status": "Must supply configid"})
×
127
        }
×
128

129
        db := database.DBConn
1✔
130

1✔
131
        // Use the underlying sql.DB to get LastInsertId() directly from the MySQL protocol
1✔
132
        // response — never issue a separate SELECT LAST_INSERT_ID() as it's unsafe under
1✔
133
        // parallel load (GORM's connection pool may assign a different connection).
1✔
134
        sqlDB, err := db.DB()
1✔
135
        if err != nil {
1✔
136
                return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"ret": 1, "status": "Database error"})
×
137
        }
×
138
        sqlResult, err := sqlDB.Exec("INSERT INTO mod_stdmsgs (configid, title, subjpref, subjsuff, body) VALUES (?, ?, '', '', '')", req.Configid, req.Title)
1✔
139
        if err != nil {
1✔
140
                return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"ret": 1, "status": "Create failed"})
×
141
        }
×
142

143
        var newID uint64
1✔
144
        lastID, err := sqlResult.LastInsertId()
1✔
145
        if err == nil && lastID > 0 {
2✔
146
                newID = uint64(lastID)
1✔
147
        }
1✔
148

149
        // Apply optional attributes.
150
        if req.Action != "" {
1✔
151
                db.Exec("UPDATE mod_stdmsgs SET action = ? WHERE id = ?", req.Action, newID)
×
152
        }
×
153
        if req.Subjpref != "" {
1✔
154
                db.Exec("UPDATE mod_stdmsgs SET subjpref = ? WHERE id = ?", req.Subjpref, newID)
×
155
        }
×
156
        if req.Subjsuff != "" {
1✔
157
                db.Exec("UPDATE mod_stdmsgs SET subjsuff = ? WHERE id = ?", req.Subjsuff, newID)
×
158
        }
×
159
        if req.Body != "" {
1✔
160
                db.Exec("UPDATE mod_stdmsgs SET body = ? WHERE id = ?", req.Body, newID)
×
161
        }
×
162
        if req.Rarelyused != 0 {
1✔
163
                db.Exec("UPDATE mod_stdmsgs SET rarelyused = ? WHERE id = ?", req.Rarelyused, newID)
×
164
        }
×
165
        if req.Autosend != 0 {
1✔
166
                db.Exec("UPDATE mod_stdmsgs SET autosend = ? WHERE id = ?", req.Autosend, newID)
×
167
        }
×
168

169
        return c.JSON(fiber.Map{"ret": 0, "status": "Success", "id": newID})
1✔
170
}
171

172
// PatchStdMsg handles PATCH /stdmsg to update attributes.
173
//
174
// @Summary Update standard message
175
// @Tags stdmsg
176
// @Accept json
177
// @Produce json
178
// @Security BearerAuth
179
// @Router /api/stdmsg [patch]
180
func PatchStdMsg(c *fiber.Ctx) error {
1✔
181
        myid := user.WhoAmI(c)
1✔
182
        if myid == 0 {
1✔
183
                return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"ret": 1, "status": "Not logged in"})
×
184
        }
×
185

186
        type PatchRequest struct {
1✔
187
                ID           uint64  `json:"id"`
1✔
188
                Title        *string `json:"title"`
1✔
189
                Action       *string `json:"action"`
1✔
190
                Subjpref     *string `json:"subjpref"`
1✔
191
                Subjsuff     *string `json:"subjsuff"`
1✔
192
                Body         *string `json:"body"`
1✔
193
                Rarelyused   *int    `json:"rarelyused"`
1✔
194
                Autosend     *int    `json:"autosend"`
1✔
195
                Newmodstatus *string `json:"newmodstatus"`
1✔
196
                Newdelstatus *string `json:"newdelstatus"`
1✔
197
                Edittext     *string `json:"edittext"`
1✔
198
                Insert       *string `json:"insert"`
1✔
199
        }
1✔
200

1✔
201
        var req PatchRequest
1✔
202
        if strings.Contains(c.Get("Content-Type"), "application/json") {
2✔
203
                c.BodyParser(&req)
1✔
204
        }
1✔
205
        if req.ID == 0 {
1✔
206
                req.ID, _ = strconv.ParseUint(c.FormValue("id", c.Query("id", "0")), 10, 64)
×
207
        }
×
208

209
        if req.ID == 0 {
1✔
210
                return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"ret": 2, "status": "Invalid stdmsg id"})
×
211
        }
×
212

213
        db := database.DBConn
1✔
214

1✔
215
        // Get the stdmsg to find its configid.
1✔
216
        var configid uint64
1✔
217
        db.Raw("SELECT configid FROM mod_stdmsgs WHERE id = ?", req.ID).Scan(&configid)
1✔
218
        if configid == 0 {
1✔
219
                return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"ret": 2, "status": "Invalid stdmsg id"})
×
220
        }
×
221

222
        if !canModifyConfig(myid, configid) {
1✔
223
                return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"ret": 4, "status": "Don't have rights to modify config"})
×
224
        }
×
225

226
        if req.Title != nil {
2✔
227
                db.Exec("UPDATE mod_stdmsgs SET title = ? WHERE id = ?", *req.Title, req.ID)
1✔
228
        }
1✔
229
        if req.Action != nil {
1✔
230
                db.Exec("UPDATE mod_stdmsgs SET action = ? WHERE id = ?", *req.Action, req.ID)
×
231
        }
×
232
        if req.Subjpref != nil {
1✔
233
                db.Exec("UPDATE mod_stdmsgs SET subjpref = ? WHERE id = ?", *req.Subjpref, req.ID)
×
234
        }
×
235
        if req.Subjsuff != nil {
1✔
236
                db.Exec("UPDATE mod_stdmsgs SET subjsuff = ? WHERE id = ?", *req.Subjsuff, req.ID)
×
237
        }
×
238
        if req.Body != nil {
2✔
239
                db.Exec("UPDATE mod_stdmsgs SET body = ? WHERE id = ?", *req.Body, req.ID)
1✔
240
        }
1✔
241
        if req.Rarelyused != nil {
1✔
242
                db.Exec("UPDATE mod_stdmsgs SET rarelyused = ? WHERE id = ?", *req.Rarelyused, req.ID)
×
243
        }
×
244
        if req.Autosend != nil {
1✔
245
                db.Exec("UPDATE mod_stdmsgs SET autosend = ? WHERE id = ?", *req.Autosend, req.ID)
×
246
        }
×
247
        if req.Newmodstatus != nil {
1✔
248
                db.Exec("UPDATE mod_stdmsgs SET newmodstatus = ? WHERE id = ?", *req.Newmodstatus, req.ID)
×
249
        }
×
250
        if req.Newdelstatus != nil {
1✔
251
                db.Exec("UPDATE mod_stdmsgs SET newdelstatus = ? WHERE id = ?", *req.Newdelstatus, req.ID)
×
252
        }
×
253
        if req.Edittext != nil {
1✔
254
                db.Exec("UPDATE mod_stdmsgs SET edittext = ? WHERE id = ?", *req.Edittext, req.ID)
×
255
        }
×
256
        if req.Insert != nil {
1✔
257
                db.Exec("UPDATE mod_stdmsgs SET `insert` = ? WHERE id = ?", *req.Insert, req.ID)
×
258
        }
×
259

260
        return c.JSON(fiber.Map{"ret": 0, "status": "Success"})
1✔
261
}
262

263
// DeleteStdMsg handles DELETE /stdmsg.
264
//
265
// @Summary Delete standard message
266
// @Tags stdmsg
267
// @Produce json
268
// @Param id query integer true "StdMsg ID"
269
// @Security BearerAuth
270
// @Router /api/stdmsg [delete]
271
type DeleteStdMsgRequest struct {
272
        ID uint64 `json:"id"`
273
}
274

275
func DeleteStdMsg(c *fiber.Ctx) error {
4✔
276
        myid := user.WhoAmI(c)
4✔
277
        if myid == 0 {
4✔
278
                return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"ret": 1, "status": "Not logged in"})
×
279
        }
×
280

281
        // The frontend sends DELETE params as a JSON body (via $delv2 in BaseAPI).
282
        // Fall back to query string for backwards compatibility.
283
        var req DeleteStdMsgRequest
4✔
284
        if strings.Contains(c.Get("Content-Type"), "application/json") {
5✔
285
                if err := c.BodyParser(&req); err != nil {
1✔
NEW
286
                        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"ret": 3, "status": "Invalid JSON in request body"})
×
NEW
287
                }
×
288
        }
289
        id := req.ID
4✔
290
        if id == 0 {
7✔
291
                id, _ = strconv.ParseUint(c.Query("id", "0"), 10, 64)
3✔
292
        }
3✔
293
        if id == 0 {
4✔
294
                return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"ret": 2, "status": "Invalid stdmsg id"})
×
295
        }
×
296

297
        db := database.DBConn
4✔
298

4✔
299
        var configid uint64
4✔
300
        db.Raw("SELECT configid FROM mod_stdmsgs WHERE id = ?", id).Scan(&configid)
4✔
301
        if configid == 0 {
5✔
302
                return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"ret": 2, "status": "Invalid stdmsg id"})
1✔
303
        }
1✔
304

305
        if !canModifyConfig(myid, configid) {
4✔
306
                return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"ret": 4, "status": "Don't have rights to modify config"})
1✔
307
        }
1✔
308

309
        db.Exec("DELETE FROM mod_stdmsgs WHERE id = ?", id)
2✔
310

2✔
311
        return c.JSON(fiber.Map{"ret": 0, "status": "Success"})
2✔
312
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc