hmn/src/website/snippet_helper.go

212 lines
4.7 KiB
Go
Raw Normal View History

2021-11-11 19:00:46 +00:00
package website
import (
"context"
"git.handmade.network/hmn/hmn/src/oops"
"git.handmade.network/hmn/hmn/src/db"
"git.handmade.network/hmn/hmn/src/models"
)
type SnippetQuery struct {
IDs []int
OwnerIDs []int
Tags []int
Limit, Offset int // if empty, no pagination
}
type SnippetAndStuff struct {
Snippet models.Snippet
Owner *models.User
Asset *models.Asset `db:"asset"`
DiscordMessage *models.DiscordMessage `db:"discord_message"`
Tags []*models.Tag
}
func FetchSnippets(
ctx context.Context,
dbConn db.ConnOrTx,
currentUser *models.User,
q SnippetQuery,
) ([]SnippetAndStuff, error) {
perf := ExtractPerf(ctx)
perf.StartBlock("SQL", "Fetch snippets")
defer perf.EndBlock()
tx, err := dbConn.Begin(ctx)
if err != nil {
return nil, oops.New(err, "failed to start transaction")
}
defer tx.Rollback(ctx)
var qb db.QueryBuilder
qb.Add(
`
SELECT $columns
FROM
handmade_snippet AS snippet
LEFT JOIN auth_user AS owner ON snippet.owner_id = owner.id
LEFT JOIN handmade_asset AS asset ON snippet.asset_id = asset.id
LEFT JOIN handmade_discordmessage AS discord_message ON snippet.discord_message_id = discord_message.id
LEFT JOIN snippet_tags ON snippet.id = snippet_tags.snippet_id
LEFT JOIN tags ON snippet_tags.tag_id = tags.id
WHERE
TRUE
`,
)
if len(q.IDs) > 0 {
qb.Add(`AND snippet.id = ANY ($?)`, q.IDs)
}
if len(q.OwnerIDs) > 0 {
qb.Add(`AND snippet.owner_id = ANY ($?)`, q.OwnerIDs)
}
if len(q.Tags) > 0 {
qb.Add(`AND snippet_tags.tag_id = ANY ($?)`, q.Tags)
}
if currentUser == nil {
qb.Add(
`AND owner.status = $? -- snippet owner is Approved`,
models.UserStatusApproved,
)
} else if !currentUser.IsStaff {
qb.Add(
`
AND (
owner.status = $? -- snippet owner is Approved
OR owner.id = $? -- current user is the snippet owner
)
`,
models.UserStatusApproved,
currentUser.ID,
)
}
qb.Add(`ORDER BY snippet.when DESC, snippet.id ASC`)
if q.Limit > 0 {
qb.Add(`LIMIT $? OFFSET $?`, q.Limit, q.Offset)
}
type resultRow struct {
Snippet models.Snippet `db:"snippet"`
Owner *models.User `db:"owner"`
Asset *models.Asset `db:"asset"`
DiscordMessage *models.DiscordMessage `db:"discord_message"`
Tag *models.Tag `db:"tags"`
}
it, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...)
if err != nil {
return nil, oops.New(err, "failed to fetch threads")
}
iresults := it.ToSlice()
result := make([]SnippetAndStuff, 0, len(iresults)) // allocate extra space because why not
currentSnippetId := -1
for _, iresult := range iresults {
row := *iresult.(*resultRow)
if row.Snippet.ID != currentSnippetId {
// we have moved onto a new snippet; make a new entry
result = append(result, SnippetAndStuff{
Snippet: row.Snippet,
Owner: row.Owner,
Asset: row.Asset,
DiscordMessage: row.DiscordMessage,
// no tags! tags next
})
}
if row.Tag != nil {
result[len(result)-1].Tags = append(result[len(result)-1].Tags, row.Tag)
}
}
err = tx.Commit(ctx)
if err != nil {
return nil, oops.New(err, "failed to commit transaction")
}
return result, nil
}
func FetchSnippet(
ctx context.Context,
dbConn db.ConnOrTx,
currentUser *models.User,
snippetID int,
q SnippetQuery,
) (SnippetAndStuff, error) {
q.IDs = []int{snippetID}
q.Limit = 1
q.Offset = 0
res, err := FetchSnippets(ctx, dbConn, currentUser, q)
if err != nil {
return SnippetAndStuff{}, oops.New(err, "failed to fetch snippet")
}
if len(res) == 0 {
return SnippetAndStuff{}, db.NotFound
}
return res[0], nil
}
type TagQuery struct {
IDs []int
Text []string
Limit, Offset int
}
func FetchTags(ctx context.Context, dbConn db.ConnOrTx, q TagQuery) ([]*models.Tag, error) {
2021-11-11 19:00:46 +00:00
perf := ExtractPerf(ctx)
perf.StartBlock("SQL", "Fetch snippets")
defer perf.EndBlock()
var qb db.QueryBuilder
qb.Add(
2021-11-11 19:00:46 +00:00
`
SELECT $columns
FROM tags
WHERE
TRUE
2021-11-11 19:00:46 +00:00
`,
)
if len(q.IDs) > 0 {
qb.Add(`AND id = ANY ($?)`, q.IDs)
}
if len(q.Text) > 0 {
qb.Add(`AND text = ANY ($?)`, q.Text)
}
if q.Limit > 0 {
qb.Add(`LIMIT $? OFFSET $?`, q.Limit, q.Offset)
}
it, err := db.Query(ctx, dbConn, models.Tag{}, qb.String(), qb.Args()...)
2021-11-11 19:00:46 +00:00
if err != nil {
return nil, oops.New(err, "failed to fetch tags")
}
itags := it.ToSlice()
res := make([]*models.Tag, len(itags))
for i, itag := range itags {
tag := itag.(*models.Tag)
res[i] = tag
}
return res, nil
}
func FetchTag(ctx context.Context, dbConn db.ConnOrTx, q TagQuery) (*models.Tag, error) {
tags, err := FetchTags(ctx, dbConn, q)
2021-11-11 19:00:46 +00:00
if err != nil {
return nil, err
}
if len(tags) == 0 {
return nil, db.NotFound
}
return tags[0], nil
}