Fix orphaned comment photo worker

This commit is contained in:
Noah Petherbridge 2024-07-25 22:46:32 -07:00
parent 40b1f2f57a
commit 5c5367c557
4 changed files with 15 additions and 91 deletions

View File

@ -126,26 +126,15 @@ the web app by using the admin controls on their profile page.
templates, issue redirects, error pages, ...
* `pkg/utility`: miscellaneous useful functions for the app.
## Cron API Endpoints
## Cron workers
In settings.json get or configure the CronAPIKey (a UUID4 value is good and
the app generates a fresh one by default). The following are the cron API
endpoints that you may want to configure to run periodic maintenance tasks
on the app, such as to remove orphaned comment photos.
### GET /v1/comment-photos/remove-orphaned
Query parameters: `apiKey` which is the CronAPIKey.
This endpoint removes orphaned CommentPhotos (photo attachments to forum
posts). An orphaned photo is one that has no CommentID and was uploaded
older than 24 hours ago; e.g. a user uploaded a picture but then did not
complete the posting of their comment.
Suggested crontab:
You can schedule the `nonshy vacuum` command in your crontab. This command
will check and clean up the database for things such as: orphaned comment
photos (where somebody uploaded a photo to post on the forum, but then didn't
finish creating their post).
```cron
0 2 * * * curl "http://localhost:8080/v1/comment-photos/remove-orphaned?apiKey=X"
0 2 * * * cd /home/nonshy/git/website && ./nonshy vacuum
```
## License

View File

@ -1,72 +0,0 @@
package api
import (
"net/http"
"code.nonshy.com/nonshy/website/pkg/config"
"code.nonshy.com/nonshy/website/pkg/worker"
)
// RemoveOrphanedCommentPhotos API.
//
// URL: /v1/comment-photos/remove-orphaned
//
// Query parameters: ?apiKey={CronAPIKey}
//
// This endpoint looks for CommentPhotos having a blank CommentID that were created older
// than 24 hours ago and removes them. Configure the "CronAPIKey" in your settings.json
// and pass it as the query parameter.
func RemoveOrphanedCommentPhotos() http.HandlerFunc {
// Response JSON schema.
type Response struct {
OK bool `json:"OK"`
Error string `json:"error,omitempty"`
Total int64 `json:"total"`
Removed int64 `json:"removed"`
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
SendJSON(w, http.StatusNotAcceptable, Response{
Error: "GET method only",
})
return
}
// Get and validate the API key.
var (
apiKey = r.FormValue("apiKey")
compare = config.Current.CronAPIKey
)
if compare == "" {
SendJSON(w, http.StatusInternalServerError, Response{
OK: false,
Error: "app CronAPIKey is not configured",
})
return
} else if apiKey == "" || apiKey != compare {
SendJSON(w, http.StatusInternalServerError, Response{
OK: false,
Error: "invalid apiKey query parameter",
})
return
}
// Do the needful.
total, err := worker.VacuumOrphanedCommentPhotos(false)
if err != nil {
SendJSON(w, http.StatusInternalServerError, Response{
Error: err.Error(),
})
return
}
// Send success response.
SendJSON(w, http.StatusOK, Response{
OK: true,
Total: total,
Removed: total,
})
})
}

View File

@ -134,7 +134,15 @@ func GetOrphanedCommentPhotos() ([]*CommentPhoto, int64, error) {
ps = []*CommentPhoto{}
)
query := DB.Model(&CommentPhoto{}).Where("comment_id = 0 AND created_at < ?", cutoff)
query := DB.Model(&CommentPhoto{}).Where(`
(comment_id <> 0 AND NOT EXISTS (
SELECT 1 FROM comments
WHERE comments.id = comment_photos.comment_id
))
OR
(comment_id = 0 AND created_at < ?)`,
cutoff,
)
query.Count(&count)
res := query.Limit(500).Find(&ps)
if res.Error != nil {

View File

@ -121,7 +121,6 @@ func New() http.Handler {
mux.Handle("POST /v1/notifications/read", middleware.LoginRequired(api.ReadNotification()))
mux.Handle("POST /v1/notifications/delete", middleware.LoginRequired(api.ClearNotification()))
mux.Handle("POST /v1/photos/mark-explicit", middleware.LoginRequired(api.MarkPhotoExplicit()))
mux.Handle("GET /v1/comment-photos/remove-orphaned", api.RemoveOrphanedCommentPhotos())
mux.Handle("POST /v1/barertc/report", barertc.Report())
mux.Handle("POST /v1/barertc/profile", barertc.Profile())