Skip to content

Commit

Permalink
New movie autocomplete (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
NiJeTi authored Jan 26, 2025
1 parent 3fbaefb commit fc93d2b
Show file tree
Hide file tree
Showing 14 changed files with 696 additions and 17 deletions.
1 change: 1 addition & 0 deletions .coverignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ cmd/service
internal/generated

internal/adapters/discord
internal/adapters/omdb

internal/db

Expand Down
77 changes: 77 additions & 0 deletions internal/adapters/omdb/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package omdb

import (
"context"
"encoding/json"
"fmt"
"net/http"

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

type Config struct {
Key string `conf:"key"`
}

type Adapter struct {
config Config
client *http.Client
}

func New(config Config) *Adapter {
return &Adapter{
config: config,
client: &http.Client{},
}
}

func (a *Adapter) MoviesByTitle(
ctx context.Context, title string,
) ([]models.MovieShort, error) {
var dto movieSearch
if err := a.makeRequest(ctx, a.baseURL()+"&s="+title, &dto); err != nil {
return nil, err
}

return dto.toModel(), nil
}

func (a *Adapter) MovieByID(
ctx context.Context, id string,
) (*models.MovieShort, error) {
var dto movie
if err := a.makeRequest(ctx, a.baseURL()+"&i="+id, &dto); err != nil {
return nil, err
}

return ptr.To(dto.toModel()), nil
}

func (a *Adapter) makeRequest(
ctx context.Context, url string, result any,
) error {
req, err := http.NewRequestWithContext(
ctx, http.MethodGet, url, http.NoBody,
)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}

resp, err := a.client.Do(req)
if err != nil {
return fmt.Errorf("failed to get response: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

err = json.NewDecoder(resp.Body).Decode(result)
if err != nil {
return fmt.Errorf("failed to unmarshal response body: %w", err)
}

return nil
}
30 changes: 30 additions & 0 deletions internal/adapters/omdb/dto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package omdb

import (
"github.com/nijeti/cinema-keeper/internal/models"
)

//nolint:tagliatelle // external model
type movie struct {
IMDBID string `json:"imdbID"`
Title string `json:"Title"`
}

type movieSearch struct {
Search []movie
}

func (m movie) toModel() models.MovieShort {
return models.MovieShort{
ID: models.IMDBID(m.IMDBID),
Title: m.Title,
}
}

func (s movieSearch) toModel() []models.MovieShort {
movies := make([]models.MovieShort, 0, len(s.Search))
for _, m := range s.Search {
movies = append(movies, m.toModel())
}
return movies
}
14 changes: 14 additions & 0 deletions internal/adapters/omdb/url.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package omdb

import (
"fmt"
)

const (
host = "http://www.omdbapi.com"
apiKeyParam = "apikey"
)

func (a *Adapter) baseURL() string {
return fmt.Sprintf("%s/?%s=%s", host, apiKeyParam, a.config.Key)
}
32 changes: 24 additions & 8 deletions internal/discord/commands/movie.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package commands

import (
"github.com/bwmarrin/discordgo"

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

const (
Expand All @@ -13,7 +15,7 @@ const (
MovieSubCommandList = "list"
MovieSubCommandWatched = "watched"

MovieOptionName = "name"
MovieOptionTitle = "title"

MovieOptionType = "type"
MovieOptionTypeNext = "next"
Expand All @@ -27,6 +29,9 @@ const (
const (
MovieOptionTypeDefault = movieOptionTypeNextValue
MovieOptionRateDefault = 0

MovieOptionTitleMinLength = 4
MovieOptionTitleChoiceLimit = 5
)

const (
Expand All @@ -37,6 +42,11 @@ const (
movieOptionRateDislikeValue = -1
)

const (
movieOptionTitleValueMinLength = 9
movieOptionTitleValueMaxLength = 9
)

func Movie() *discordgo.ApplicationCommand {
return &discordgo.ApplicationCommand{
Name: MovieName,
Expand Down Expand Up @@ -71,10 +81,12 @@ func Movie() *discordgo.ApplicationCommand {
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: MovieOptionName,
Description: "Name of the movie",
Name: MovieOptionTitle,
Description: "Title of the movie",
Required: true,
Autocomplete: true,
MinLength: ptr.To(movieOptionTitleValueMinLength),
MaxLength: movieOptionTitleValueMaxLength,
},
},
},
Expand All @@ -85,10 +97,12 @@ func Movie() *discordgo.ApplicationCommand {
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: MovieOptionName,
Description: "Name of the movie",
Name: MovieOptionTitle,
Description: "Title of the movie",
Required: true,
Autocomplete: true,
MinLength: ptr.To(movieOptionTitleValueMinLength),
MaxLength: movieOptionTitleValueMaxLength,
},
},
},
Expand All @@ -104,15 +118,17 @@ func Movie() *discordgo.ApplicationCommand {
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: MovieOptionName,
Description: "Name of the movie",
Name: MovieOptionTitle,
Description: "Title of the movie",
Required: true,
Autocomplete: true,
MinLength: ptr.To(movieOptionTitleValueMinLength),
MaxLength: movieOptionTitleValueMaxLength,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: MovieOptionRate,
Description: "Rate of the movie",
Description: "Rate the movie",
Choices: []*discordgo.ApplicationCommandOptionChoice{
{
Name: MovieOptionRateLike,
Expand Down
37 changes: 37 additions & 0 deletions internal/discord/commands/responses/movie.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package responses

import (
"github.com/bwmarrin/discordgo"

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

func MovieAutocompleteEmptySearch() *discordgo.InteractionResponse {
return &discordgo.InteractionResponse{
Type: discordgo.InteractionApplicationCommandAutocompleteResult,
Data: &discordgo.InteractionResponseData{
Choices: []*discordgo.ApplicationCommandOptionChoice{},
},
}
}

func MovieAutocompleteSearch(
movies []models.MovieShort,
) *discordgo.InteractionResponse {
choices := make([]*discordgo.ApplicationCommandOptionChoice, 0, len(movies))
for _, movie := range movies {
choices = append(
choices, &discordgo.ApplicationCommandOptionChoice{
Name: movie.Title,
Value: movie.ID,
},
)
}

return &discordgo.InteractionResponse{
Type: discordgo.InteractionApplicationCommandAutocompleteResult,
Data: &discordgo.InteractionResponseData{
Choices: choices,
},
}
}
12 changes: 6 additions & 6 deletions internal/generated/mocks/discord/handler.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 85 additions & 0 deletions internal/generated/mocks/services/searchNewMovie/discord.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fc93d2b

Please sign in to comment.