made the jump from remote api to local postgres
This commit is contained in:
parent
96246469cb
commit
4d207eed8b
|
@ -3,4 +3,6 @@
|
|||
*.db-wal
|
||||
emdb
|
||||
emdb-api
|
||||
public
|
||||
public
|
||||
*.sql
|
||||
*.sql.bak
|
2
Makefile
2
Makefile
|
@ -10,7 +10,7 @@ run-tui-local:
|
|||
EMDB_BASE_URL=http://localhost:8085/ EMDB_API_KEY=hoi go run ./cmd/terminal-client/main.go
|
||||
|
||||
run-tui:
|
||||
go run ./cmd/terminal-client/main.go
|
||||
go run ./terminal-client/main.go
|
||||
|
||||
run-md-export:
|
||||
go run ./cmd/markdown-export/main.go
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"code.ewintr.nl/emdb/cmd/api-service/job"
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
"code.ewintr.nl/emdb/job"
|
||||
)
|
||||
|
||||
type JobAPI struct {
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"code.ewintr.nl/emdb/cmd/api-service/job"
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
"code.ewintr.nl/emdb/job"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
|
|
|
@ -135,9 +135,9 @@ func NewSQLite(dbPath string) (*SQLite, error) {
|
|||
db: db,
|
||||
}
|
||||
|
||||
if err := s.migrate(sqliteMigrations); err != nil {
|
||||
return &SQLite{}, err
|
||||
}
|
||||
//if err := s.migrate(sqliteMigrations); err != nil {
|
||||
// return &SQLite{}, err
|
||||
//}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
@ -154,71 +154,71 @@ func (s *SQLite) Query(query string, args ...any) (*sql.Rows, error) {
|
|||
return s.db.Query(query, args...)
|
||||
}
|
||||
|
||||
func (s *SQLite) migrate(wanted []sqliteMigration) error {
|
||||
// admin table
|
||||
if _, err := s.db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS migration
|
||||
("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "query" TEXT)
|
||||
`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// find existing
|
||||
rows, err := s.db.Query(`SELECT query FROM migration ORDER BY id`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
}
|
||||
|
||||
existing := []sqliteMigration{}
|
||||
for rows.Next() {
|
||||
var query string
|
||||
if err := rows.Scan(&query); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
}
|
||||
existing = append(existing, sqliteMigration(query))
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
// compare
|
||||
missing, err := compareMigrations(wanted, existing)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
}
|
||||
|
||||
// execute missing
|
||||
for _, query := range missing {
|
||||
if _, err := s.db.Exec(string(query)); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
}
|
||||
|
||||
// register
|
||||
if _, err := s.db.Exec(`
|
||||
INSERT INTO migration
|
||||
(query) VALUES (?)
|
||||
`, query); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareMigrations(wanted, existing []sqliteMigration) ([]sqliteMigration, error) {
|
||||
needed := []sqliteMigration{}
|
||||
if len(wanted) < len(existing) {
|
||||
return []sqliteMigration{}, ErrNotEnoughSQLMigrations
|
||||
}
|
||||
|
||||
for i, want := range wanted {
|
||||
switch {
|
||||
case i >= len(existing):
|
||||
needed = append(needed, want)
|
||||
case want == existing[i]:
|
||||
// do nothing
|
||||
case want != existing[i]:
|
||||
return []sqliteMigration{}, fmt.Errorf("%w: %v", ErrIncompatibleSQLMigration, want)
|
||||
}
|
||||
}
|
||||
|
||||
return needed, nil
|
||||
}
|
||||
//func (s *SQLite) migrate(wanted []sqliteMigration) error {
|
||||
// // admin table
|
||||
// if _, err := s.db.Exec(`
|
||||
//CREATE TABLE IF NOT EXISTS migration
|
||||
//("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "query" TEXT)
|
||||
//`); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // find existing
|
||||
// rows, err := s.db.Query(`SELECT query FROM migration ORDER BY id`)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
// }
|
||||
//
|
||||
// existing := []sqliteMigration{}
|
||||
// for rows.Next() {
|
||||
// var query string
|
||||
// if err := rows.Scan(&query); err != nil {
|
||||
// return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
// }
|
||||
// existing = append(existing, sqliteMigration(query))
|
||||
// }
|
||||
// rows.Close()
|
||||
//
|
||||
// // compare
|
||||
// missing, err := compareMigrations(wanted, existing)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
// }
|
||||
//
|
||||
// // execute missing
|
||||
// for _, query := range missing {
|
||||
// if _, err := s.db.Exec(string(query)); err != nil {
|
||||
// return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
// }
|
||||
//
|
||||
// // register
|
||||
// if _, err := s.db.Exec(`
|
||||
//INSERT INTO migration
|
||||
//(query) VALUES (?)
|
||||
//`, query); err != nil {
|
||||
// return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//func compareMigrations(wanted, existing []sqliteMigration) ([]sqliteMigration, error) {
|
||||
// needed := []sqliteMigration{}
|
||||
// if len(wanted) < len(existing) {
|
||||
// return []sqliteMigration{}, ErrNotEnoughSQLMigrations
|
||||
// }
|
||||
//
|
||||
// for i, want := range wanted {
|
||||
// switch {
|
||||
// case i >= len(existing):
|
||||
// needed = append(needed, want)
|
||||
// case want == existing[i]:
|
||||
// // do nothing
|
||||
// case want != existing[i]:
|
||||
// return []sqliteMigration{}, fmt.Errorf("%w: %v", ErrIncompatibleSQLMigration, want)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return needed, nil
|
||||
//}
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
|
||||
"code.ewintr.nl/emdb/client"
|
||||
"code.ewintr.nl/emdb/cmd/api-service/handler"
|
||||
"code.ewintr.nl/emdb/cmd/api-service/job"
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
job2 "code.ewintr.nl/emdb/job"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -32,8 +32,8 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
jobQueue := job.NewJobQueue(db, logger)
|
||||
worker := job.NewWorker(jobQueue, moviestore.NewMovieRepository(db), moviestore.NewReviewRepository(db), client.NewIMDB(), logger)
|
||||
jobQueue := job2.NewJobQueue(db, logger)
|
||||
worker := job2.NewWorker(jobQueue, moviestore.NewMovieRepository(db), moviestore.NewReviewRepository(db), client.NewIMDB(), logger)
|
||||
go worker.Run()
|
||||
|
||||
apis := handler.APIIndex{
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
"code.ewintr.nl/emdb/storage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dbSQLite, err := moviestore.NewSQLite("./emdb.db")
|
||||
if err != nil {
|
||||
fmt.Printf("could not create new sqlite repo: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
pgConnStr := ""
|
||||
dbPostgres, err := storage.NewPostgres(pgConnStr)
|
||||
if err != nil {
|
||||
fmt.Printf("could not create new postgres repo: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
//fmt.Println("movies")
|
||||
//movieRepoSqlite := moviestore.NewMovieRepository(dbSQLite)
|
||||
//movieRepoPG := moviestore.NewMovieRepositoryPG(dbPostgres)
|
||||
//
|
||||
//movies, err := movieRepoSqlite.FindAll()
|
||||
//if err != nil {
|
||||
// fmt.Println(err)
|
||||
// os.Exit(1)
|
||||
//}
|
||||
//for _, movie := range movies {
|
||||
// if err := movieRepoPG.Store(movie); err != nil {
|
||||
// fmt.Println(err)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
//}
|
||||
fmt.Println("reviews")
|
||||
reviewRepoSqlite := moviestore.NewReviewRepository(dbSQLite)
|
||||
reviewRepoPG := storage.NewReviewRepositoryPG(dbPostgres)
|
||||
|
||||
reviews, err := reviewRepoSqlite.FindAll()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, review := range reviews {
|
||||
if err := reviewRepoPG.Store(review); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("success")
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"code.ewintr.nl/emdb/client"
|
||||
"code.ewintr.nl/emdb/cmd/terminal-client/tui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger := tui.NewLogger()
|
||||
tmdb, err := client.NewTMDB(os.Getenv("TMDB_API_KEY"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
emdb := client.NewEMDB(os.Getenv("EMDB_BASE_URL"), os.Getenv("EMDB_API_KEY"))
|
||||
|
||||
p, err := tui.New(emdb, tmdb, logger)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.SetProgram(p)
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
1
go.mod
1
go.mod
|
@ -10,6 +10,7 @@ require (
|
|||
github.com/charmbracelet/lipgloss v0.10.0
|
||||
github.com/cyruzin/golang-tmdb v1.6.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/muesli/termenv v0.15.2
|
||||
github.com/tmc/langchaingo v0.1.5
|
||||
modernc.org/sqlite v1.29.3
|
||||
|
|
2
go.sum
2
go.sum
|
@ -127,6 +127,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
|
|
|
@ -9,14 +9,15 @@ import (
|
|||
"time"
|
||||
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
"code.ewintr.nl/emdb/storage"
|
||||
)
|
||||
|
||||
type JobQueue struct {
|
||||
db *moviestore.SQLite
|
||||
db *storage.Postgres
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewJobQueue(db *moviestore.SQLite, logger *slog.Logger) *JobQueue {
|
||||
func NewJobQueue(db *storage.Postgres, logger *slog.Logger) *JobQueue {
|
||||
jq := &JobQueue{
|
||||
db: db,
|
||||
logger: logger.With("service", "jobqueue"),
|
||||
|
@ -38,7 +39,7 @@ func (jq *JobQueue) Run() {
|
|||
UPDATE job_queue
|
||||
SET status = 'todo'
|
||||
WHERE status = 'doing'
|
||||
AND strftime('%s', 'now') - strftime('%s', updated_at) > 2*24*60*60;`); err != nil {
|
||||
AND EXTRACT(EPOCH FROM now() - updated_at) > 2*24*60*60;`); err != nil {
|
||||
logger.Error("could not clean up job queue", "error", err)
|
||||
}
|
||||
}
|
||||
|
@ -50,8 +51,9 @@ func (jq *JobQueue) Add(movieID, action string) error {
|
|||
return errors.New("invalid action")
|
||||
}
|
||||
|
||||
_, err := jq.db.Exec(`INSERT INTO job_queue (action_id, action, status)
|
||||
VALUES (?, ?, 'todo')`, movieID, action)
|
||||
_, err := jq.db.Exec(`
|
||||
INSERT INTO job_queue (action_id, action, status)
|
||||
VALUES ($1, $2, 'todo');`, movieID, action)
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -68,9 +70,9 @@ func (jq *JobQueue) Next(t moviestore.JobType) (moviestore.Job, error) {
|
|||
SELECT id, action_id, action
|
||||
FROM job_queue
|
||||
WHERE status='todo'
|
||||
AND action IN %s
|
||||
AND action = ANY($1)
|
||||
ORDER BY id ASC
|
||||
LIMIT 1`, actionsStr)
|
||||
LIMIT 1;`, actionsStr)
|
||||
row := jq.db.QueryRow(query)
|
||||
var job moviestore.Job
|
||||
err := row.Scan(&job.ID, &job.ActionID, &job.Action)
|
||||
|
@ -85,7 +87,7 @@ LIMIT 1`, actionsStr)
|
|||
if _, err := jq.db.Exec(`
|
||||
UPDATE job_queue
|
||||
SET status='doing'
|
||||
WHERE id=?`, job.ID); err != nil {
|
||||
WHERE id=$1;`, job.ID); err != nil {
|
||||
logger.Error("could not set job to doing", "error")
|
||||
return moviestore.Job{}, err
|
||||
}
|
||||
|
@ -97,7 +99,7 @@ func (jq *JobQueue) MarkDone(id int) {
|
|||
logger := jq.logger.With("method", "markdone")
|
||||
if _, err := jq.db.Exec(`
|
||||
DELETE FROM job_queue
|
||||
WHERE id=?`, id); err != nil {
|
||||
WHERE id=$1;`, id); err != nil {
|
||||
logger.Error("could not mark job done", "error", err)
|
||||
}
|
||||
return
|
||||
|
@ -108,7 +110,7 @@ func (jq *JobQueue) MarkFailed(id int) {
|
|||
if _, err := jq.db.Exec(`
|
||||
UPDATE job_queue
|
||||
SET status='failed'
|
||||
WHERE id=?`, id); err != nil {
|
||||
WHERE id=$1;`, id); err != nil {
|
||||
logger.Error("could not mark job failed", "error", err)
|
||||
}
|
||||
return
|
||||
|
@ -118,7 +120,7 @@ func (jq *JobQueue) List() ([]moviestore.Job, error) {
|
|||
rows, err := jq.db.Query(`
|
||||
SELECT id, action_id, action, status, created_at, updated_at
|
||||
FROM job_queue
|
||||
ORDER BY id DESC`)
|
||||
ORDER BY id DESC;`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -138,15 +140,14 @@ ORDER BY id DESC`)
|
|||
func (jq *JobQueue) Delete(id string) error {
|
||||
if _, err := jq.db.Exec(`
|
||||
DELETE FROM job_queue
|
||||
WHERE id=?`, id); err != nil {
|
||||
WHERE id=$1;`, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (jq *JobQueue) DeleteAll() error {
|
||||
if _, err := jq.db.Exec(`
|
||||
DELETE FROM job_queue`); err != nil {
|
||||
if _, err := jq.db.Exec(`DELETE FROM job_queue;`); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
|
@ -0,0 +1,98 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type MovieRepositoryPG struct {
|
||||
db *Postgres
|
||||
}
|
||||
|
||||
func NewMovieRepositoryPG(db *Postgres) *MovieRepositoryPG {
|
||||
return &MovieRepositoryPG{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (mr *MovieRepositoryPG) Store(m moviestore.Movie) error {
|
||||
if m.ID == "" {
|
||||
m.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
directors := strings.Join(m.Directors, ",")
|
||||
if _, err := mr.db.Exec(`INSERT INTO movie (id, tmdb_id, imdb_id, title, english_title, year, directors, summary, watched_on, rating, comment)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
ON CONFLICT (id) DO UPDATE
|
||||
SET
|
||||
tmdb_id = EXCLUDED.tmdb_id,
|
||||
imdb_id = EXCLUDED.imdb_id,
|
||||
title = EXCLUDED.title,
|
||||
english_title = EXCLUDED.english_title,
|
||||
year = EXCLUDED.year,
|
||||
directors = EXCLUDED.directors,
|
||||
summary = EXCLUDED.summary,
|
||||
watched_on = EXCLUDED.watched_on,
|
||||
rating = EXCLUDED.rating,
|
||||
comment = EXCLUDED.comment;`,
|
||||
m.ID, m.TMDBID, m.IMDBID, m.Title, m.EnglishTitle, m.Year, directors, m.Summary, m.WatchedOn, m.Rating, m.Comment); err != nil {
|
||||
return fmt.Errorf("%w: %v", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mr *MovieRepositoryPG) Delete(id string) error {
|
||||
if _, err := mr.db.Exec(`DELETE FROM movie WHERE id=$1`, id); err != nil {
|
||||
return fmt.Errorf("%w: %v", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mr *MovieRepositoryPG) FindOne(id string) (moviestore.Movie, error) {
|
||||
row := mr.db.QueryRow(`
|
||||
SELECT id, tmdb_id, imdb_id, title, english_title, year, directors, summary, watched_on, rating, comment
|
||||
FROM movie
|
||||
WHERE id=$1`, id)
|
||||
if row.Err() != nil {
|
||||
return moviestore.Movie{}, row.Err()
|
||||
}
|
||||
|
||||
m := moviestore.Movie{
|
||||
ID: id,
|
||||
}
|
||||
var directors string
|
||||
if err := row.Scan(&m.ID, &m.TMDBID, &m.IMDBID, &m.Title, &m.EnglishTitle, &m.Year, &directors, &m.Summary, &m.WatchedOn, &m.Rating, &m.Comment); err != nil {
|
||||
return moviestore.Movie{}, fmt.Errorf("%w: %w", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
m.Directors = strings.Split(directors, ",")
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (mr *MovieRepositoryPG) FindAll() ([]moviestore.Movie, error) {
|
||||
rows, err := mr.db.Query(`
|
||||
SELECT id, tmdb_id, imdb_id, title, english_title, year, directors, summary, watched_on, rating, comment
|
||||
FROM movie`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
|
||||
movies := make([]moviestore.Movie, 0)
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
m := moviestore.Movie{}
|
||||
var directors string
|
||||
if err := rows.Scan(&m.ID, &m.TMDBID, &m.IMDBID, &m.Title, &m.EnglishTitle, &m.Year, &directors, &m.Summary, &m.WatchedOn, &m.Rating, &m.Comment); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
m.Directors = strings.Split(directors, ",")
|
||||
movies = append(movies, m)
|
||||
}
|
||||
|
||||
return movies, nil
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
type migration string
|
||||
|
||||
var migrations = []migration{
|
||||
`CREATE TABLE movie (
|
||||
"id" TEXT UNIQUE NOT NULL,
|
||||
"imdb_id" TEXT NOT NULL DEFAULT '',
|
||||
"title" TEXT NOT NULL DEFAULT '',
|
||||
"english_title" TEXT NOT NULL DEFAULT '',
|
||||
"year" INTEGER NOT NULL DEFAULT 0,
|
||||
"directors" TEXT NOT NULL DEFAULT '',
|
||||
"watched_on" TEXT NOT NULL DEFAULT '',
|
||||
"rating" INTEGER NOT NULL DEFAULT 0,
|
||||
"comment" TEXT NOT NULL DEFAULT '',
|
||||
"tmdb_id" INTEGER NOT NULL DEFAULT 0,
|
||||
"summary" TEXT NOT NULL DEFAULT ''
|
||||
);`,
|
||||
`CREATE TABLE movie_new (
|
||||
"id" TEXT UNIQUE NOT NULL,
|
||||
"imdb_id" TEXT UNIQUE NOT NULL DEFAULT '',
|
||||
"tmdb_id" INTEGER UNIQUE NOT NULL DEFAULT 0,
|
||||
"title" TEXT NOT NULL DEFAULT '',
|
||||
"english_title" TEXT NOT NULL DEFAULT '',
|
||||
"year" INTEGER NOT NULL DEFAULT 0,
|
||||
"directors" TEXT NOT NULL DEFAULT '',
|
||||
"summary" TEXT NOT NULL DEFAULT '',
|
||||
"watched_on" TEXT NOT NULL DEFAULT '',
|
||||
"rating" INTEGER NOT NULL DEFAULT 0,
|
||||
"comment" TEXT NOT NULL DEFAULT ''
|
||||
);`,
|
||||
`CREATE TABLE system ("latest_sync" INTEGER);`,
|
||||
`CREATE TABLE review (
|
||||
"id" TEXT UNIQUE NOT NULL,
|
||||
"movie_id" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL DEFAULT '',
|
||||
"url" TEXT NOT NULL DEFAULT '',
|
||||
"review" TEXT NOT NULL DEFAULT '',
|
||||
"references" TEXT NOT NULL DEFAULT '',
|
||||
"quality" INTEGER NOT NULL DEFAULT 0,
|
||||
"mentions" TEXT NOT NULL DEFAULT '',
|
||||
"movie_rating" INTEGER NOT NULL DEFAULT 0,
|
||||
"mentioned_titles" JSONB NOT NULL DEFAULT '[]'
|
||||
);`,
|
||||
`CREATE TABLE job_queue (
|
||||
"id" SERIAL PRIMARY KEY,
|
||||
"action_id" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL DEFAULT '',
|
||||
"status" TEXT NOT NULL DEFAULT '',
|
||||
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);`,
|
||||
}
|
||||
|
||||
type Postgres struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewPostgres(connStr string) (*Postgres, error) {
|
||||
db, err := sql.Open("postgres", connStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pg := &Postgres{
|
||||
db: db,
|
||||
}
|
||||
|
||||
if err := pg.migrate(migrations); err != nil {
|
||||
return &Postgres{}, err
|
||||
}
|
||||
|
||||
return pg, nil
|
||||
}
|
||||
|
||||
func (pg *Postgres) migrate(wanted []migration) error {
|
||||
// admin table
|
||||
if _, err := pg.db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS migration
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
query TEXT
|
||||
)`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// find existing
|
||||
rows, err := pg.db.Query(`SELECT query FROM migration ORDER BY id`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
|
||||
existing := []migration{}
|
||||
for rows.Next() {
|
||||
var query string
|
||||
if err := rows.Scan(&query); err != nil {
|
||||
return fmt.Errorf("%w: %v", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
existing = append(existing, migration(query))
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
// compare
|
||||
missing, err := compareMigrations(wanted, existing)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
|
||||
// execute missing
|
||||
for _, query := range missing {
|
||||
if _, err := pg.db.Exec(string(query)); err != nil {
|
||||
return fmt.Errorf("%w: %v", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
|
||||
// register
|
||||
if _, err := pg.db.Exec(`
|
||||
INSERT INTO migration
|
||||
(query) VALUES ($1)
|
||||
`, query); err != nil {
|
||||
return fmt.Errorf("%w: %v", moviestore.ErrSqliteFailure, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pg *Postgres) Exec(query string, args ...any) (sql.Result, error) {
|
||||
return pg.db.Exec(query, args...)
|
||||
}
|
||||
|
||||
func (pg *Postgres) QueryRow(query string, args ...any) *sql.Row {
|
||||
return pg.db.QueryRow(query, args...)
|
||||
}
|
||||
|
||||
func (pg *Postgres) Query(query string, args ...any) (*sql.Rows, error) {
|
||||
return pg.db.Query(query, args...)
|
||||
}
|
||||
|
||||
func compareMigrations(wanted, existing []migration) ([]migration, error) {
|
||||
needed := []migration{}
|
||||
if len(wanted) < len(existing) {
|
||||
return []migration{}, moviestore.ErrNotEnoughSQLMigrations
|
||||
}
|
||||
|
||||
for i, want := range wanted {
|
||||
switch {
|
||||
case i >= len(existing):
|
||||
needed = append(needed, want)
|
||||
case want == existing[i]:
|
||||
// do nothing
|
||||
case want != existing[i]:
|
||||
return []migration{}, fmt.Errorf("%w: %v", moviestore.ErrIncompatibleSQLMigration, want)
|
||||
}
|
||||
}
|
||||
|
||||
return needed, nil
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
)
|
||||
|
||||
//const (
|
||||
// ReviewSourceIMDB = "imdb"
|
||||
//
|
||||
// MentionsSeparator = "|"
|
||||
//)
|
||||
//
|
||||
//type ReviewSource string
|
||||
//
|
||||
//type Titles struct {
|
||||
// Movies []string `json:"movies"`
|
||||
// TVShows []string `json:"tvShows"`
|
||||
// Games []string `json:"games"`
|
||||
// Books []string `json:"books"`
|
||||
//}
|
||||
//
|
||||
//type Review struct {
|
||||
// ID string
|
||||
// MovieID string
|
||||
// Source ReviewSource
|
||||
// URL string
|
||||
// Review string
|
||||
// MovieRating int
|
||||
// Quality int
|
||||
// Titles Titles
|
||||
//}
|
||||
|
||||
type ReviewRepositoryPG struct {
|
||||
db *Postgres
|
||||
}
|
||||
|
||||
func NewReviewRepositoryPG(db *Postgres) *ReviewRepositoryPG {
|
||||
return &ReviewRepositoryPG{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *ReviewRepositoryPG) Store(r moviestore.Review) error {
|
||||
titles, err := json.Marshal(r.Titles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := rr.db.Exec(`INSERT INTO review (id, movie_id, source, url, review, movie_rating, quality, mentioned_titles)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (id) DO UPDATE SET movie_id = EXCLUDED.movie_id, source = EXCLUDED.source, url = EXCLUDED.url,
|
||||
review = EXCLUDED.review, movie_rating = EXCLUDED.movie_rating, quality = EXCLUDED.quality,
|
||||
mentioned_titles = EXCLUDED.mentioned_titles;`,
|
||||
r.ID, r.MovieID, r.Source, r.URL, r.Review, r.MovieRating, r.Quality, titles); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *ReviewRepositoryPG) FindOne(id string) (moviestore.Review, error) {
|
||||
row := rr.db.QueryRow(`
|
||||
SELECT id, movie_id, source, url, review, movie_rating, quality, mentioned_titles
|
||||
FROM review
|
||||
WHERE id=$1`, id)
|
||||
if row.Err() != nil {
|
||||
return moviestore.Review{}, row.Err()
|
||||
}
|
||||
|
||||
r := moviestore.Review{}
|
||||
var titles string
|
||||
if err := row.Scan(&r.ID, &r.MovieID, &r.Source, &r.URL, &r.Review, &r.MovieRating, &r.Quality, &titles); err != nil {
|
||||
return moviestore.Review{}, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(titles), &r.Titles); err != nil {
|
||||
return moviestore.Review{}, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (rr *ReviewRepositoryPG) FindByMovieID(movieID string) ([]moviestore.Review, error) {
|
||||
rows, err := rr.db.Query(`
|
||||
SELECT id, movie_id, source, url, review, movie_rating, quality, mentioned_titles
|
||||
FROM review
|
||||
WHERE movie_id=$1`, movieID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reviews := make([]moviestore.Review, 0)
|
||||
var titles string
|
||||
for rows.Next() {
|
||||
r := moviestore.Review{}
|
||||
if err := rows.Scan(&r.ID, &r.MovieID, &r.Source, &r.URL, &r.Review, &r.MovieRating, &r.Quality, &titles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(titles), &r.Titles); err != nil {
|
||||
return []moviestore.Review{}, err
|
||||
}
|
||||
reviews = append(reviews, r)
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
return reviews, nil
|
||||
}
|
||||
|
||||
func (rr *ReviewRepositoryPG) FindNextUnrated() (moviestore.Review, error) {
|
||||
row := rr.db.QueryRow(`
|
||||
SELECT id, movie_id, source, url, review, movie_rating, quality, mentioned_titles
|
||||
FROM review
|
||||
WHERE quality=0
|
||||
LIMIT 1`)
|
||||
if row.Err() != nil {
|
||||
return moviestore.Review{}, row.Err()
|
||||
}
|
||||
|
||||
r := moviestore.Review{}
|
||||
var titles string
|
||||
if err := row.Scan(&r.ID, &r.MovieID, &r.Source, &r.URL, &r.Review, &r.MovieRating, &r.Quality, &titles); err != nil {
|
||||
return moviestore.Review{}, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(titles), &r.Titles); err != nil {
|
||||
return moviestore.Review{}, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (rr *ReviewRepositoryPG) FindUnrated() ([]moviestore.Review, error) {
|
||||
rows, err := rr.db.Query(`
|
||||
SELECT id, movie_id, source, url, review, movie_rating, quality, mentioned_titles
|
||||
FROM review
|
||||
WHERE quality=0`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reviews := make([]moviestore.Review, 0)
|
||||
var titles string
|
||||
for rows.Next() {
|
||||
r := moviestore.Review{}
|
||||
if err := rows.Scan(&r.ID, &r.MovieID, &r.Source, &r.URL, &r.Review, &r.MovieRating, &r.Quality, &titles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(titles), &r.Titles); err != nil {
|
||||
return []moviestore.Review{}, err
|
||||
}
|
||||
reviews = append(reviews, r)
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
return reviews, nil
|
||||
}
|
||||
|
||||
func (rr *ReviewRepositoryPG) FindNextNoTitles() (moviestore.Review, error) {
|
||||
row := rr.db.QueryRow(`
|
||||
SELECT id, movie_id, source, url, review, movie_rating, quality, mentioned_titles
|
||||
FROM review
|
||||
WHERE mentioned_titles='{}'
|
||||
LIMIT 1`)
|
||||
if row.Err() != nil {
|
||||
return moviestore.Review{}, row.Err()
|
||||
}
|
||||
|
||||
r := moviestore.Review{}
|
||||
var titles string
|
||||
if err := row.Scan(&r.ID, &r.MovieID, &r.Source, &r.URL, &r.Review, &r.MovieRating, &r.Quality, &titles); err != nil {
|
||||
return moviestore.Review{}, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(titles), &r.Titles); err != nil {
|
||||
return moviestore.Review{}, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (rr *ReviewRepositoryPG) FindNoTitles() ([]moviestore.Review, error) {
|
||||
rows, err := rr.db.Query(`
|
||||
SELECT id, movie_id, source, url, review, movie_rating, quality, mentioned_titles
|
||||
FROM review
|
||||
WHERE mentioned_titles='{}'`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reviews := make([]moviestore.Review, 0)
|
||||
var titles string
|
||||
for rows.Next() {
|
||||
r := moviestore.Review{}
|
||||
if err := rows.Scan(&r.ID, &r.MovieID, &r.Source, &r.URL, &r.Review, &r.MovieRating, &r.Quality, &titles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(titles), &r.Titles); err != nil {
|
||||
return []moviestore.Review{}, err
|
||||
}
|
||||
reviews = append(reviews, r)
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
return reviews, nil
|
||||
}
|
||||
|
||||
func (rr *ReviewRepositoryPG) FindAll() ([]moviestore.Review, error) {
|
||||
rows, err := rr.db.Query(`
|
||||
SELECT id, movie_id, source, url, review, movie_rating, quality, mentioned_titles
|
||||
FROM review`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reviews := make([]moviestore.Review, 0)
|
||||
var titles string
|
||||
for rows.Next() {
|
||||
r := moviestore.Review{}
|
||||
if err := rows.Scan(&r.ID, &r.MovieID, &r.Source, &r.URL, &r.Review, &r.MovieRating, &r.Quality, &titles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(titles), &r.Titles); err != nil {
|
||||
return []moviestore.Review{}, err
|
||||
}
|
||||
reviews = append(reviews, r)
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
return reviews, nil
|
||||
}
|
||||
|
||||
func (rr *ReviewRepositoryPG) DeleteByMovieID(id string) error {
|
||||
if _, err := rr.db.Exec(`DELETE FROM review WHERE movie_id=$1`, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"code.ewintr.nl/emdb/client"
|
||||
"code.ewintr.nl/emdb/job"
|
||||
"code.ewintr.nl/emdb/storage"
|
||||
"code.ewintr.nl/emdb/terminal-client/tui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
tuiLogger := tui.NewLogger()
|
||||
tmdb, err := client.NewTMDB(os.Getenv("TMDB_API_KEY"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
//emdb := client.NewEMDB(os.Getenv("EMDB_BASE_URL"), os.Getenv("EMDB_API_KEY"))
|
||||
dbHost := os.Getenv("EMDB_DB_HOST")
|
||||
dbName := os.Getenv("EMDB_DB_NAME")
|
||||
dbUser := os.Getenv("EMDB_DB_USER")
|
||||
dbPassword := os.Getenv("EMDB_DB_PASSWORD")
|
||||
pgConnStr := fmt.Sprintf("host=%s user=%s password=%s dbname=%s sslmode=disable", dbHost, dbUser, dbPassword, dbName)
|
||||
dbPostgres, err := storage.NewPostgres(pgConnStr)
|
||||
if err != nil {
|
||||
fmt.Printf("could not create new postgres repo: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
movieRepo := storage.NewMovieRepositoryPG(dbPostgres)
|
||||
reviewRepo := storage.NewReviewRepositoryPG(dbPostgres)
|
||||
jobQueue := job.NewJobQueue(dbPostgres, logger)
|
||||
|
||||
p, err := tui.New(movieRepo, reviewRepo, jobQueue, tmdb, tuiLogger)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
tuiLogger.SetProgram(p)
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -5,12 +5,16 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.ewintr.nl/emdb/client"
|
||||
"code.ewintr.nl/emdb/job"
|
||||
"code.ewintr.nl/emdb/storage"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type baseModel struct {
|
||||
emdb *client.EMDB
|
||||
movieRepo *storage.MovieRepositoryPG
|
||||
reviewRepo *storage.ReviewRepositoryPG
|
||||
jobQueue *job.JobQueue
|
||||
tmdb *client.TMDB
|
||||
tabs *TabSet
|
||||
initialized bool
|
||||
|
@ -20,12 +24,14 @@ type baseModel struct {
|
|||
contentSize tea.WindowSizeMsg
|
||||
}
|
||||
|
||||
func NewBaseModel(emdb *client.EMDB, tmdb *client.TMDB, logger *Logger) (tea.Model, tea.Cmd) {
|
||||
func NewBaseModel(movieRepo *storage.MovieRepositoryPG, reviewRepo *storage.ReviewRepositoryPG, jobQueue *job.JobQueue, tmdb *client.TMDB, logger *Logger) (tea.Model, tea.Cmd) {
|
||||
logViewport := viewport.New(0, 0)
|
||||
logViewport.KeyMap = viewport.KeyMap{}
|
||||
|
||||
m := baseModel{
|
||||
emdb: emdb,
|
||||
movieRepo: movieRepo,
|
||||
reviewRepo: reviewRepo,
|
||||
jobQueue: jobQueue,
|
||||
tmdb: tmdb,
|
||||
tabs: NewTabSet(),
|
||||
logViewport: logViewport,
|
||||
|
@ -53,11 +59,11 @@ func (m baseModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.windowSize = msg
|
||||
if !m.initialized {
|
||||
var emdbTab, tmdbTab tea.Model
|
||||
emdbTab, cmd = NewTabEMDB(m.emdb, m.logger)
|
||||
emdbTab, cmd = NewTabEMDB(m.movieRepo, m.logger)
|
||||
cmds = append(cmds, cmd)
|
||||
tmdbTab, cmd = NewTabTMDB(m.emdb, m.tmdb, m.logger)
|
||||
tmdbTab, cmd = NewTabTMDB(m.movieRepo, m.jobQueue, m.tmdb, m.logger)
|
||||
cmds = append(cmds, cmd)
|
||||
reviewTab, cmd := NewTabReview(m.emdb, m.logger)
|
||||
reviewTab, cmd := NewTabReview(m.reviewRepo, m.logger)
|
||||
cmds = append(cmds, cmd)
|
||||
m.tabs.AddTab("emdb", "Watched movies", emdbTab)
|
||||
m.tabs.AddTab("review", "Review", reviewTab)
|
||||
|
@ -74,7 +80,7 @@ func (m baseModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
case NewMovie:
|
||||
m.Log(fmt.Sprintf("imported movie %s", msg.m.Title))
|
||||
m.tabs.Select("emdb")
|
||||
cmds = append(cmds, FetchMovieList(m.emdb))
|
||||
cmds = append(cmds, FetchMovieList(m.movieRepo))
|
||||
case error:
|
||||
m.Log(fmt.Sprintf("ERROR: %s", msg.Error()))
|
||||
default:
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.ewintr.nl/emdb/client"
|
||||
"code.ewintr.nl/emdb/storage"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/textarea"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
|
@ -24,7 +24,7 @@ type StoredMovie struct{}
|
|||
|
||||
type tabEMDB struct {
|
||||
initialized bool
|
||||
emdb *client.EMDB
|
||||
movieRepo *storage.MovieRepositoryPG
|
||||
mode string
|
||||
focused string
|
||||
colWidth int
|
||||
|
@ -38,7 +38,7 @@ type tabEMDB struct {
|
|||
logger *Logger
|
||||
}
|
||||
|
||||
func NewTabEMDB(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) {
|
||||
func NewTabEMDB(movieRepo *storage.MovieRepositoryPG, logger *Logger) (tea.Model, tea.Cmd) {
|
||||
del := list.NewDefaultDelegate()
|
||||
list := list.New([]list.Item{}, del, 0, 0)
|
||||
list.Title = "Movies"
|
||||
|
@ -65,7 +65,7 @@ func NewTabEMDB(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) {
|
|||
|
||||
m := tabEMDB{
|
||||
focused: "form",
|
||||
emdb: emdb,
|
||||
movieRepo: movieRepo,
|
||||
logger: logger,
|
||||
mode: "view",
|
||||
list: list,
|
||||
|
@ -76,7 +76,7 @@ func NewTabEMDB(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
|
||||
logger.Log("search emdb...")
|
||||
return m, FetchMovieList(emdb)
|
||||
return m, FetchMovieList(movieRepo)
|
||||
}
|
||||
|
||||
func (m tabEMDB) Init() tea.Cmd {
|
||||
|
@ -105,7 +105,7 @@ func (m tabEMDB) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
cmds = append(cmds, cmd)
|
||||
case StoredMovie:
|
||||
m.logger.Log("stored movie, fetching movie list")
|
||||
cmds = append(cmds, FetchMovieList(m.emdb))
|
||||
cmds = append(cmds, FetchMovieList(m.movieRepo))
|
||||
case tea.KeyMsg:
|
||||
switch m.mode {
|
||||
case "edit":
|
||||
|
@ -265,7 +265,7 @@ func (m *tabEMDB) StoreMovie() tea.Cmd {
|
|||
return fmt.Errorf("rating cannot be converted to an int: %w", err)
|
||||
}
|
||||
updatedMovie.m.Comment = m.inputComment.Value()
|
||||
if _, err := m.emdb.CreateMovie(updatedMovie.m); err != nil {
|
||||
if err := m.movieRepo.Store(updatedMovie.m); err != nil {
|
||||
return err
|
||||
}
|
||||
return StoredMovie{}
|
||||
|
@ -276,9 +276,9 @@ func (m *tabEMDB) Log(s string) {
|
|||
m.logger.Log(s)
|
||||
}
|
||||
|
||||
func FetchMovieList(emdb *client.EMDB) tea.Cmd {
|
||||
func FetchMovieList(movieRepo *storage.MovieRepositoryPG) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
ems, err := emdb.GetMovies()
|
||||
ems, err := movieRepo.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
|
@ -5,8 +5,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.ewintr.nl/emdb/client"
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
"code.ewintr.nl/emdb/storage"
|
||||
"github.com/charmbracelet/bubbles/textarea"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
|
@ -16,7 +16,7 @@ import (
|
|||
|
||||
type tabReview struct {
|
||||
initialized bool
|
||||
emdb *client.EMDB
|
||||
reviewRepo *storage.ReviewRepositoryPG
|
||||
width int
|
||||
height int
|
||||
mode string
|
||||
|
@ -28,7 +28,7 @@ type tabReview struct {
|
|||
logger *Logger
|
||||
}
|
||||
|
||||
func NewTabReview(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) {
|
||||
func NewTabReview(reviewRepo *storage.ReviewRepositoryPG, logger *Logger) (tea.Model, tea.Cmd) {
|
||||
reviewViewport := viewport.New(0, 0)
|
||||
//reviewViewport.KeyMap = viewport.KeyMap{}
|
||||
|
||||
|
@ -42,7 +42,7 @@ func NewTabReview(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) {
|
|||
inputMentions.CharLimit = 500
|
||||
|
||||
return &tabReview{
|
||||
emdb: emdb,
|
||||
reviewRepo: reviewRepo,
|
||||
mode: "view",
|
||||
reviewViewport: reviewViewport,
|
||||
inputQuality: inputQuality,
|
||||
|
@ -97,7 +97,7 @@ func (m *tabReview) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.formFocus = 0
|
||||
m.logger.Log("fetching next unrated review")
|
||||
cmds = append(cmds, m.inputQuality.Focus())
|
||||
cmds = append(cmds, FetchNextUnratedReview(m.emdb))
|
||||
cmds = append(cmds, FetchNextUnratedReview(m.reviewRepo))
|
||||
default:
|
||||
m.logger.Log(fmt.Sprintf("key: %s", msg.String()))
|
||||
m.reviewViewport, cmd = m.reviewViewport.Update(msg)
|
||||
|
@ -115,7 +115,7 @@ func (m *tabReview) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
case ReviewStored:
|
||||
m.logger.Log(fmt.Sprintf("stored review %s", msg))
|
||||
cmds = append(cmds, m.inputQuality.Focus())
|
||||
cmds = append(cmds, FetchNextUnratedReview(m.emdb))
|
||||
cmds = append(cmds, FetchNextUnratedReview(m.reviewRepo))
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
|
@ -210,7 +210,7 @@ func (m *tabReview) StoreReview() tea.Cmd {
|
|||
m.selectedReview.Quality = quality
|
||||
//m.selectedReview.Mentions = strings.Split(mentions, ",")
|
||||
|
||||
if err := m.emdb.UpdateReview(m.selectedReview); err != nil {
|
||||
if err := m.reviewRepo.Store(m.selectedReview); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -218,9 +218,9 @@ func (m *tabReview) StoreReview() tea.Cmd {
|
|||
}
|
||||
}
|
||||
|
||||
func FetchNextUnratedReview(emdb *client.EMDB) tea.Cmd {
|
||||
func FetchNextUnratedReview(reviewRepo *storage.ReviewRepositoryPG) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
review, err := emdb.GetNextUnratedReview()
|
||||
review, err := reviewRepo.FindNextUnrated()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
|
@ -5,13 +5,16 @@ import (
|
|||
|
||||
"code.ewintr.nl/emdb/client"
|
||||
"code.ewintr.nl/emdb/cmd/api-service/moviestore"
|
||||
"code.ewintr.nl/emdb/job"
|
||||
"code.ewintr.nl/emdb/storage"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type tabTMDB struct {
|
||||
emdb *client.EMDB
|
||||
movieRepo *storage.MovieRepositoryPG
|
||||
jobQueue *job.JobQueue
|
||||
tmdb *client.TMDB
|
||||
initialized bool
|
||||
focused string
|
||||
|
@ -20,11 +23,12 @@ type tabTMDB struct {
|
|||
logger *Logger
|
||||
}
|
||||
|
||||
func NewTabTMDB(emdb *client.EMDB, tmdb *client.TMDB, logger *Logger) (tea.Model, tea.Cmd) {
|
||||
func NewTabTMDB(movieRepo *storage.MovieRepositoryPG, jobQueue *job.JobQueue, tmdb *client.TMDB, logger *Logger) (tea.Model, tea.Cmd) {
|
||||
m := tabTMDB{
|
||||
emdb: emdb,
|
||||
tmdb: tmdb,
|
||||
logger: logger,
|
||||
movieRepo: movieRepo,
|
||||
jobQueue: jobQueue,
|
||||
tmdb: tmdb,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
return m, nil
|
||||
|
@ -127,15 +131,14 @@ func (m *tabTMDB) SearchTMDBCmd(query string) tea.Cmd {
|
|||
|
||||
func (m *tabTMDB) ImportMovieCmd(movie Movie) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
newMovie, err := m.emdb.CreateMovie(movie.m)
|
||||
if err != nil {
|
||||
if err := m.movieRepo.Store(movie.m); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.emdb.CreateJob(newMovie.ID, string(moviestore.ActionRefreshIMDBReviews)); err != nil {
|
||||
if err := m.jobQueue.Add(movie.m.ID, string(moviestore.ActionRefreshIMDBReviews)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return NewMovie(Movie{m: newMovie})
|
||||
return NewMovie(movie)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package tui
|
|||
|
||||
import (
|
||||
"code.ewintr.nl/emdb/client"
|
||||
"code.ewintr.nl/emdb/job"
|
||||
"code.ewintr.nl/emdb/storage"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
@ -50,11 +52,11 @@ func SelectPrevTab() tea.Cmd {
|
|||
}
|
||||
}
|
||||
|
||||
func New(emdb *client.EMDB, tmdb *client.TMDB, logger *Logger) (*tea.Program, error) {
|
||||
func New(movieRepo *storage.MovieRepositoryPG, reviewRepo *storage.ReviewRepositoryPG, jobQueue *job.JobQueue, tmdb *client.TMDB, logger *Logger) (*tea.Program, error) {
|
||||
logViewport := viewport.New(0, 0)
|
||||
logViewport.KeyMap = viewport.KeyMap{}
|
||||
|
||||
m, _ := NewBaseModel(emdb, tmdb, logger)
|
||||
m, _ := NewBaseModel(movieRepo, reviewRepo, jobQueue, tmdb, logger)
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
|
||||
return p, nil
|
Loading…
Reference in New Issue