Skip to content

Commit

Permalink
Quotes pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
NiJeTi committed Jan 4, 2025
1 parent 4cfeace commit 416f82d
Show file tree
Hide file tree
Showing 17 changed files with 508 additions and 290 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
MOCKERY_VERSION=github.com/vektra/mockery/[email protected]
GOLANGCI_LINT_IMAGE=golangci/golangci-lint:v1.62-alpine
GOLANGCI_LINT_IMAGE=golangci/golangci-lint:v1.63-alpine
GOOSE_VERSION=github.com/pressly/goose/v3/cmd/[email protected]

.PHONY: deps
Expand Down
10 changes: 5 additions & 5 deletions cmd/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"syscall"

"github.com/nijeti/healthcheck"
"github.com/nijeti/healthcheck/servers/fasthttp"
hcHTTP "github.com/nijeti/healthcheck/servers/http"

dcAdapterPkg "github.com/nijeti/cinema-keeper/internal/adapters/discord"
"github.com/nijeti/cinema-keeper/internal/db"
Expand Down Expand Up @@ -149,11 +149,11 @@ func run() (code int) {
healthcheck.WithProbe("db", dbProbe),
healthcheck.WithProbe("discord", dcProbe),
)
hcs := fasthttp.New(
hcs := hcHTTP.New(
hc,
fasthttp.WithLogger(hcLogger),
fasthttp.WithAddress(":8080"),
fasthttp.WithRoute("/health"),
hcHTTP.WithLogger(hcLogger),
hcHTTP.WithAddress(":8080"),
hcHTTP.WithRoute("/health"),
)
hcs.Start()
defer hcs.Stop()
Expand Down
26 changes: 5 additions & 21 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,36 @@ go 1.23

require (
github.com/bwmarrin/discordgo v0.28.1
github.com/bytedance/sonic v1.12.5
github.com/jmoiron/sqlx v1.4.0
github.com/knadh/koanf/parsers/yaml v0.1.0
github.com/knadh/koanf/providers/env v1.0.0
github.com/knadh/koanf/providers/file v1.1.2
github.com/knadh/koanf/v2 v2.1.2
github.com/lib/pq v1.10.9
github.com/nijeti/healthcheck v1.0.0-beta.2
github.com/nijeti/healthcheck/servers/fasthttp v1.0.0-beta.4
github.com/nijeti/healthcheck/servers/http v1.0.0-beta.4
github.com/pressly/goose/v3 v3.24.0
github.com/stretchr/testify v1.10.0
)

require (
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/avito-tech/go-transaction-manager/drivers/sql/v2 v2.0.0-rc9.1 // indirect
github.com/avito-tech/go-transaction-manager/drivers/sqlx/v2 v2.0.0 // indirect
github.com/avito-tech/go-transaction-manager/trm/v2 v2.0.0 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/diamondburned/arikawa/v3 v3.4.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pressly/goose/v3 v3.24.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.58.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/sys v0.29.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
179 changes: 34 additions & 145 deletions go.sum

Large diffs are not rendered by default.

17 changes: 4 additions & 13 deletions internal/adapters/discord/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func (a *Adapter) Respond(
i *discordgo.Interaction,
response *discordgo.InteractionResponse,
) error {
if i.Type == discordgo.InteractionMessageComponent {
response.Type = discordgo.InteractionResponseUpdateMessage
}

err := a.session.InteractionRespond(
i, response, discordgo.WithContext(ctx),
)
Expand All @@ -36,19 +40,6 @@ func (a *Adapter) Respond(
return nil
}

func (a *Adapter) SendEmbeds(
ctx context.Context, channelID models.ID, embeds []*discordgo.MessageEmbed,
) error {
_, err := a.session.ChannelMessageSendEmbeds(
channelID.String(), embeds, discordgo.WithContext(ctx),
)
if err != nil {
return fmt.Errorf("failed to send embeds: %w", err)
}

return nil
}

func (a *Adapter) GuildMember(
ctx context.Context, guildID models.ID, userID models.ID,
) (*discordgo.Member, error) {
Expand Down
37 changes: 34 additions & 3 deletions internal/db/quotes.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,47 @@ func NewQuotesRepo(
}
}

func (r *QuotesRepo) GetUserQuotesInGuild(
func (r *QuotesRepo) CountUserQuotesInGuild(
ctx context.Context, guildID models.ID, authorID models.ID,
) (int, error) {
const query = `
select count(*) from quotes
where guild_id = $1 and author_id = $2`

var rows []int
err := r.db.SelectContext(ctx, &rows, query, guildID, authorID)
if err != nil {
return 0, fmt.Errorf(
"failed to select user quotes count in guild: %w", err,
)
}

return rows[0], nil
}

func (r *QuotesRepo) GetUserQuotesInGuild(
ctx context.Context,
guildID models.ID,
authorID models.ID,
offset, limit int,
) ([]*models.Quote, error) {
if offset < 0 {
panic("offset must be greater than or equal to 0")
}
if limit <= 0 {
panic("limit must be greater than 0")
}

const query = `
select author_id, text, guild_id, added_by_id, timestamp from quotes
where guild_id = $1 and author_id = $2
order by timestamp`
order by timestamp
offset $3 limit $4`

var rows []quoteRow
err := r.db.SelectContext(ctx, &rows, query, guildID, authorID)
err := r.db.SelectContext(
ctx, &rows, query, guildID, authorID, offset, limit,
)
if err != nil {
return nil, fmt.Errorf(
"failed to select user quotes in guild: %w", err,
Expand Down
44 changes: 43 additions & 1 deletion internal/discord/commands/quote.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package commands

import (
"fmt"
"regexp"
"strconv"

"github.com/bwmarrin/discordgo"

"github.com/nijeti/cinema-keeper/internal/models"
"github.com/nijeti/cinema-keeper/internal/pkg/discordUtils"
)

const (
Expand All @@ -14,7 +21,16 @@ const (
QuoteOptionText = "text"
)

const QuoteMaxQuotesPerPage = 10
const QuoteMaxQuotesPerPage = 5

const (
quotesPageCustomIDAuthorIDIndex = 3
quotesPageCustomIDPageIndex = 4
)

var quotesPageCustomIDRegex = regexp.MustCompile(
`^([a-z]+)_([a-z]+)_(\d+)_(\d+)$`,
)

func Quote() *discordgo.ApplicationCommand {
return &discordgo.ApplicationCommand{
Expand Down Expand Up @@ -55,3 +71,29 @@ func Quote() *discordgo.ApplicationCommand {
},
}
}

func QuotesButtonCustomID(authorID models.ID, page int) string {
if page < 0 {
panic("page must be positive")
}

return fmt.Sprintf(
"%s_%s_%s_%d", QuoteName, QuoteSubCommandGet, authorID, page,
)
}

func QuoteParseButtonCustomID(id string) (authorID models.ID, page int) {
matches := quotesPageCustomIDRegex.FindStringSubmatch(id)
if len(matches) < quotesPageCustomIDRegex.NumSubexp() {
panic(discordUtils.ErrInvalidCustomID)
}

authorID = models.ID(matches[quotesPageCustomIDAuthorIDIndex])

page, err := strconv.Atoi(matches[quotesPageCustomIDPageIndex])
if err != nil {
panic(fmt.Errorf("failed to parse page: %w", err))
}

return authorID, page
}
98 changes: 70 additions & 28 deletions internal/discord/commands/responses/quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/bwmarrin/discordgo"

"github.com/nijeti/cinema-keeper/internal/discord/commands"
"github.com/nijeti/cinema-keeper/internal/models"
"github.com/nijeti/cinema-keeper/internal/pkg/utils"
)
Expand Down Expand Up @@ -42,38 +43,68 @@ func QuoteRandomQuote(quote *models.Quote) *discordgo.InteractionResponse {
Title: quote.Text,
Timestamp: quote.Timestamp.Format(time.RFC3339),
Color: utils.RandomColor(),
Author: &discordgo.MessageEmbedAuthor{
Name: quote.Author.DisplayName(),
IconURL: quote.Author.AvatarURL("32x32"),
Footer: &discordgo.MessageEmbedFooter{
Text: quote.Author.DisplayName(),
IconURL: quote.Author.AvatarURL("16x16"),
},
},
},
},
}
}

func QuoteListHeader(author *discordgo.Member) *discordgo.InteractionResponse {
return &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "Quotes",
Description: fmt.Sprintf(
"The most stunning quotes of %s", author.Mention(),
),
Color: utils.RandomColor(),
Thumbnail: &discordgo.MessageEmbedThumbnail{
URL: author.AvatarURL("64x64"),
},
},
},
func QuoteList(
quotes []*models.Quote, page int, lastPage int,
) *discordgo.InteractionResponse {
if len(quotes) > commands.QuoteMaxQuotesPerPage {
panic("too many quotes per page")
}

author := quotes[0].Author

prevButton := discordgo.Button{
Style: discordgo.PrimaryButton,
Emoji: &discordgo.ComponentEmoji{
Name: "◀️",
},
}
}
if page == 0 {
prevButton.Disabled = true
prevButton.CustomID = "prev"
} else {
prevButton.Disabled = false
prevButton.CustomID = commands.QuotesButtonCustomID(
models.ID(author.User.ID), page-1,
)
}

func QuoteList(quotes []*models.Quote) []*discordgo.MessageEmbed {
embeds := make([]*discordgo.MessageEmbed, 0, len(quotes))
nextButton := discordgo.Button{
Style: discordgo.PrimaryButton,
Emoji: &discordgo.ComponentEmoji{
Name: "▶️",
},
}
if page == lastPage {
nextButton.Disabled = true
nextButton.CustomID = "next"
} else {
nextButton.Disabled = false
nextButton.CustomID = commands.QuotesButtonCustomID(
models.ID(author.User.ID), page+1,
)
}

headerEmbed := &discordgo.MessageEmbed{
Description: fmt.Sprintf(
"The most stunning quotes of %s", author.Mention(),
),
Color: utils.RandomColor(),
Thumbnail: &discordgo.MessageEmbedThumbnail{
URL: author.AvatarURL("64x64"),
},
}

quoteEmbeds := make([]*discordgo.MessageEmbed, 0, len(quotes))
for _, quote := range quotes {
embed := &discordgo.MessageEmbed{
Title: quote.Text,
Expand All @@ -84,9 +115,24 @@ func QuoteList(quotes []*models.Quote) []*discordgo.MessageEmbed {
IconURL: quote.AddedBy.AvatarURL("16x16"),
},
}
embeds = append(embeds, embed)
quoteEmbeds = append(quoteEmbeds, embed)
}

return &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: append(
[]*discordgo.MessageEmbed{headerEmbed}, quoteEmbeds...,
),
Components: []discordgo.MessageComponent{
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
prevButton, nextButton,
},
},
},
},
}
return embeds
}

func QuoteAdded(quote *models.Quote) *discordgo.InteractionResponse {
Expand All @@ -103,10 +149,6 @@ func QuoteAdded(quote *models.Quote) *discordgo.InteractionResponse {
Text: quote.Author.DisplayName(),
IconURL: quote.Author.AvatarURL("16x16"),
},
Author: &discordgo.MessageEmbedAuthor{
Name: quote.AddedBy.DisplayName(),
IconURL: quote.AddedBy.AvatarURL("32x32"),
},
},
},
},
Expand Down
Loading

0 comments on commit 416f82d

Please sign in to comment.