Compare commits
10 Commits
e541681dd1
...
430892a512
| Author | SHA1 | Date | |
|---|---|---|---|
| 430892a512 | |||
| 958ef12e91 | |||
| a6f3325842 | |||
| 4dbb47be37 | |||
| 490e610e4e | |||
| c0dd8681aa | |||
| fdee50b574 | |||
| 90c21a1f55 | |||
| 3d71a16633 | |||
| 8c1f642e33 |
39
README.md
39
README.md
@ -1,52 +1,53 @@
|
|||||||
# Rating Orama
|
# Rating Orama
|
||||||
|
|
||||||
Rating Orama is a web application for displaying TV show ratings and statistics. It is composed of 3 main parts:
|
Rating Orama is a web application for displaying TV show ratings and statistics.
|
||||||
|
It is composed of 2 main parts:
|
||||||
|
|
||||||
1. **Core**: Written in Go and Fiber, responsible for orchestrating everything and displaying the data using a template engine.
|
1. **Core**: Written in Go and Fiber, responsible for orchestrating everything
|
||||||
2. **Harvester**: Written in Python, Flask, and Cinemagoer, responsible for collecting data for the core.
|
and displaying the data using a template engine.
|
||||||
3. **Database**: PostgreSQL for storing data.
|
3. **Database**: PostgreSQL for storing data.
|
||||||
|
|
||||||
## Running the project
|
## Running the project
|
||||||
|
|
||||||
There are two ways to run the project: launching each part individually or building the Dockerfile and running it using Docker Compose. Here's an example of the `docker-compose.yml` file for the latter option:
|
There are two ways to run the project: launching each part individually or
|
||||||
|
building the Dockerfile and running it using Docker Compose. Here's an example
|
||||||
|
of the `docker-compose.yml` file for the latter option:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
harvester:
|
|
||||||
container_name: harvester-ratingorama
|
|
||||||
image: harvester:0.1.0
|
|
||||||
networks:
|
|
||||||
- ratingorama
|
|
||||||
core:
|
core:
|
||||||
container_name: core-ratingorama
|
container_name: core-ratingorama
|
||||||
image: core:0.1.0
|
image: core:latest
|
||||||
environment:
|
environment:
|
||||||
DATASOURCE: ${DATASOURCE}
|
DATASOURCE: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?sslmode=disable
|
||||||
HARVESTER_API: ${HARVESTER_API}
|
|
||||||
IS_PRODUCTION: ${IS_PRODUCTION}
|
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "8080:8080"
|
||||||
networks:
|
networks:
|
||||||
- ratingorama
|
- ratingorama
|
||||||
db:
|
db:
|
||||||
container_name: db-ratingorama
|
container_name: db-ratingorama
|
||||||
image: postgres:15.2-alpine
|
image: postgres:16.3-alpine3.20
|
||||||
environment:
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
ports:
|
ports:
|
||||||
- "5433:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ./schema.sql:/docker-entrypoint-initdb.d/schema.sql
|
- rating-orama_data:/var/lib/postgresql/data
|
||||||
- ./data:/var/lib/postgresql/data
|
|
||||||
networks:
|
networks:
|
||||||
- ratingorama
|
- ratingorama
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
ratingorama:
|
ratingorama:
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
rating-orama_data:
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
If you have ideas for improvements or bug fixes, feel free to contribute! To do so, simply clone the repository, create a new branch, and submit a pull request.
|
If you have ideas for improvements or bug fixes, feel free to contribute! To do
|
||||||
|
so, simply clone the repository, create a new branch, and submit a pull request.
|
||||||
|
|||||||
5
core/.env.example
Normal file
5
core/.env.example
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
DRIVERNAME=pgx
|
||||||
|
MIGRATE=true
|
||||||
|
DATASOURCE=postgresql://developer:secret@localhost:5432/db?sslmode=disable
|
||||||
|
ASYMMETRICKEY=
|
||||||
|
DURATION=
|
||||||
@ -1,13 +1,8 @@
|
|||||||
FROM golang:1.20.3-alpine
|
FROM alpine:3.20
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
COPY ./database ./database
|
||||||
|
COPY ./tmp/rating .
|
||||||
|
|
||||||
RUN go mod download
|
CMD ["/app/rating"]
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN go build -o main
|
|
||||||
|
|
||||||
CMD ["/app/main"]
|
|
||||||
121
core/Makefile
121
core/Makefile
@ -1,40 +1,145 @@
|
|||||||
|
GO ?= go
|
||||||
|
GOFMT ?= gofmt "-s"
|
||||||
|
GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||||
|
PACKAGES ?= $(shell $(GO) list ./...)
|
||||||
|
VETPACKAGES ?= $(shell $(GO) list ./...)
|
||||||
|
GOFILES := $(shell find . -name "*.go")
|
||||||
|
|
||||||
|
.PHONY: sayhello
|
||||||
|
# Print Hello World
|
||||||
sayhello:
|
sayhello:
|
||||||
@echo "Hello World"
|
@echo "Hello World"
|
||||||
|
|
||||||
|
.PHONY: dockerize
|
||||||
|
# Creates a development database.
|
||||||
dockerize:
|
dockerize:
|
||||||
docker run --name rating-db-dev -e POSTGRES_PASSWORD=secret -e POSTGRES_USER=developer -e POSTGRES_DB=rating -p 5432:5432 -d postgres:16.3-alpine3.20
|
docker run --name rating-db-dev -e POSTGRES_PASSWORD=secret -e POSTGRES_USER=developer -e POSTGRES_DB=rating -p 5432:5432 -d postgres:16.3-alpine3.20
|
||||||
|
|
||||||
|
.PHONY: undockerize
|
||||||
|
# Destroy a development database.
|
||||||
undockerize:
|
undockerize:
|
||||||
docker rm -f rating-db-dev
|
docker rm -f rating-db-dev
|
||||||
|
|
||||||
|
.PHONY: migrateup
|
||||||
|
# Migrate all schemas, triggers and data located in cmd/database/migrations.
|
||||||
migrateup:
|
migrateup:
|
||||||
migrate -path database/migrations -database "postgresql://developer:secret@localhost:5432/rating?sslmode=disable" -verbose up
|
migrate -path cmd/database/migrations -database "postgresql://developer:secret@localhost:5432/rating?sslmode=disable" -verbose up
|
||||||
|
|
||||||
migratedown:
|
|
||||||
migrate -path database/migrations -database "postgresql://developer:secret@localhost:5432/rating?sslmode=disable" -verbose down
|
|
||||||
|
|
||||||
|
.PHONY: sqlc
|
||||||
|
# Generate or recreate SQLC queries.
|
||||||
sqlc:
|
sqlc:
|
||||||
sqlc generate
|
sqlc generate
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
# Test all files and generate coverage file.
|
||||||
test:
|
test:
|
||||||
go test -v -cover ./...
|
$(GO) test -v -covermode=count -coverprofile=coverage.out $(PACKAGES)
|
||||||
|
|
||||||
|
.PHONY: gomock
|
||||||
|
# Generate mock files.
|
||||||
gomock:
|
gomock:
|
||||||
mockgen -package mock -destination internal/repository/mock/querier.go github.com/zepyrshut/rating-orama/internal/repository ExtendedQuerier
|
mockgen -package mock -destination internal/repository/mock/querier.go rating-orama/internal/repository ExtendedQuerier
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
# Run project.
|
||||||
run:
|
run:
|
||||||
go run ./cmd/.
|
$(GO) run ./cmd/.
|
||||||
|
|
||||||
|
.PHONY: recreate
|
||||||
|
# Destroy development DB and generate ones.
|
||||||
recreate:
|
recreate:
|
||||||
make undockerize
|
make undockerize
|
||||||
make dockerize
|
make dockerize
|
||||||
sleep 2
|
sleep 2
|
||||||
make migrateup
|
make migrateup
|
||||||
|
|
||||||
|
.PHONY: tidy
|
||||||
|
# Runs a go mod tidy
|
||||||
|
tidy:
|
||||||
|
$(GO) mod tidy
|
||||||
|
|
||||||
|
.PHONY: build-linux
|
||||||
|
# Build and generate linux executable.
|
||||||
build-linux:
|
build-linux:
|
||||||
|
make tidy
|
||||||
|
make remove-debug
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./tmp/arena ./cmd/.
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./tmp/arena ./cmd/.
|
||||||
|
|
||||||
|
.PHONY: pack-docker
|
||||||
|
# Run docker build for pack binary and assets to Docker container.
|
||||||
pack-docker:
|
pack-docker:
|
||||||
make test
|
make test
|
||||||
make build-linux
|
make build-linux
|
||||||
docker build -t rating:${version} -t rating:latest .
|
docker build -t rating-orama:${version} -t rating-orama:latest .
|
||||||
|
|
||||||
|
.PHONY: remove-debug
|
||||||
|
# Remove all debug entries for reduce size binary.
|
||||||
|
remove-debug:
|
||||||
|
find . -name "*.go" -type f -exec sed -i '/slog\.Debug/d' {} +
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
# Ensure consistent code formatting.
|
||||||
|
fmt:
|
||||||
|
$(GOFMT) -w $(GOFILES)
|
||||||
|
|
||||||
|
.PHONY: fmt-check
|
||||||
|
# format (check only).
|
||||||
|
fmt-check:
|
||||||
|
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||||
|
if [ -n "$$diff" ]; then \
|
||||||
|
echo "Please run 'make fmt' and commit the result:"; \
|
||||||
|
echo "$${diff}"; \
|
||||||
|
exit 1; \
|
||||||
|
fi;
|
||||||
|
|
||||||
|
.PHONY: vet
|
||||||
|
# Examine packages and report suspicious constructs if any.
|
||||||
|
vet:
|
||||||
|
$(GO) vet $(VETPACKAGES)
|
||||||
|
|
||||||
|
.PHONY: tools
|
||||||
|
# Install tools (migrate and sqlc).
|
||||||
|
tools:
|
||||||
|
@if [ $(GO_VERSION) -gt 16 ]; then \
|
||||||
|
$(GO) install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest; \
|
||||||
|
$(GO) install github.com/sqlc-dev/sqlc/cmd/sqlc@latest; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: env
|
||||||
|
# Copy .env.example to .env if .env does not already exist
|
||||||
|
env:
|
||||||
|
@if [ ! -f .env ]; then \
|
||||||
|
cp .env.example .env; \
|
||||||
|
echo ".env file created from .env.example"; \
|
||||||
|
else \
|
||||||
|
echo ".env file already exists"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: first-run
|
||||||
|
# Runs for the first time
|
||||||
|
first-run:
|
||||||
|
make tools
|
||||||
|
make env
|
||||||
|
make recreate
|
||||||
|
make run
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
# Help.
|
||||||
|
help:
|
||||||
|
@echo ''
|
||||||
|
@echo 'Usage:'
|
||||||
|
@echo ' make [target]'
|
||||||
|
@echo ''
|
||||||
|
@echo 'Targets:'
|
||||||
|
@awk '/^[a-zA-Z\-\0-9]+:/ { \
|
||||||
|
helpMessage = match(lastLine, /^# (.*)/); \
|
||||||
|
if (helpMessage) { \
|
||||||
|
helpCommand = substr($$1, 0, index($$1, ":")-1); \
|
||||||
|
helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \
|
||||||
|
printf " - \033[36m%-20s\033[0m %s\n", helpCommand, helpMessage; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
{ lastLine = $$0 }' $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
@ -14,7 +14,8 @@ where tt_imdb = $1;
|
|||||||
|
|
||||||
-- name: GetEpisodes :many
|
-- name: GetEpisodes :many
|
||||||
select * from "episodes"
|
select * from "episodes"
|
||||||
where tv_show_id = $1;
|
where tv_show_id = $1
|
||||||
|
order by season, episode asc;
|
||||||
|
|
||||||
-- name: IncreasePopularity :exec
|
-- name: IncreasePopularity :exec
|
||||||
update "tv_show" set popularity = popularity + 1
|
update "tv_show" set popularity = popularity + 1
|
||||||
@ -33,5 +34,5 @@ select avg(avg_rating) from "episodes"
|
|||||||
where tv_show_id = $1 and season = $2;
|
where tv_show_id = $1 and season = $2;
|
||||||
|
|
||||||
-- name: SeasonMedianRating :one
|
-- name: SeasonMedianRating :one
|
||||||
-- select percentile_cont(0.5) within group (order by avg_rating) from "episodes"
|
select percentile_cont(0.5) within group (order by avg_rating) from "episodes"
|
||||||
-- where tv_show_id = $1 and season = $2;
|
where tv_show_id = $1 and season = $2;
|
||||||
@ -1,82 +1,42 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"gopher-toolbox/config"
|
"gopher-toolbox/app"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gopher-toolbox/db"
|
"gopher-toolbox/db"
|
||||||
"gopher-toolbox/utils"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/gofiber/fiber/v2"
|
||||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
|
||||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/zepyrshut/rating-orama/internal/handlers"
|
"github.com/zepyrshut/rating-orama/internal/handlers"
|
||||||
"github.com/zepyrshut/rating-orama/internal/repository"
|
"github.com/zepyrshut/rating-orama/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "0.2.0-beta.20241116-4"
|
//go:embed database/migrations
|
||||||
|
var database embed.FS
|
||||||
|
|
||||||
var app *config.App
|
const version = "0.2.0-beta.20241116-4"
|
||||||
|
const appName = "rating-orama"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gob.Register(map[string]string{})
|
gob.Register(map[string]string{})
|
||||||
|
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("cannot load .env file", "error", err)
|
|
||||||
}
|
|
||||||
config.NewLogger(config.LogLevel(os.Getenv("LOG_LEVEL")))
|
|
||||||
slog.Info("starting server")
|
|
||||||
|
|
||||||
if os.Getenv("MIGRATE") == "true" {
|
|
||||||
migrateDB()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateDB() {
|
|
||||||
slog.Info("migrating database")
|
|
||||||
m, err := migrate.New("file://database/migrations", os.Getenv("DATASOURCE"))
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("cannot create migration", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = m.Up()
|
|
||||||
if err != nil && err != migrate.ErrNoChange {
|
|
||||||
slog.Error("cannot migrate", "error", err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err == migrate.ErrNoChange {
|
|
||||||
slog.Info("migration has no changes")
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("migration done")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
app := app.New(version)
|
||||||
app = &config.App{
|
r := fiber.New(fiber.Config{
|
||||||
DataSource: os.Getenv("DATASOURCE"),
|
AppName: appName,
|
||||||
UseCache: utils.GetBool(os.Getenv("USE_CACHE")),
|
})
|
||||||
AppInfo: config.AppInfo{
|
|
||||||
GinMode: os.Getenv("GIN_MODE"),
|
dbPool := db.NewPGXPool(app.Database.DataSource)
|
||||||
Version: version,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
dbPool := db.NewPostgresPool(app.DataSource)
|
|
||||||
defer dbPool.Close()
|
defer dbPool.Close()
|
||||||
|
|
||||||
q := repository.NewPGXRepo(dbPool)
|
q := repository.NewPGXRepo(dbPool)
|
||||||
h := handlers.New(q, app)
|
h := handlers.New(app, q)
|
||||||
r := Router(h, app)
|
router(h, r)
|
||||||
|
|
||||||
slog.Info("server started", "port", "8080", "version", version)
|
slog.Info("server started", "port", "8080", "version", version)
|
||||||
err := http.ListenAndServe(":8080", r)
|
if err := r.Listen(":8080"); err != nil {
|
||||||
if err != nil {
|
|
||||||
slog.Error("cannot start server", "error", err)
|
slog.Error("cannot start server", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/zepyrshut/rating-orama/internal/handlers"
|
"github.com/zepyrshut/rating-orama/internal/handlers"
|
||||||
|
|
||||||
"gopher-toolbox/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Router(h *handlers.Handlers, app *config.App) *gin.Engine {
|
func router(h *handlers.Handlers, r *fiber.App) {
|
||||||
gin.SetMode(app.AppInfo.GinMode)
|
|
||||||
r := gin.New()
|
|
||||||
|
|
||||||
r.GET("/tvshow", h.GetTVShow)
|
r.Get("/tvshow", h.GetTVShow)
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|||||||
55
core/go.mod
55
core/go.mod
@ -2,69 +2,56 @@ module github.com/zepyrshut/rating-orama
|
|||||||
|
|
||||||
go 1.23.2
|
go 1.23.2
|
||||||
|
|
||||||
require github.com/jackc/pgx/v5 v5.7.1
|
require (
|
||||||
|
github.com/PuerkitoBio/goquery v1.10.0
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.5
|
||||||
|
github.com/jackc/pgx/v5 v5.7.1
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.10.0 // indirect
|
aidanwoods.dev/go-paseto v1.5.2 // indirect
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
aidanwoods.dev/go-result v0.1.0 // indirect
|
||||||
github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 // indirect
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
|
|
||||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||||
github.com/antchfx/htmlquery v1.3.3 // indirect
|
github.com/antchfx/htmlquery v1.3.3 // indirect
|
||||||
github.com/antchfx/xmlquery v1.4.2 // indirect
|
github.com/antchfx/xmlquery v1.4.2 // indirect
|
||||||
github.com/antchfx/xpath v1.3.2 // indirect
|
github.com/antchfx/xpath v1.3.2 // indirect
|
||||||
github.com/bytedance/sonic v1.12.4 // indirect
|
github.com/go-sql-driver/mysql v1.5.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/gabriel-vasile/mimetype v1.4.6 // indirect
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
|
||||||
github.com/gobwas/glob v0.2.3 // indirect
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/jackc/pgconn v1.14.3 // indirect
|
github.com/jackc/pgconn v1.14.3 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/kennygrant/sanitize v1.2.4 // indirect
|
github.com/kennygrant/sanitize v1.2.4 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
github.com/klauspost/compress v1.17.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
|
||||||
github.com/lib/pq v1.10.9 // indirect
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/o1egl/paseto v1.0.0 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||||
github.com/temoto/robotstxt v1.1.2 // indirect
|
github.com/temoto/robotstxt v1.1.2 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
golang.org/x/arch v0.11.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.10.0
|
|
||||||
github.com/gocolly/colly v1.2.0
|
github.com/gocolly/colly v1.2.0
|
||||||
github.com/golang-migrate/migrate/v4 v4.18.1
|
github.com/golang-migrate/migrate/v4 v4.18.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/joho/godotenv v1.5.1
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
golang.org/x/crypto v0.28.0 // indirect
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
|
|||||||
112
core/go.sum
112
core/go.sum
@ -1,16 +1,15 @@
|
|||||||
|
aidanwoods.dev/go-paseto v1.5.2 h1:9aKbCQQUeHCqis9Y6WPpJpM9MhEOEI5XBmfTkFMSF/o=
|
||||||
|
aidanwoods.dev/go-paseto v1.5.2/go.mod h1:7eEJZ98h2wFi5mavCcbKfv9h86oQwut4fLVeL/UBFnw=
|
||||||
|
aidanwoods.dev/go-result v0.1.0 h1:y/BMIRX6q3HwaorX1Wzrjo3WUdiYeyWbvGe18hKS3K8=
|
||||||
|
aidanwoods.dev/go-result v0.1.0/go.mod h1:yridkWghM7AXSFA6wzx0IbsurIm1Lhuro3rYef8FBHM=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
||||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=
|
|
||||||
github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 h1:1DcvRPZOdbQRg5nAHt2jrc5QbV0AGuhDdfQI6gXjiFE=
|
|
||||||
github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=
|
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
|
||||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||||
github.com/antchfx/htmlquery v1.3.3 h1:x6tVzrRhVNfECDaVxnZi1mEGrQg3mjE/rxbH2Pe6dNE=
|
github.com/antchfx/htmlquery v1.3.3 h1:x6tVzrRhVNfECDaVxnZi1mEGrQg3mjE/rxbH2Pe6dNE=
|
||||||
@ -19,15 +18,6 @@ github.com/antchfx/xmlquery v1.4.2 h1:MZKd9+wblwxfQ1zd1AdrTsqVaMjMCwow3IqkCSe00K
|
|||||||
github.com/antchfx/xmlquery v1.4.2/go.mod h1:QXhvf5ldTuGqhd1SHNvvtlhhdQLks4dD0awIVhXIDTA=
|
github.com/antchfx/xmlquery v1.4.2/go.mod h1:QXhvf5ldTuGqhd1SHNvvtlhhdQLks4dD0awIVhXIDTA=
|
||||||
github.com/antchfx/xpath v1.3.2 h1:LNjzlsSjinu3bQpw9hWMY9ocB80oLOWuQqFvO6xt51U=
|
github.com/antchfx/xpath v1.3.2 h1:LNjzlsSjinu3bQpw9hWMY9ocB80oLOWuQqFvO6xt51U=
|
||||||
github.com/antchfx/xpath v1.3.2/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
github.com/antchfx/xpath v1.3.2/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||||
github.com/bytedance/sonic v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k=
|
|
||||||
github.com/bytedance/sonic v1.12.4/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
|
||||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
|
||||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
|
||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -43,30 +33,18 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
|||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
||||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
|
||||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
|
||||||
github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI=
|
github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI=
|
||||||
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
|
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
|
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
|
||||||
@ -80,7 +58,6 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@ -107,75 +84,52 @@ github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
|||||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
||||||
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
|
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
|
||||||
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
|
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0=
|
|
||||||
github.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
|
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
|
||||||
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
|
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||||
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/zepyrshut/esfaker v0.0.0-20241017072233-b4a5efb1f24d h1:o52tUkQBIDD6s2v2OHmXIsZQIKTEiVMPeov2SmvXJWk=
|
|
||||||
github.com/zepyrshut/esfaker v0.0.0-20241017072233-b4a5efb1f24d/go.mod h1:HgsPkO8n/XumWNHKfMZNV9UgC9/sUghpxVFuQcPJd2o=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||||
@ -184,11 +138,8 @@ go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2
|
|||||||
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
|
||||||
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
|
||||||
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
@ -208,12 +159,12 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@ -241,13 +192,10 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs
|
|||||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
|
|||||||
@ -1,90 +1,30 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopher-toolbox/config"
|
"gopher-toolbox/app"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/zepyrshut/rating-orama/internal/repository"
|
"github.com/zepyrshut/rating-orama/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Extract to toolbox
|
|
||||||
const (
|
|
||||||
InvalidRequest string = "invalid_request"
|
|
||||||
InternalError string = "internal_error"
|
|
||||||
RequestID string = "request_id"
|
|
||||||
NotFound string = "not_found"
|
|
||||||
Created string = "created"
|
|
||||||
Updated string = "updated"
|
|
||||||
Deleted string = "deleted"
|
|
||||||
Enabled string = "enabled"
|
|
||||||
Disabled string = "disabled"
|
|
||||||
Retrieved string = "retrieved"
|
|
||||||
ErrorCreating string = "error_creating"
|
|
||||||
ErrorUpdating string = "error_updating"
|
|
||||||
ErrorEnabling string = "error_enabling"
|
|
||||||
ErrorDisabling string = "error_disabling"
|
|
||||||
ErrorGetting string = "error_getting"
|
|
||||||
ErrorGettingAll string = "error_getting_all"
|
|
||||||
InvalidEntityID string = "invalid_entity_id"
|
|
||||||
NotImplemented string = "not_implemented"
|
|
||||||
|
|
||||||
UserUsernameKey string = "user_username_key"
|
|
||||||
UserEmailKey string = "user_email_key"
|
|
||||||
UsernameAlReadyExists string = "username_already_exists"
|
|
||||||
EmailAlreadyExists string = "email_already_exists"
|
|
||||||
IncorrectPassword string = "incorrect_password"
|
|
||||||
ErrorGeneratingToken string = "error_generating_token"
|
|
||||||
LoggedIn string = "logged_in"
|
|
||||||
|
|
||||||
CategoryNameKey string = "category_name_key"
|
|
||||||
CategoryAlreadyExists string = "category_already_exists"
|
|
||||||
|
|
||||||
ItemsNameKey string = "items_name_key"
|
|
||||||
NameAlreadyExists string = "name_already_exists"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Handlers struct {
|
type Handlers struct {
|
||||||
App *config.App
|
app *app.App
|
||||||
Queries repository.ExtendedQuerier
|
queries repository.ExtendedQuerier
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(q repository.ExtendedQuerier, app *config.App) *Handlers {
|
func New(app *app.App, q repository.ExtendedQuerier) *Handlers {
|
||||||
return &Handlers{
|
return &Handlers{
|
||||||
Queries: q,
|
app: app,
|
||||||
App: app,
|
queries: q,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hq *Handlers) ToBeImplemented(c *gin.Context) {
|
func (hq *Handlers) ToBeImplemented(c *fiber.Ctx) error {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
return c.Status(http.StatusNotImplemented).JSON("not implemented")
|
||||||
"message": "Not implemented yet",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hq *Handlers) Ping(c *gin.Context) {
|
func (hq *Handlers) Ping(c *fiber.Ctx) error {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
return c.JSON("pong")
|
||||||
"message": "pong",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Extract to toolbox
|
|
||||||
func handleQueryError(c *gin.Context, err error, errorMap map[string]string, logMessage string, defaultErrorMessage string) bool {
|
|
||||||
if err != nil {
|
|
||||||
for key, message := range errorMap {
|
|
||||||
if strings.Contains(err.Error(), key) {
|
|
||||||
slog.Error(logMessage, "error", message, RequestID, c.Request.Context().Value(RequestID))
|
|
||||||
c.JSON(http.StatusConflict, gin.H{"error": message})
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Error(logMessage, "error", err.Error(), RequestID, c.Request.Context().Value(RequestID))
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": defaultErrorMessage})
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,53 +4,58 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
"github.com/zepyrshut/rating-orama/internal/scraper"
|
"github.com/zepyrshut/rating-orama/internal/scraper"
|
||||||
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (hq *Handlers) GetTVShow(c *gin.Context) {
|
func (hq *Handlers) GetTVShow(c *fiber.Ctx) error {
|
||||||
ttShowID := c.Query("ttid")
|
ttShowID := c.Query("ttid")
|
||||||
slog.Info("", "ttid", ttShowID, RequestID, c.Request.Context().Value(RequestID))
|
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
var scraperEpisodes []scraper.Episode
|
var scraperEpisodes []scraper.Episode
|
||||||
var sqlcEpisodes []sqlc.Episode
|
var sqlcEpisodes []sqlc.Episode
|
||||||
|
|
||||||
tvShow, err := hq.Queries.CheckTVShowExists(c, ttShowID)
|
tvShow, err := hq.queries.CheckTVShowExists(c.Context(), ttShowID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
title, scraperEpisodes = scraper.ScrapeEpisodes(ttShowID)
|
title, scraperEpisodes = scraper.ScrapeEpisodes(ttShowID)
|
||||||
|
// TODO: make transactional
|
||||||
sqlcEpisodes, err = hq.Queries.CreateTvShowWithEpisodes(c, sqlc.CreateTVShowParams{
|
ttShow, err := hq.queries.CreateTVShow(c.Context(), sqlc.CreateTVShowParams{
|
||||||
TtImdb: ttShowID,
|
TtImdb: ttShowID,
|
||||||
Name: title,
|
Name: title,
|
||||||
}, scraperEpisodes)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to create tv show with episodes", "ttid", ttShowID, "error", err)
|
slog.Error("failed to create tv show", "ttid", ttShowID, "error", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": ErrorCreating})
|
return c.SendStatus(http.StatusInternalServerError)
|
||||||
return
|
}
|
||||||
|
|
||||||
|
slog.Info("ttshowid", "id", ttShow.ID)
|
||||||
|
for _, episode := range scraperEpisodes {
|
||||||
|
sqlcEpisodesParams := episode.ToEpisodeParams(ttShow.ID)
|
||||||
|
sqlcEpisode, err := hq.queries.CreateEpisodes(c.Context(), sqlcEpisodesParams)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to create episodes", "ttid", ttShowID, "error", err)
|
||||||
|
return c.SendStatus(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlcEpisodes = append(sqlcEpisodes, sqlcEpisode)
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("scraped seasons", "ttid", ttShowID, "title", title)
|
slog.Info("scraped seasons", "ttid", ttShowID, "title", title)
|
||||||
} else {
|
} else {
|
||||||
title = tvShow.Name
|
title = tvShow.Name
|
||||||
sqlcEpisodes, err = hq.Queries.GetEpisodes(c, tvShow.ID)
|
sqlcEpisodes, err = hq.queries.GetEpisodes(c.Context(), tvShow.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to get episodes", "ttid", ttShowID, "error", err)
|
slog.Error("failed to get episodes", "ttid", ttShowID, "error", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": ErrorGetting})
|
return c.SendStatus(http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := hq.Queries.IncreasePopularity(c, ttShowID); err != nil {
|
|
||||||
slog.Error("failed to increase popularity", "ttid", ttShowID, "error", err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": ErrorUpdating})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hq.queries.IncreasePopularity(c.Context(), ttShowID)
|
||||||
slog.Info("tv show exists", "ttid", ttShowID, "title", tvShow.Name)
|
slog.Info("tv show exists", "ttid", ttShowID, "title", tvShow.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
return c.JSON(fiber.Map{
|
||||||
"popularity": tvShow.Popularity,
|
"popularity": tvShow.Popularity,
|
||||||
"title": title,
|
"title": title,
|
||||||
"seasons": sqlcEpisodes,
|
"seasons": sqlcEpisodes,
|
||||||
|
|||||||
@ -3,8 +3,6 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"github.com/zepyrshut/rating-orama/internal/scraper"
|
"github.com/zepyrshut/rating-orama/internal/scraper"
|
||||||
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
||||||
)
|
)
|
||||||
@ -18,11 +16,8 @@ func (r *pgxRepository) CreateTvShowWithEpisodes(ctx context.Context, tvShow sql
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("episodes lenght", "episodes", len(episodes))
|
|
||||||
|
|
||||||
for _, episode := range episodes {
|
for _, episode := range episodes {
|
||||||
sqlcEpisodeParams := episode.ToEpisodeParams(tvShow.ID)
|
sqlcEpisodeParams := episode.ToEpisodeParams(tvShow.ID)
|
||||||
slog.Info("creating episode", "episode", sqlcEpisodeParams)
|
|
||||||
episode, err := qtx.CreateEpisodes(ctx, sqlcEpisodeParams)
|
episode, err := qtx.CreateEpisodes(ctx, sqlcEpisodeParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
|
||||||
"github.com/gocolly/colly"
|
"github.com/gocolly/colly"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
||||||
@ -27,7 +29,7 @@ type Episode struct {
|
|||||||
func (e Episode) ToEpisodeParams(tvShowID int32) sqlc.CreateEpisodesParams {
|
func (e Episode) ToEpisodeParams(tvShowID int32) sqlc.CreateEpisodesParams {
|
||||||
|
|
||||||
var date pgtype.Date
|
var date pgtype.Date
|
||||||
date.Scan(e.Released)
|
_ = date.Scan(e.Released)
|
||||||
|
|
||||||
return sqlc.CreateEpisodesParams{
|
return sqlc.CreateEpisodesParams{
|
||||||
TvShowID: tvShowID,
|
TvShowID: tvShowID,
|
||||||
@ -42,14 +44,19 @@ func (e Episode) ToEpisodeParams(tvShowID int32) sqlc.CreateEpisodesParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
titleSelector = "h2.sc-b8cc654b-9.dmvgRY"
|
titleSelector = "h2.sc-b8cc654b-9.dmvgRY"
|
||||||
seasonsSelector = "ul.ipc-tabs a[data-testid='tab-season-entry']"
|
seasonsSelector = "ul.ipc-tabs a[data-testid='tab-season-entry']"
|
||||||
episodesSelector = "section.sc-1e7f96be-0.ZaQIL"
|
episodeCardSelector = "article.sc-f8507e90-1.cHtpvn.episode-item-wrapper"
|
||||||
nextSeasonButtonSelector = "#next-season-btn"
|
seasonEpisodeAndTitleSelector = "div.ipc-title__text"
|
||||||
imdbEpisodesURL = "https://www.imdb.com/title/%s/episodes?season=%d"
|
releasedDateSelector = "span.sc-f2169d65-10.bYaARM"
|
||||||
visitURL = "https://www.imdb.com/title/%s/episodes"
|
plotSelector = "div.ipc-html-content-inner-div"
|
||||||
|
starRatingSelector = "span.ipc-rating-star--rating"
|
||||||
|
voteCountSelector = "span.ipc-rating-star--voteCount"
|
||||||
|
imdbEpisodesURL = "https://www.imdb.com/title/%s/episodes/?season=%d"
|
||||||
|
visitURL = "https://www.imdb.com/title/%s/episodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func ScrapeEpisodes(ttImdb string) (string, []Episode) {
|
func ScrapeEpisodes(ttImdb string) (string, []Episode) {
|
||||||
c := colly.NewCollector(
|
c := colly.NewCollector(
|
||||||
colly.AllowedDomains("imdb.com", "www.imdb.com"),
|
colly.AllowedDomains("imdb.com", "www.imdb.com"),
|
||||||
@ -77,7 +84,7 @@ func ScrapeEpisodes(ttImdb string) (string, []Episode) {
|
|||||||
|
|
||||||
c.OnScraped(func(r *colly.Response) {
|
c.OnScraped(func(r *colly.Response) {
|
||||||
seasonMap := make(map[int]bool)
|
seasonMap := make(map[int]bool)
|
||||||
uniqueSeasons := []int{}
|
var uniqueSeasons []int
|
||||||
slog.Info("scraped seasons", "seasons", seasons)
|
slog.Info("scraped seasons", "seasons", seasons)
|
||||||
for _, seasonNum := range seasons {
|
for _, seasonNum := range seasons {
|
||||||
if !seasonMap[seasonNum] {
|
if !seasonMap[seasonNum] {
|
||||||
@ -87,77 +94,121 @@ func ScrapeEpisodes(ttImdb string) (string, []Episode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sort.Ints(uniqueSeasons)
|
sort.Ints(uniqueSeasons)
|
||||||
|
|
||||||
episodeCollector := c.Clone()
|
episodeCollector := c.Clone()
|
||||||
|
|
||||||
episodeCollector.OnHTML(episodesSelector, func(e *colly.HTMLElement) {
|
episodeCollector.OnResponse(func(r *colly.Response) {
|
||||||
seasonEpisodes := extractEpisodesFromSeason(e.Text)
|
slog.Info("response", "url", r.Request.URL)
|
||||||
allSeasons = append(allSeasons, seasonEpisodes...)
|
season := extractEpisodesFromSeason(string(r.Body))
|
||||||
|
allSeasons = append(allSeasons, season...)
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, seasonNum := range uniqueSeasons {
|
for _, seasonNum := range uniqueSeasons {
|
||||||
seasonURL := fmt.Sprintf(imdbEpisodesURL, ttImdb, seasonNum)
|
seasonURL := fmt.Sprintf(imdbEpisodesURL, ttImdb, seasonNum)
|
||||||
slog.Info("visiting season", "url", seasonURL)
|
slog.Info("visiting season", "url", seasonURL)
|
||||||
episodeCollector.Visit(seasonURL)
|
_ = episodeCollector.Visit(seasonURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
episodeCollector.Wait()
|
episodeCollector.Wait()
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Visit(fmt.Sprintf(visitURL, ttImdb))
|
_ = c.Visit(fmt.Sprintf(visitURL, ttImdb))
|
||||||
c.Wait()
|
c.Wait()
|
||||||
|
|
||||||
slog.Info("scraped all seasons", "seasons", allSeasons)
|
slog.Info("scraped all seasons", "length", len(allSeasons))
|
||||||
return title, allSeasons
|
return title, allSeasons
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractEpisodesFromSeason(data string) []Episode {
|
func extractEpisodesFromSeason(data string) []Episode {
|
||||||
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
|
||||||
slog.Info("extracting episodes", "data", data)
|
if err != nil {
|
||||||
|
slog.Error("error parsing html")
|
||||||
const pattern = `(S\d+\.E\d+)\s∙\s(.*?)` +
|
return []Episode{}
|
||||||
`(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s` +
|
|
||||||
`(.*?),\s(\d{4})(.*?)` +
|
|
||||||
`(\d\.\d{1,2}\/10) \((\d+K)\)Rate`
|
|
||||||
|
|
||||||
re := regexp.MustCompile(pattern)
|
|
||||||
matches := re.FindAllStringSubmatch(data, -1)
|
|
||||||
|
|
||||||
episodes := make([]Episode, 0, len(matches))
|
|
||||||
|
|
||||||
slog.Info("matches", "num", len(matches))
|
|
||||||
|
|
||||||
for _, match := range matches {
|
|
||||||
var episode Episode
|
|
||||||
|
|
||||||
seasonEpisode := match[1]
|
|
||||||
|
|
||||||
name := strings.TrimSpace(match[2])
|
|
||||||
|
|
||||||
day := match[3]
|
|
||||||
dateRest := strings.TrimSpace(match[4])
|
|
||||||
year := match[5]
|
|
||||||
|
|
||||||
plot := strings.TrimSpace(match[6])
|
|
||||||
rate := match[7]
|
|
||||||
voteCount := match[8]
|
|
||||||
|
|
||||||
seasonNum := strings.TrimPrefix(strings.Split(seasonEpisode, ".")[0], "S")
|
|
||||||
episodeNum := strings.TrimPrefix(strings.Split(seasonEpisode, ".")[1], "E")
|
|
||||||
|
|
||||||
votesInt, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimSuffix(voteCount, "K"), "K"))
|
|
||||||
rateFloat, _ := strconv.ParseFloat(strings.TrimSuffix(rate, "/10"), 32)
|
|
||||||
|
|
||||||
episode.Name = name
|
|
||||||
episode.Episode, _ = strconv.Atoi(episodeNum)
|
|
||||||
episode.Season, _ = strconv.Atoi(seasonNum)
|
|
||||||
episode.Released, _ = time.Parse("Mon, Jan 2, 2006", fmt.Sprintf("%s, %s, %s", day, dateRest, year))
|
|
||||||
episode.Plot = plot
|
|
||||||
episode.Rate = float32(rateFloat)
|
|
||||||
episode.VoteCount = votesInt * 1000
|
|
||||||
|
|
||||||
episodes = append(episodes, episode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var episodes []Episode
|
||||||
|
doc.Find(episodeCardSelector).Each(func(i int, s *goquery.Selection) {
|
||||||
|
var episode Episode
|
||||||
|
|
||||||
|
seasonEpisodeTitle := s.Find(seasonEpisodeAndTitleSelector).Text()
|
||||||
|
episode.Season, episode.Episode, episode.Name = parseSeasonEpisodeTitle(seasonEpisodeTitle)
|
||||||
|
|
||||||
|
releasedDate := s.Find(releasedDateSelector).Text()
|
||||||
|
episode.Released = parseReleasedDate(releasedDate)
|
||||||
|
|
||||||
|
plot := s.Find(plotSelector).Text()
|
||||||
|
if plot == "Add a plot" {
|
||||||
|
episode.Plot = ""
|
||||||
|
} else {
|
||||||
|
episode.Plot = plot
|
||||||
|
}
|
||||||
|
|
||||||
|
starRating := s.Find(starRatingSelector).Text()
|
||||||
|
episode.Rate = parseStarRating(starRating)
|
||||||
|
|
||||||
|
voteCount := s.Find(voteCountSelector).Text()
|
||||||
|
episode.VoteCount = parseVoteCount(voteCount)
|
||||||
|
|
||||||
|
episodes = append(episodes, episode)
|
||||||
|
})
|
||||||
|
|
||||||
|
slog.Info("extracted episodes", "length", len(episodes))
|
||||||
return episodes
|
return episodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseSeasonEpisodeTitle(input string) (int, int, string) {
|
||||||
|
re := regexp.MustCompile(`S(\d+)\.E(\d+)\s*∙\s*(.+)`)
|
||||||
|
matches := re.FindStringSubmatch(input)
|
||||||
|
if len(matches) != 4 {
|
||||||
|
return 0, 0, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
seasonNum, err1 := strconv.Atoi(matches[1])
|
||||||
|
episodeNum, err2 := strconv.Atoi(matches[2])
|
||||||
|
name := strings.TrimSpace(matches[3])
|
||||||
|
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
return 0, 0, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return seasonNum, episodeNum, name
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseReleasedDate(releasedDate string) time.Time {
|
||||||
|
const layout = "Mon, Jan 2, 2006"
|
||||||
|
parsedDate, err := time.Parse(layout, releasedDate)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error parsing date", "date", releasedDate)
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return parsedDate
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStarRating(starRating string) float32 {
|
||||||
|
rating, err := strconv.ParseFloat(starRating, 32)
|
||||||
|
if err != nil || rating < 0 || rating > 10 {
|
||||||
|
slog.Warn("error parsing rating, out of limits", "rating", starRating)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return float32(rating)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVoteCount(voteCount string) int {
|
||||||
|
re := regexp.MustCompile(`\(([\d.]+)(K?)\)`)
|
||||||
|
matches := re.FindStringSubmatch(voteCount)
|
||||||
|
if len(matches) != 3 {
|
||||||
|
slog.Error("error parsing vote count", "count", voteCount)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := strconv.ParseFloat(matches[1], 64)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error parsing vote count", "count", voteCount)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches[2] == "K" {
|
||||||
|
num *= 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(num)
|
||||||
|
}
|
||||||
|
|||||||
117
core/internal/scraper/tvshow_test.go
Normal file
117
core/internal/scraper/tvshow_test.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package scraper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseSeasonEpisodeTitle(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
given string
|
||||||
|
expected struct {
|
||||||
|
seasonNum int
|
||||||
|
episodeNum int
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
{"S5.E1 ∙ Live Free or Die", struct {
|
||||||
|
seasonNum int
|
||||||
|
episodeNum int
|
||||||
|
name string
|
||||||
|
}{5, 1, "Live Free or Die"}},
|
||||||
|
{"S5.E13 ∙ To'hajiilee", struct {
|
||||||
|
seasonNum int
|
||||||
|
episodeNum int
|
||||||
|
name string
|
||||||
|
}{5, 13, "To'hajiilee"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.given, func(t *testing.T) {
|
||||||
|
seasonNum, episodeNum, name := parseSeasonEpisodeTitle(tt.given)
|
||||||
|
if seasonNum != tt.expected.seasonNum || episodeNum != tt.expected.episodeNum || name != tt.expected.name {
|
||||||
|
t.Errorf("parseSeasonEpisodeTitle(%s): expected %d, %d, %s, actual %d, %d, %s", tt.given, tt.expected.seasonNum, tt.expected.episodeNum, tt.expected.name, seasonNum, episodeNum, name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseReleasedDate(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
given string
|
||||||
|
expected time.Time
|
||||||
|
}{
|
||||||
|
{"", time.Time{}},
|
||||||
|
{"1", time.Time{}},
|
||||||
|
{"Sun, Feb 3, 2005", time.Date(2005, time.February, 3, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Mon, Jan 2, 2006", time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Tue, Mar 4, 2007", time.Date(2007, time.March, 4, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Wed, Apr 5, 2008", time.Date(2008, time.April, 5, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Thu, May 6, 2009", time.Date(2009, time.May, 6, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Fri, Jun 7, 2010", time.Date(2010, time.June, 7, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Sat, Jul 8, 2011", time.Date(2011, time.July, 8, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Sun, Aug 9, 2012", time.Date(2012, time.August, 9, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Mon, Sep 10, 2013", time.Date(2013, time.September, 10, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Tue, Oct 11, 2014", time.Date(2014, time.October, 11, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Wed, Nov 12, 2015", time.Date(2015, time.November, 12, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{"Thu, Dec 13, 2016", time.Date(2016, time.December, 13, 0, 0, 0, 0, time.UTC)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.given, func(t *testing.T) {
|
||||||
|
actual := parseReleasedDate(tt.given)
|
||||||
|
if actual != tt.expected {
|
||||||
|
t.Errorf("parseReleasedDate(%s): expected %v, actual %v", tt.given, tt.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseStarRating(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
given string
|
||||||
|
expected float32
|
||||||
|
}{
|
||||||
|
{"1", 1},
|
||||||
|
{"1.5", 1.5},
|
||||||
|
{"10", 10},
|
||||||
|
{"10.5", 0},
|
||||||
|
{"0", 0},
|
||||||
|
{"999", 0},
|
||||||
|
{"hello", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.given, func(t *testing.T) {
|
||||||
|
actual := parseStarRating(tt.given)
|
||||||
|
if actual != tt.expected {
|
||||||
|
t.Errorf("parseStarRating(%s): expected %f, actual %f", tt.given, tt.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseVoteCount(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
given string
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{" (148K)", 148000},
|
||||||
|
{" (8K)", 8000},
|
||||||
|
{" (12K)", 12000},
|
||||||
|
{" (1)", 1},
|
||||||
|
{" (10)", 10},
|
||||||
|
{" (100)", 100},
|
||||||
|
{" (1K)", 1000},
|
||||||
|
{" (1.9K)", 1900},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.given, func(t *testing.T) {
|
||||||
|
actual := parseVoteCount(tt.given)
|
||||||
|
if actual != tt.expected {
|
||||||
|
t.Errorf("parseVoteCount(%s): expected %d, actual %d", tt.given, tt.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ type Querier interface {
|
|||||||
GetEpisodes(ctx context.Context, tvShowID int32) ([]Episode, error)
|
GetEpisodes(ctx context.Context, tvShowID int32) ([]Episode, error)
|
||||||
IncreasePopularity(ctx context.Context, ttImdb string) error
|
IncreasePopularity(ctx context.Context, ttImdb string) error
|
||||||
SeasonAverageRating(ctx context.Context, arg SeasonAverageRatingParams) (float64, error)
|
SeasonAverageRating(ctx context.Context, arg SeasonAverageRatingParams) (float64, error)
|
||||||
|
SeasonMedianRating(ctx context.Context, arg SeasonMedianRatingParams) (float64, error)
|
||||||
TvShowAverageRating(ctx context.Context, tvShowID int32) (float64, error)
|
TvShowAverageRating(ctx context.Context, tvShowID int32) (float64, error)
|
||||||
TvShowMedianRating(ctx context.Context, tvShowID int32) (float64, error)
|
TvShowMedianRating(ctx context.Context, tvShowID int32) (float64, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,6 +101,7 @@ func (q *Queries) CreateTVShow(ctx context.Context, arg CreateTVShowParams) (TvS
|
|||||||
const getEpisodes = `-- name: GetEpisodes :many
|
const getEpisodes = `-- name: GetEpisodes :many
|
||||||
select id, tv_show_id, season, episode, released, name, plot, avg_rating, vote_count from "episodes"
|
select id, tv_show_id, season, episode, released, name, plot, avg_rating, vote_count from "episodes"
|
||||||
where tv_show_id = $1
|
where tv_show_id = $1
|
||||||
|
order by season, episode asc
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetEpisodes(ctx context.Context, tvShowID int32) ([]Episode, error) {
|
func (q *Queries) GetEpisodes(ctx context.Context, tvShowID int32) ([]Episode, error) {
|
||||||
@ -160,6 +161,23 @@ func (q *Queries) SeasonAverageRating(ctx context.Context, arg SeasonAverageRati
|
|||||||
return avg, err
|
return avg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const seasonMedianRating = `-- name: SeasonMedianRating :one
|
||||||
|
select percentile_cont(0.5) within group (order by avg_rating) from "episodes"
|
||||||
|
where tv_show_id = $1 and season = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type SeasonMedianRatingParams struct {
|
||||||
|
TvShowID int32 `json:"tv_show_id"`
|
||||||
|
Season int32 `json:"season"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SeasonMedianRating(ctx context.Context, arg SeasonMedianRatingParams) (float64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, seasonMedianRating, arg.TvShowID, arg.Season)
|
||||||
|
var percentile_cont float64
|
||||||
|
err := row.Scan(&percentile_cont)
|
||||||
|
return percentile_cont, err
|
||||||
|
}
|
||||||
|
|
||||||
const tvShowAverageRating = `-- name: TvShowAverageRating :one
|
const tvShowAverageRating = `-- name: TvShowAverageRating :one
|
||||||
select avg(avg_rating) from "episodes"
|
select avg(avg_rating) from "episodes"
|
||||||
where tv_show_id = $1
|
where tv_show_id = $1
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
version: "2"
|
version: "2"
|
||||||
sql:
|
sql:
|
||||||
- engine: "postgresql"
|
- engine: "postgresql"
|
||||||
schema: "./database/migrations/*"
|
schema: "./cmd/database/migrations/*"
|
||||||
queries: "./database/queries/*"
|
queries: "./cmd/database/queries/*"
|
||||||
gen:
|
gen:
|
||||||
go:
|
go:
|
||||||
package: "sqlc"
|
package: "sqlc"
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// TODO: Move to toolbox
|
|
||||||
func TimeParser(timeString string) (time.Time, error) {
|
|
||||||
if len(timeString) == 1 {
|
|
||||||
return time.Time{}, nil
|
|
||||||
}
|
|
||||||
if len(timeString) == 4 {
|
|
||||||
return time.Parse("2006", timeString)
|
|
||||||
}
|
|
||||||
if len(timeString) == 9 {
|
|
||||||
return time.Parse("Jan. 2006", timeString)
|
|
||||||
}
|
|
||||||
if len(timeString) == 10 {
|
|
||||||
return time.Parse("2 Jan 2006", timeString)
|
|
||||||
}
|
|
||||||
if len(timeString) == 11 {
|
|
||||||
if timeString[5:6] == "." {
|
|
||||||
return time.Parse("2 Jan. 2006", timeString)
|
|
||||||
} else {
|
|
||||||
return time.Parse("2 Jan 2006", timeString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Parse("2 Jan. 2006", timeString)
|
|
||||||
}
|
|
||||||
3284
views-dev/pnpm-lock.yaml
generated
Normal file
3284
views-dev/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user