update project and selectors
This commit is contained in:
parent
430892a512
commit
34d1088d9d
204
Makefile
Normal file
204
Makefile
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
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")
|
||||||
|
CORE_DIR := ./core
|
||||||
|
UI_DIR := ./ui
|
||||||
|
DOCS_DIR := ./docs
|
||||||
|
LIBRARIES_DIR := ./../libraries
|
||||||
|
PG_VERSION := 16.4-alpine3.20
|
||||||
|
DB_NAME := rating
|
||||||
|
MOD_NAME := rating-orama
|
||||||
|
|
||||||
|
.PHONY: sayhello
|
||||||
|
# Print Hello World
|
||||||
|
sayhello:
|
||||||
|
@echo "Hello World"
|
||||||
|
|
||||||
|
.PHONY: dockerize
|
||||||
|
# Creates a development database.
|
||||||
|
dockerize:
|
||||||
|
docker run --name $(DB_NAME)-db-dev -e POSTGRES_PASSWORD=secret -e POSTGRES_USER=developer -e POSTGRES_DB=$(DB_NAME) -p 5432:5432 -d postgres:$(PG_VERSION)
|
||||||
|
|
||||||
|
.PHONY: dockerize-test
|
||||||
|
# Creates a test database.
|
||||||
|
dockerize-test:
|
||||||
|
docker run --name $(DB_NAME)-db-test -e POSTGRES_PASSWORD=secret -e POSTGRES_USER=developer -e POSTGRES_DB=$(DB_NAME) -p 5433:5432 -d postgres:$(PG_VERSION)
|
||||||
|
|
||||||
|
.PHONY: undockerize
|
||||||
|
# Destroy a development database.
|
||||||
|
undockerize:
|
||||||
|
docker rm -f $(DB_NAME)-db-dev
|
||||||
|
|
||||||
|
.PHONY: undockerize-test
|
||||||
|
# Destroy a test database.
|
||||||
|
undockerize-test:
|
||||||
|
docker rm -f $(DB_NAME)-db-test
|
||||||
|
|
||||||
|
.PHONY: restart-db
|
||||||
|
# Restart a development database.
|
||||||
|
restart-db:
|
||||||
|
make undockerize
|
||||||
|
make dockerize
|
||||||
|
|
||||||
|
.PHONY: restart-db-test
|
||||||
|
# Restart a test database.
|
||||||
|
restart-db-test:
|
||||||
|
make undockerize-test
|
||||||
|
make dockerize-test
|
||||||
|
|
||||||
|
.PHONY: migrateup
|
||||||
|
# Migrate all schemas, triggers and data located in database/migrations.
|
||||||
|
migrateup:
|
||||||
|
migrate -path $(CORE_DIR)/cmd/database/migrations -database "postgresql://developer:secret@localhost:5432/$(DB_NAME)?sslmode=disable" -verbose up
|
||||||
|
|
||||||
|
.PHONY: migratedown
|
||||||
|
# Migrate all schemas, triggers and data located in database/migrations.
|
||||||
|
migratedown:
|
||||||
|
migrate -path $(CORE_DIR)/cmd/database/migrations -database "postgresql://developer:secret@localhost:5432/$(DB_NAME)?sslmode=disable" -verbose down
|
||||||
|
|
||||||
|
.PHONY: pg-dump
|
||||||
|
# Dump database to file.
|
||||||
|
pg-dump:
|
||||||
|
docker exec -e PGPASSWORD=secret $(DB_NAME)-db-dev pg_dump -U developer --column-inserts --data-only $(DB_NAME) > $(CORE_DIR)/cmd/database/data/data.sql
|
||||||
|
sed -i '1iSET session_replication_role = '\''replica'\'';' $(CORE_DIR)/cmd/database/data/data.sql
|
||||||
|
sed -i '$$aSET session_replication_role = '\''origin'\'';' $(CORE_DIR)/cmd/database/data/data.sql
|
||||||
|
|
||||||
|
.PHONY: pg-restore
|
||||||
|
# Restore database from file.
|
||||||
|
pg-restore:
|
||||||
|
docker cp $(CORE_DIR)/cmd/database/data/data.sql $(DB_NAME)-db-dev:/data.sql
|
||||||
|
docker exec -e PGPASSWORD=secret $(DB_NAME)-db-dev psql -U developer -d $(DB_NAME) -f data.sql
|
||||||
|
|
||||||
|
.PHONY: pg-docs
|
||||||
|
# Generate docs from database.
|
||||||
|
pg-docs:
|
||||||
|
java -jar $(LIBRARIES_DIR)/schemaspy-6.2.4.jar -t pgsql -dp $(LIBRARIES_DIR)/postgresql-42.7.4.jar -db $(DB_NAME) -host localhost -port 5432 -u developer -p secret -o $(DOCS_DIR)/database -vizjs
|
||||||
|
|
||||||
|
.PHONY: sqlc
|
||||||
|
# Generate or recreate SQLC queries.
|
||||||
|
sqlc:
|
||||||
|
cd $(CORE_DIR) && sqlc generate
|
||||||
|
make gomock
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
# Test all files and generate coverage file.
|
||||||
|
test:
|
||||||
|
cd $(CORE_DIR) && $(GO) test ./... -v -covermode=count -coverprofile=./benchmark/coverage.out $(PACKAGES)
|
||||||
|
|
||||||
|
.PHONY: gomock
|
||||||
|
# Generate mock files.
|
||||||
|
gomock:
|
||||||
|
cd $(CORE_DIR) && mockgen -package mock -destination internal/repository/mock/querier.go $(MOD_NAME)/internal/repository ExtendedQuerier
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
# Run project.
|
||||||
|
run:
|
||||||
|
cd $(CORE_DIR) && $(GO) run ./cmd/.
|
||||||
|
|
||||||
|
.PHONY: bench
|
||||||
|
# Run benchmarks.
|
||||||
|
bench:
|
||||||
|
cd $(CORE_DIR) && test -f benchmark/new_benchmark.txt && mv benchmark/new_benchmark.txt benchmark/old_benchmark.txt || true
|
||||||
|
cd $(CORE_DIR) && $(GO) test ./... -bench=. -count=10 -benchmem > benchmark/new_benchmark.txt
|
||||||
|
cd $(CORE_DIR) && benchstat benchmark/old_benchmark.txt benchmark/new_benchmark.txt > benchmark/benchstat.txt
|
||||||
|
|
||||||
|
.PHONY: recreate
|
||||||
|
# Destroy development DB and generate ones.
|
||||||
|
recreate:
|
||||||
|
echo "y" | make migratedown
|
||||||
|
make migrateup
|
||||||
|
|
||||||
|
.PHONY: tidy
|
||||||
|
# Runs a go mod tidy
|
||||||
|
tidy:
|
||||||
|
cd $(CORE_DIR) && $(GO) mod tidy
|
||||||
|
|
||||||
|
.PHONY: build-linux
|
||||||
|
# Build and generate linux executable.
|
||||||
|
build-linux:
|
||||||
|
cd $(CORE_DIR) && make tidy
|
||||||
|
cd $(CORE_DIR) && make remove-debug
|
||||||
|
cd $(CORE_DIR) && 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:
|
||||||
|
make test
|
||||||
|
make build-linux
|
||||||
|
docker build -t $(MOD_NAME):${version} -t $(MOD_NAME):latest .
|
||||||
|
|
||||||
|
.PHONY: remove-debug
|
||||||
|
# Remove all debug entries for reduce size binary.
|
||||||
|
remove-debug:
|
||||||
|
cd $(CORE_DIR) && find . -name "*.go" -type f -exec sed -i '/slog\.Debug/d' {} +
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
# Ensure consistent code formatting.
|
||||||
|
fmt:
|
||||||
|
cd $(CORE_DIR) && $(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:
|
||||||
|
cd $(CORE_DIR) && $(GO) vet $(VETPACKAGES)
|
||||||
|
|
||||||
|
.PHONY: tools
|
||||||
|
# Install tools (migrate and sqlc).
|
||||||
|
tools:
|
||||||
|
@if [ $(GO_VERSION) -gt 16 ]; then \
|
||||||
|
cd $(CORE_DIR) && $(GO) install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest; \
|
||||||
|
cd $(CORE_DIR) && $(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:
|
||||||
|
cd $(CORE_DIR) && @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
|
||||||
145
core/Makefile
145
core/Makefile
@ -1,145 +0,0 @@
|
|||||||
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:
|
|
||||||
@echo "Hello World"
|
|
||||||
|
|
||||||
.PHONY: dockerize
|
|
||||||
# Creates a development database.
|
|
||||||
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
|
|
||||||
|
|
||||||
.PHONY: undockerize
|
|
||||||
# Destroy a development database.
|
|
||||||
undockerize:
|
|
||||||
docker rm -f rating-db-dev
|
|
||||||
|
|
||||||
.PHONY: migrateup
|
|
||||||
# Migrate all schemas, triggers and data located in cmd/database/migrations.
|
|
||||||
migrateup:
|
|
||||||
migrate -path cmd/database/migrations -database "postgresql://developer:secret@localhost:5432/rating?sslmode=disable" -verbose up
|
|
||||||
|
|
||||||
.PHONY: sqlc
|
|
||||||
# Generate or recreate SQLC queries.
|
|
||||||
sqlc:
|
|
||||||
sqlc generate
|
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
# Test all files and generate coverage file.
|
|
||||||
test:
|
|
||||||
$(GO) test -v -covermode=count -coverprofile=coverage.out $(PACKAGES)
|
|
||||||
|
|
||||||
.PHONY: gomock
|
|
||||||
# Generate mock files.
|
|
||||||
gomock:
|
|
||||||
mockgen -package mock -destination internal/repository/mock/querier.go rating-orama/internal/repository ExtendedQuerier
|
|
||||||
|
|
||||||
.PHONY: run
|
|
||||||
# Run project.
|
|
||||||
run:
|
|
||||||
$(GO) run ./cmd/.
|
|
||||||
|
|
||||||
.PHONY: recreate
|
|
||||||
# Destroy development DB and generate ones.
|
|
||||||
recreate:
|
|
||||||
make undockerize
|
|
||||||
make dockerize
|
|
||||||
sleep 2
|
|
||||||
make migrateup
|
|
||||||
|
|
||||||
.PHONY: tidy
|
|
||||||
# Runs a go mod tidy
|
|
||||||
tidy:
|
|
||||||
$(GO) mod tidy
|
|
||||||
|
|
||||||
.PHONY: build-linux
|
|
||||||
# Build and generate linux executable.
|
|
||||||
build-linux:
|
|
||||||
make tidy
|
|
||||||
make remove-debug
|
|
||||||
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:
|
|
||||||
make test
|
|
||||||
make build-linux
|
|
||||||
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
|
|
||||||
@ -3,18 +3,15 @@ package main
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"gopher-toolbox/app"
|
|
||||||
"gopher-toolbox/db"
|
"gopher-toolbox/db"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/zepyrshut/rating-orama/internal/app"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed database/migrations
|
|
||||||
var database embed.FS
|
|
||||||
|
|
||||||
const version = "0.2.0-beta.20241116-4"
|
const version = "0.2.0-beta.20241116-4"
|
||||||
const appName = "rating-orama"
|
const appName = "rating-orama"
|
||||||
|
|
||||||
@ -22,21 +19,26 @@ func init() {
|
|||||||
gob.Register(map[string]string{})
|
gob.Register(map[string]string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed database/migrations
|
||||||
|
var database embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := app.New(version)
|
app := app.NewExtendedApp(appName, version, ".env")
|
||||||
r := fiber.New(fiber.Config{
|
app.Migrate(database)
|
||||||
|
f := fiber.New(fiber.Config{
|
||||||
AppName: appName,
|
AppName: appName,
|
||||||
})
|
})
|
||||||
|
|
||||||
dbPool := db.NewPGXPool(app.Database.DataSource)
|
|
||||||
defer dbPool.Close()
|
|
||||||
|
|
||||||
q := repository.NewPGXRepo(dbPool)
|
pgxPool := db.NewPGXPool(app.Database.DataSource)
|
||||||
h := handlers.New(app, q)
|
defer pgxPool.Close()
|
||||||
router(h, r)
|
|
||||||
|
r := repository.NewPGXRepo(pgxPool, app)
|
||||||
|
h := handlers.New(r, app)
|
||||||
|
router(h, f)
|
||||||
|
|
||||||
slog.Info("server started", "port", "8080", "version", version)
|
slog.Info("server started", "port", "8080", "version", version)
|
||||||
if err := r.Listen(":8080"); err != nil {
|
err := f.Listen(":8080")
|
||||||
|
if err != nil {
|
||||||
slog.Error("cannot start server", "error", err)
|
slog.Error("cannot start server", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,7 @@ require (
|
|||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // 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.28.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/text v0.19.0 // indirect
|
||||||
gopher-toolbox v0.0.0-00010101000000-000000000000
|
gopher-toolbox v0.0.0-00010101000000-000000000000
|
||||||
)
|
)
|
||||||
|
|||||||
@ -168,8 +168,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
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=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
|||||||
16
core/internal/app/app.go
Normal file
16
core/internal/app/app.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopher-toolbox/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExtendedApp struct {
|
||||||
|
app.App
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExtendedApp(appName, version, envDirectory string) *ExtendedApp {
|
||||||
|
app := app.New(appName, version, envDirectory)
|
||||||
|
return &ExtendedApp{
|
||||||
|
App: *app,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,30 +1,18 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"github.com/zepyrshut/rating-orama/internal/app"
|
||||||
|
|
||||||
"gopher-toolbox/app"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/zepyrshut/rating-orama/internal/repository"
|
"github.com/zepyrshut/rating-orama/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handlers struct {
|
type Handlers struct {
|
||||||
app *app.App
|
app *app.ExtendedApp
|
||||||
queries repository.ExtendedQuerier
|
queries repository.ExtendedQuerier
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(app *app.App, q repository.ExtendedQuerier) *Handlers {
|
func New(r repository.ExtendedQuerier, app *app.ExtendedApp) *Handlers {
|
||||||
return &Handlers{
|
return &Handlers{
|
||||||
app: app,
|
app: app,
|
||||||
queries: q,
|
queries: r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hq *Handlers) ToBeImplemented(c *fiber.Ctx) error {
|
|
||||||
return c.Status(http.StatusNotImplemented).JSON("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hq *Handlers) Ping(c *fiber.Ctx) error {
|
|
||||||
return c.JSON("pong")
|
|
||||||
}
|
|
||||||
|
|||||||
@ -13,6 +13,10 @@ import (
|
|||||||
func (hq *Handlers) GetTVShow(c *fiber.Ctx) error {
|
func (hq *Handlers) GetTVShow(c *fiber.Ctx) error {
|
||||||
ttShowID := c.Query("ttid")
|
ttShowID := c.Query("ttid")
|
||||||
|
|
||||||
|
if ttShowID == "" {
|
||||||
|
return c.SendStatus(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
var scraperEpisodes []scraper.Episode
|
var scraperEpisodes []scraper.Episode
|
||||||
var sqlcEpisodes []sqlc.Episode
|
var sqlcEpisodes []sqlc.Episode
|
||||||
@ -20,7 +24,7 @@ func (hq *Handlers) GetTVShow(c *fiber.Ctx) error {
|
|||||||
tvShow, err := hq.queries.CheckTVShowExists(c.Context(), 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
|
//TODO: make transactional
|
||||||
ttShow, err := hq.queries.CreateTVShow(c.Context(), sqlc.CreateTVShowParams{
|
ttShow, err := hq.queries.CreateTVShow(c.Context(), sqlc.CreateTVShowParams{
|
||||||
TtImdb: ttShowID,
|
TtImdb: ttShowID,
|
||||||
Name: title,
|
Name: title,
|
||||||
|
|||||||
@ -2,39 +2,44 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/zepyrshut/rating-orama/internal/app"
|
||||||
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pgxRepository struct {
|
type pgxRepository struct {
|
||||||
*sqlc.Queries
|
*sqlc.Queries
|
||||||
db *pgxpool.Pool
|
pool *pgxpool.Pool
|
||||||
|
app *app.ExtendedApp
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPGXRepo(db *pgxpool.Pool) ExtendedQuerier {
|
var _ ExtendedQuerier = &pgxRepository{}
|
||||||
|
|
||||||
|
func NewPGXRepo(pgx *pgxpool.Pool, app *app.ExtendedApp) ExtendedQuerier {
|
||||||
return &pgxRepository{
|
return &pgxRepository{
|
||||||
Queries: sqlc.New(db),
|
Queries: sqlc.New(pgx),
|
||||||
db: db,
|
pool: pgx,
|
||||||
|
app: app,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *pgxRepository) execTx(ctx context.Context, txFunc func(tx pgx.Tx) error) error {
|
func (r *pgxRepository) execTx(ctx context.Context, fn func(*sqlc.Queries) error) error {
|
||||||
slog.Info("starting transaction", "txFunc", txFunc)
|
tx, err := r.pool.Begin(ctx)
|
||||||
tx, err := r.db.Begin(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to start transaction", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback(ctx)
|
|
||||||
|
|
||||||
if err := txFunc(tx); err != nil {
|
|
||||||
slog.Error("failed to execute transaction", "error", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("committing transaction", "txFunc", txFunc)
|
q := sqlc.New(tx)
|
||||||
|
|
||||||
|
err = fn(q)
|
||||||
|
if err != nil {
|
||||||
|
if rbErr := tx.Rollback(ctx); rbErr != nil {
|
||||||
|
return fmt.Errorf("tx err: %v, rb err: %v", err, rbErr)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return tx.Commit(ctx)
|
return tx.Commit(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,12 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExtendedQuerier interface {
|
type ExtendedQuerier interface {
|
||||||
sqlc.Querier
|
sqlc.Querier
|
||||||
CreateTvShowWithEpisodes(ctx context.Context, tvShow sqlc.CreateTVShowParams, episodes []scraper.Episode) ([]sqlc.Episode, error)
|
CreateTvShowWithEpisodesTX(ctx context.Context, tvShow sqlc.CreateTVShowParams, episodes []scraper.Episode) ([]sqlc.Episode, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
"github.com/zepyrshut/rating-orama/internal/scraper"
|
|
||||||
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *pgxRepository) CreateTvShowWithEpisodes(ctx context.Context, tvShow sqlc.CreateTVShowParams, episodes []scraper.Episode) ([]sqlc.Episode, error) {
|
|
||||||
var sqlcEpisodes []sqlc.Episode
|
|
||||||
err := r.execTx(ctx, func(tx pgx.Tx) error {
|
|
||||||
qtx := r.WithTx(tx)
|
|
||||||
tvShow, err := qtx.CreateTVShow(ctx, tvShow)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, episode := range episodes {
|
|
||||||
sqlcEpisodeParams := episode.ToEpisodeParams(tvShow.ID)
|
|
||||||
episode, err := qtx.CreateEpisodes(ctx, sqlcEpisodeParams)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlcEpisodes = append(sqlcEpisodes, episode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return sqlcEpisodes, err
|
|
||||||
}
|
|
||||||
34
core/internal/repository/tvshow.tx.go
Normal file
34
core/internal/repository/tvshow.tx.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/zepyrshut/rating-orama/internal/scraper"
|
||||||
|
"github.com/zepyrshut/rating-orama/internal/sqlc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *pgxRepository) CreateTvShowWithEpisodesTX(ctx context.Context, tvShow sqlc.CreateTVShowParams, episodes []scraper.Episode) ([]sqlc.Episode, error) {
|
||||||
|
var err error
|
||||||
|
var episodesSqlc []sqlc.Episode
|
||||||
|
|
||||||
|
err = r.execTx(ctx, func(tx *sqlc.Queries) error {
|
||||||
|
tvShow, err := tx.CreateTVShow(ctx, tvShow)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, episode := range episodes {
|
||||||
|
sqlcEpisodeParams := episode.ToEpisodeParams(tvShow.ID)
|
||||||
|
episode, err := tx.CreateEpisodes(ctx, sqlcEpisodeParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
episodesSqlc = append(episodesSqlc, episode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return episodesSqlc, err
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ package scraper
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -43,20 +44,6 @@ func (e Episode) ToEpisodeParams(tvShowID int32) sqlc.CreateEpisodesParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
titleSelector = "h2.sc-b8cc654b-9.dmvgRY"
|
|
||||||
seasonsSelector = "ul.ipc-tabs a[data-testid='tab-season-entry']"
|
|
||||||
episodeCardSelector = "article.sc-f8507e90-1.cHtpvn.episode-item-wrapper"
|
|
||||||
seasonEpisodeAndTitleSelector = "div.ipc-title__text"
|
|
||||||
releasedDateSelector = "span.sc-f2169d65-10.bYaARM"
|
|
||||||
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"),
|
||||||
@ -70,7 +57,7 @@ func ScrapeEpisodes(ttImdb string) (string, []Episode) {
|
|||||||
var seasons []int
|
var seasons []int
|
||||||
var title string
|
var title string
|
||||||
|
|
||||||
c.OnHTML(seasonsSelector, func(e *colly.HTMLElement) {
|
c.OnHTML(os.Getenv("SEASON_SELECTOR"), func(e *colly.HTMLElement) {
|
||||||
seasonText := strings.TrimSpace(e.Text)
|
seasonText := strings.TrimSpace(e.Text)
|
||||||
seasonNum, err := strconv.Atoi(seasonText)
|
seasonNum, err := strconv.Atoi(seasonText)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -78,7 +65,7 @@ func ScrapeEpisodes(ttImdb string) (string, []Episode) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
c.OnHTML(titleSelector, func(e *colly.HTMLElement) {
|
c.OnHTML(os.Getenv("TITLE_SELECTOR"), func(e *colly.HTMLElement) {
|
||||||
title = e.Text
|
title = e.Text
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -103,7 +90,7 @@ func ScrapeEpisodes(ttImdb string) (string, []Episode) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for _, seasonNum := range uniqueSeasons {
|
for _, seasonNum := range uniqueSeasons {
|
||||||
seasonURL := fmt.Sprintf(imdbEpisodesURL, ttImdb, seasonNum)
|
seasonURL := fmt.Sprintf(os.Getenv("IMDB_EPISODES_URL"), ttImdb, seasonNum)
|
||||||
slog.Info("visiting season", "url", seasonURL)
|
slog.Info("visiting season", "url", seasonURL)
|
||||||
_ = episodeCollector.Visit(seasonURL)
|
_ = episodeCollector.Visit(seasonURL)
|
||||||
}
|
}
|
||||||
@ -111,7 +98,7 @@ func ScrapeEpisodes(ttImdb string) (string, []Episode) {
|
|||||||
episodeCollector.Wait()
|
episodeCollector.Wait()
|
||||||
})
|
})
|
||||||
|
|
||||||
_ = c.Visit(fmt.Sprintf(visitURL, ttImdb))
|
_ = c.Visit(fmt.Sprintf(os.Getenv("VISIT_URL"), ttImdb))
|
||||||
c.Wait()
|
c.Wait()
|
||||||
|
|
||||||
slog.Info("scraped all seasons", "length", len(allSeasons))
|
slog.Info("scraped all seasons", "length", len(allSeasons))
|
||||||
@ -126,26 +113,26 @@ func extractEpisodesFromSeason(data string) []Episode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var episodes []Episode
|
var episodes []Episode
|
||||||
doc.Find(episodeCardSelector).Each(func(i int, s *goquery.Selection) {
|
doc.Find(os.Getenv("EPISODE_CARD_SELECTOR")).Each(func(i int, s *goquery.Selection) {
|
||||||
var episode Episode
|
var episode Episode
|
||||||
|
|
||||||
seasonEpisodeTitle := s.Find(seasonEpisodeAndTitleSelector).Text()
|
seasonEpisodeTitle := s.Find(os.Getenv("SEASON_EPISODE_AND_TITLE_SELECTOR")).Text()
|
||||||
episode.Season, episode.Episode, episode.Name = parseSeasonEpisodeTitle(seasonEpisodeTitle)
|
episode.Season, episode.Episode, episode.Name = parseSeasonEpisodeTitle(seasonEpisodeTitle)
|
||||||
|
|
||||||
releasedDate := s.Find(releasedDateSelector).Text()
|
releasedDate := s.Find(os.Getenv("RELEASED_DATE_SELECTOR")).Text()
|
||||||
episode.Released = parseReleasedDate(releasedDate)
|
episode.Released = parseReleasedDate(releasedDate)
|
||||||
|
|
||||||
plot := s.Find(plotSelector).Text()
|
plot := s.Find(os.Getenv("PLOT_SELECTOR")).Text()
|
||||||
if plot == "Add a plot" {
|
if plot == "Add a plot" {
|
||||||
episode.Plot = ""
|
episode.Plot = ""
|
||||||
} else {
|
} else {
|
||||||
episode.Plot = plot
|
episode.Plot = plot
|
||||||
}
|
}
|
||||||
|
|
||||||
starRating := s.Find(starRatingSelector).Text()
|
starRating := s.Find(os.Getenv("STAR_RATING_SELECTOR")).Text()
|
||||||
episode.Rate = parseStarRating(starRating)
|
episode.Rate = parseStarRating(starRating)
|
||||||
|
|
||||||
voteCount := s.Find(voteCountSelector).Text()
|
voteCount := s.Find(os.Getenv("VOTE_COUNT_SELECTOR")).Text()
|
||||||
episode.VoteCount = parseVoteCount(voteCount)
|
episode.VoteCount = parseVoteCount(voteCount)
|
||||||
|
|
||||||
episodes = append(episodes, episode)
|
episodes = append(episodes, episode)
|
||||||
|
|||||||
9
core/internal/transfers/episodes.go
Normal file
9
core/internal/transfers/episodes.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package transfers
|
||||||
|
|
||||||
|
type EpisodePayload struct {
|
||||||
|
Title string
|
||||||
|
Season int
|
||||||
|
Episode int
|
||||||
|
Description string
|
||||||
|
Rating float64
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user