made the jump from remote api to local postgres

This commit is contained in:
Erik Winter 2024-03-09 12:19:55 +01:00
parent 96246469cb
commit 4d207eed8b
25 changed files with 748 additions and 158 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@
emdb emdb
emdb-api emdb-api
public public
*.sql
*.sql.bak

View File

@ -10,7 +10,7 @@ run-tui-local:
EMDB_BASE_URL=http://localhost:8085/ EMDB_API_KEY=hoi go run ./cmd/terminal-client/main.go EMDB_BASE_URL=http://localhost:8085/ EMDB_API_KEY=hoi go run ./cmd/terminal-client/main.go
run-tui: run-tui:
go run ./cmd/terminal-client/main.go go run ./terminal-client/main.go
run-md-export: run-md-export:
go run ./cmd/markdown-export/main.go go run ./cmd/markdown-export/main.go

View File

@ -8,8 +8,8 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"code.ewintr.nl/emdb/cmd/api-service/job"
"code.ewintr.nl/emdb/cmd/api-service/moviestore" "code.ewintr.nl/emdb/cmd/api-service/moviestore"
"code.ewintr.nl/emdb/job"
) )
type JobAPI struct { type JobAPI struct {

View File

@ -10,8 +10,8 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"code.ewintr.nl/emdb/cmd/api-service/job"
"code.ewintr.nl/emdb/cmd/api-service/moviestore" "code.ewintr.nl/emdb/cmd/api-service/moviestore"
"code.ewintr.nl/emdb/job"
"github.com/google/uuid" "github.com/google/uuid"
) )

View File

@ -135,9 +135,9 @@ func NewSQLite(dbPath string) (*SQLite, error) {
db: db, db: db,
} }
if err := s.migrate(sqliteMigrations); err != nil { //if err := s.migrate(sqliteMigrations); err != nil {
return &SQLite{}, err // return &SQLite{}, err
} //}
return s, nil return s, nil
} }
@ -154,71 +154,71 @@ func (s *SQLite) Query(query string, args ...any) (*sql.Rows, error) {
return s.db.Query(query, args...) return s.db.Query(query, args...)
} }
func (s *SQLite) migrate(wanted []sqliteMigration) error { //func (s *SQLite) migrate(wanted []sqliteMigration) error {
// admin table // // admin table
if _, err := s.db.Exec(` // if _, err := s.db.Exec(`
CREATE TABLE IF NOT EXISTS migration //CREATE TABLE IF NOT EXISTS migration
("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "query" TEXT) //("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "query" TEXT)
`); err != nil { //`); err != nil {
return err // return err
} // }
//
// find existing // // find existing
rows, err := s.db.Query(`SELECT query FROM migration ORDER BY id`) // rows, err := s.db.Query(`SELECT query FROM migration ORDER BY id`)
if err != nil { // if err != nil {
return fmt.Errorf("%w: %v", ErrSqliteFailure, err) // return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} // }
//
existing := []sqliteMigration{} // existing := []sqliteMigration{}
for rows.Next() { // for rows.Next() {
var query string // var query string
if err := rows.Scan(&query); err != nil { // if err := rows.Scan(&query); err != nil {
return fmt.Errorf("%w: %v", ErrSqliteFailure, err) // return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} // }
existing = append(existing, sqliteMigration(query)) // existing = append(existing, sqliteMigration(query))
} // }
rows.Close() // rows.Close()
//
// compare // // compare
missing, err := compareMigrations(wanted, existing) // missing, err := compareMigrations(wanted, existing)
if err != nil { // if err != nil {
return fmt.Errorf("%w: %v", ErrSqliteFailure, err) // return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} // }
//
// execute missing // // execute missing
for _, query := range missing { // for _, query := range missing {
if _, err := s.db.Exec(string(query)); err != nil { // if _, err := s.db.Exec(string(query)); err != nil {
return fmt.Errorf("%w: %v", ErrSqliteFailure, err) // return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} // }
//
// register // // register
if _, err := s.db.Exec(` // if _, err := s.db.Exec(`
INSERT INTO migration //INSERT INTO migration
(query) VALUES (?) //(query) VALUES (?)
`, query); err != nil { //`, query); err != nil {
return fmt.Errorf("%w: %v", ErrSqliteFailure, err) // return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} // }
} // }
//
return nil // return nil
} //}
//
func compareMigrations(wanted, existing []sqliteMigration) ([]sqliteMigration, error) { //func compareMigrations(wanted, existing []sqliteMigration) ([]sqliteMigration, error) {
needed := []sqliteMigration{} // needed := []sqliteMigration{}
if len(wanted) < len(existing) { // if len(wanted) < len(existing) {
return []sqliteMigration{}, ErrNotEnoughSQLMigrations // return []sqliteMigration{}, ErrNotEnoughSQLMigrations
} // }
//
for i, want := range wanted { // for i, want := range wanted {
switch { // switch {
case i >= len(existing): // case i >= len(existing):
needed = append(needed, want) // needed = append(needed, want)
case want == existing[i]: // case want == existing[i]:
// do nothing // // do nothing
case want != existing[i]: // case want != existing[i]:
return []sqliteMigration{}, fmt.Errorf("%w: %v", ErrIncompatibleSQLMigration, want) // return []sqliteMigration{}, fmt.Errorf("%w: %v", ErrIncompatibleSQLMigration, want)
} // }
} // }
//
return needed, nil // return needed, nil
} //}

View File

@ -11,8 +11,8 @@ import (
"code.ewintr.nl/emdb/client" "code.ewintr.nl/emdb/client"
"code.ewintr.nl/emdb/cmd/api-service/handler" "code.ewintr.nl/emdb/cmd/api-service/handler"
"code.ewintr.nl/emdb/cmd/api-service/job"
"code.ewintr.nl/emdb/cmd/api-service/moviestore" "code.ewintr.nl/emdb/cmd/api-service/moviestore"
job2 "code.ewintr.nl/emdb/job"
) )
var ( var (
@ -32,8 +32,8 @@ func main() {
os.Exit(1) os.Exit(1)
} }
jobQueue := job.NewJobQueue(db, logger) jobQueue := job2.NewJobQueue(db, logger)
worker := job.NewWorker(jobQueue, moviestore.NewMovieRepository(db), moviestore.NewReviewRepository(db), client.NewIMDB(), logger) worker := job2.NewWorker(jobQueue, moviestore.NewMovieRepository(db), moviestore.NewReviewRepository(db), client.NewIMDB(), logger)
go worker.Run() go worker.Run()
apis := handler.APIIndex{ apis := handler.APIIndex{

57
cmd/import/main.go Normal file
View File

@ -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")
}

View File

@ -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
View File

@ -10,6 +10,7 @@ require (
github.com/charmbracelet/lipgloss v0.10.0 github.com/charmbracelet/lipgloss v0.10.0
github.com/cyruzin/golang-tmdb v1.6.0 github.com/cyruzin/golang-tmdb v1.6.0
github.com/google/uuid 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/muesli/termenv v0.15.2
github.com/tmc/langchaingo v0.1.5 github.com/tmc/langchaingo v0.1.5
modernc.org/sqlite v1.29.3 modernc.org/sqlite v1.29.3

2
go.sum
View File

@ -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/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 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= 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 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=

View File

@ -9,14 +9,15 @@ import (
"time" "time"
"code.ewintr.nl/emdb/cmd/api-service/moviestore" "code.ewintr.nl/emdb/cmd/api-service/moviestore"
"code.ewintr.nl/emdb/storage"
) )
type JobQueue struct { type JobQueue struct {
db *moviestore.SQLite db *storage.Postgres
logger *slog.Logger logger *slog.Logger
} }
func NewJobQueue(db *moviestore.SQLite, logger *slog.Logger) *JobQueue { func NewJobQueue(db *storage.Postgres, logger *slog.Logger) *JobQueue {
jq := &JobQueue{ jq := &JobQueue{
db: db, db: db,
logger: logger.With("service", "jobqueue"), logger: logger.With("service", "jobqueue"),
@ -38,7 +39,7 @@ func (jq *JobQueue) Run() {
UPDATE job_queue UPDATE job_queue
SET status = 'todo' SET status = 'todo'
WHERE status = 'doing' 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) 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") return errors.New("invalid action")
} }
_, err := jq.db.Exec(`INSERT INTO job_queue (action_id, action, status) _, err := jq.db.Exec(`
VALUES (?, ?, 'todo')`, movieID, action) INSERT INTO job_queue (action_id, action, status)
VALUES ($1, $2, 'todo');`, movieID, action)
return err return err
} }
@ -68,9 +70,9 @@ func (jq *JobQueue) Next(t moviestore.JobType) (moviestore.Job, error) {
SELECT id, action_id, action SELECT id, action_id, action
FROM job_queue FROM job_queue
WHERE status='todo' WHERE status='todo'
AND action IN %s AND action = ANY($1)
ORDER BY id ASC ORDER BY id ASC
LIMIT 1`, actionsStr) LIMIT 1;`, actionsStr)
row := jq.db.QueryRow(query) row := jq.db.QueryRow(query)
var job moviestore.Job var job moviestore.Job
err := row.Scan(&job.ID, &job.ActionID, &job.Action) err := row.Scan(&job.ID, &job.ActionID, &job.Action)
@ -85,7 +87,7 @@ LIMIT 1`, actionsStr)
if _, err := jq.db.Exec(` if _, err := jq.db.Exec(`
UPDATE job_queue UPDATE job_queue
SET status='doing' 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") logger.Error("could not set job to doing", "error")
return moviestore.Job{}, err return moviestore.Job{}, err
} }
@ -97,7 +99,7 @@ func (jq *JobQueue) MarkDone(id int) {
logger := jq.logger.With("method", "markdone") logger := jq.logger.With("method", "markdone")
if _, err := jq.db.Exec(` if _, err := jq.db.Exec(`
DELETE FROM job_queue DELETE FROM job_queue
WHERE id=?`, id); err != nil { WHERE id=$1;`, id); err != nil {
logger.Error("could not mark job done", "error", err) logger.Error("could not mark job done", "error", err)
} }
return return
@ -108,7 +110,7 @@ func (jq *JobQueue) MarkFailed(id int) {
if _, err := jq.db.Exec(` if _, err := jq.db.Exec(`
UPDATE job_queue UPDATE job_queue
SET status='failed' SET status='failed'
WHERE id=?`, id); err != nil { WHERE id=$1;`, id); err != nil {
logger.Error("could not mark job failed", "error", err) logger.Error("could not mark job failed", "error", err)
} }
return return
@ -118,7 +120,7 @@ func (jq *JobQueue) List() ([]moviestore.Job, error) {
rows, err := jq.db.Query(` rows, err := jq.db.Query(`
SELECT id, action_id, action, status, created_at, updated_at SELECT id, action_id, action, status, created_at, updated_at
FROM job_queue FROM job_queue
ORDER BY id DESC`) ORDER BY id DESC;`)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -138,15 +140,14 @@ ORDER BY id DESC`)
func (jq *JobQueue) Delete(id string) error { func (jq *JobQueue) Delete(id string) error {
if _, err := jq.db.Exec(` if _, err := jq.db.Exec(`
DELETE FROM job_queue DELETE FROM job_queue
WHERE id=?`, id); err != nil { WHERE id=$1;`, id); err != nil {
return err return err
} }
return nil return nil
} }
func (jq *JobQueue) DeleteAll() error { func (jq *JobQueue) DeleteAll() error {
if _, err := jq.db.Exec(` if _, err := jq.db.Exec(`DELETE FROM job_queue;`); err != nil {
DELETE FROM job_queue`); err != nil {
return err return err
} }
return nil return nil

98
storage/moviepg.go Normal file
View File

@ -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
}

165
storage/postgres.go Normal file
View File

@ -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
}

236
storage/reviewpg.go Normal file
View File

@ -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
}

47
terminal-client/main.go Normal file
View File

@ -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)
}
}

View File

@ -5,12 +5,16 @@ import (
"strings" "strings"
"code.ewintr.nl/emdb/client" "code.ewintr.nl/emdb/client"
"code.ewintr.nl/emdb/job"
"code.ewintr.nl/emdb/storage"
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
type baseModel struct { type baseModel struct {
emdb *client.EMDB movieRepo *storage.MovieRepositoryPG
reviewRepo *storage.ReviewRepositoryPG
jobQueue *job.JobQueue
tmdb *client.TMDB tmdb *client.TMDB
tabs *TabSet tabs *TabSet
initialized bool initialized bool
@ -20,12 +24,14 @@ type baseModel struct {
contentSize tea.WindowSizeMsg 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 := viewport.New(0, 0)
logViewport.KeyMap = viewport.KeyMap{} logViewport.KeyMap = viewport.KeyMap{}
m := baseModel{ m := baseModel{
emdb: emdb, movieRepo: movieRepo,
reviewRepo: reviewRepo,
jobQueue: jobQueue,
tmdb: tmdb, tmdb: tmdb,
tabs: NewTabSet(), tabs: NewTabSet(),
logViewport: logViewport, logViewport: logViewport,
@ -53,11 +59,11 @@ func (m baseModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.windowSize = msg m.windowSize = msg
if !m.initialized { if !m.initialized {
var emdbTab, tmdbTab tea.Model var emdbTab, tmdbTab tea.Model
emdbTab, cmd = NewTabEMDB(m.emdb, m.logger) emdbTab, cmd = NewTabEMDB(m.movieRepo, m.logger)
cmds = append(cmds, cmd) 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) cmds = append(cmds, cmd)
reviewTab, cmd := NewTabReview(m.emdb, m.logger) reviewTab, cmd := NewTabReview(m.reviewRepo, m.logger)
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
m.tabs.AddTab("emdb", "Watched movies", emdbTab) m.tabs.AddTab("emdb", "Watched movies", emdbTab)
m.tabs.AddTab("review", "Review", reviewTab) m.tabs.AddTab("review", "Review", reviewTab)
@ -74,7 +80,7 @@ func (m baseModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case NewMovie: case NewMovie:
m.Log(fmt.Sprintf("imported movie %s", msg.m.Title)) m.Log(fmt.Sprintf("imported movie %s", msg.m.Title))
m.tabs.Select("emdb") m.tabs.Select("emdb")
cmds = append(cmds, FetchMovieList(m.emdb)) cmds = append(cmds, FetchMovieList(m.movieRepo))
case error: case error:
m.Log(fmt.Sprintf("ERROR: %s", msg.Error())) m.Log(fmt.Sprintf("ERROR: %s", msg.Error()))
default: default:

View File

@ -5,7 +5,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"code.ewintr.nl/emdb/client" "code.ewintr.nl/emdb/storage"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/textinput"
@ -24,7 +24,7 @@ type StoredMovie struct{}
type tabEMDB struct { type tabEMDB struct {
initialized bool initialized bool
emdb *client.EMDB movieRepo *storage.MovieRepositoryPG
mode string mode string
focused string focused string
colWidth int colWidth int
@ -38,7 +38,7 @@ type tabEMDB struct {
logger *Logger 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() del := list.NewDefaultDelegate()
list := list.New([]list.Item{}, del, 0, 0) list := list.New([]list.Item{}, del, 0, 0)
list.Title = "Movies" list.Title = "Movies"
@ -65,7 +65,7 @@ func NewTabEMDB(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) {
m := tabEMDB{ m := tabEMDB{
focused: "form", focused: "form",
emdb: emdb, movieRepo: movieRepo,
logger: logger, logger: logger,
mode: "view", mode: "view",
list: list, list: list,
@ -76,7 +76,7 @@ func NewTabEMDB(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) {
} }
logger.Log("search emdb...") logger.Log("search emdb...")
return m, FetchMovieList(emdb) return m, FetchMovieList(movieRepo)
} }
func (m tabEMDB) Init() tea.Cmd { 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) cmds = append(cmds, cmd)
case StoredMovie: case StoredMovie:
m.logger.Log("stored movie, fetching movie list") m.logger.Log("stored movie, fetching movie list")
cmds = append(cmds, FetchMovieList(m.emdb)) cmds = append(cmds, FetchMovieList(m.movieRepo))
case tea.KeyMsg: case tea.KeyMsg:
switch m.mode { switch m.mode {
case "edit": case "edit":
@ -265,7 +265,7 @@ func (m *tabEMDB) StoreMovie() tea.Cmd {
return fmt.Errorf("rating cannot be converted to an int: %w", err) return fmt.Errorf("rating cannot be converted to an int: %w", err)
} }
updatedMovie.m.Comment = m.inputComment.Value() 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 err
} }
return StoredMovie{} return StoredMovie{}
@ -276,9 +276,9 @@ func (m *tabEMDB) Log(s string) {
m.logger.Log(s) m.logger.Log(s)
} }
func FetchMovieList(emdb *client.EMDB) tea.Cmd { func FetchMovieList(movieRepo *storage.MovieRepositoryPG) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
ems, err := emdb.GetMovies() ems, err := movieRepo.FindAll()
if err != nil { if err != nil {
return err return err
} }

View File

@ -5,8 +5,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"code.ewintr.nl/emdb/client"
"code.ewintr.nl/emdb/cmd/api-service/moviestore" "code.ewintr.nl/emdb/cmd/api-service/moviestore"
"code.ewintr.nl/emdb/storage"
"github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
@ -16,7 +16,7 @@ import (
type tabReview struct { type tabReview struct {
initialized bool initialized bool
emdb *client.EMDB reviewRepo *storage.ReviewRepositoryPG
width int width int
height int height int
mode string mode string
@ -28,7 +28,7 @@ type tabReview struct {
logger *Logger 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 := viewport.New(0, 0)
//reviewViewport.KeyMap = viewport.KeyMap{} //reviewViewport.KeyMap = viewport.KeyMap{}
@ -42,7 +42,7 @@ func NewTabReview(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) {
inputMentions.CharLimit = 500 inputMentions.CharLimit = 500
return &tabReview{ return &tabReview{
emdb: emdb, reviewRepo: reviewRepo,
mode: "view", mode: "view",
reviewViewport: reviewViewport, reviewViewport: reviewViewport,
inputQuality: inputQuality, inputQuality: inputQuality,
@ -97,7 +97,7 @@ func (m *tabReview) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.formFocus = 0 m.formFocus = 0
m.logger.Log("fetching next unrated review") m.logger.Log("fetching next unrated review")
cmds = append(cmds, m.inputQuality.Focus()) cmds = append(cmds, m.inputQuality.Focus())
cmds = append(cmds, FetchNextUnratedReview(m.emdb)) cmds = append(cmds, FetchNextUnratedReview(m.reviewRepo))
default: default:
m.logger.Log(fmt.Sprintf("key: %s", msg.String())) m.logger.Log(fmt.Sprintf("key: %s", msg.String()))
m.reviewViewport, cmd = m.reviewViewport.Update(msg) m.reviewViewport, cmd = m.reviewViewport.Update(msg)
@ -115,7 +115,7 @@ func (m *tabReview) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case ReviewStored: case ReviewStored:
m.logger.Log(fmt.Sprintf("stored review %s", msg)) m.logger.Log(fmt.Sprintf("stored review %s", msg))
cmds = append(cmds, m.inputQuality.Focus()) cmds = append(cmds, m.inputQuality.Focus())
cmds = append(cmds, FetchNextUnratedReview(m.emdb)) cmds = append(cmds, FetchNextUnratedReview(m.reviewRepo))
} }
return m, tea.Batch(cmds...) return m, tea.Batch(cmds...)
@ -210,7 +210,7 @@ func (m *tabReview) StoreReview() tea.Cmd {
m.selectedReview.Quality = quality m.selectedReview.Quality = quality
//m.selectedReview.Mentions = strings.Split(mentions, ",") //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 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 { return func() tea.Msg {
review, err := emdb.GetNextUnratedReview() review, err := reviewRepo.FindNextUnrated()
if err != nil { if err != nil {
return err return err
} }

View File

@ -5,13 +5,16 @@ import (
"code.ewintr.nl/emdb/client" "code.ewintr.nl/emdb/client"
"code.ewintr.nl/emdb/cmd/api-service/moviestore" "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/list"
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
type tabTMDB struct { type tabTMDB struct {
emdb *client.EMDB movieRepo *storage.MovieRepositoryPG
jobQueue *job.JobQueue
tmdb *client.TMDB tmdb *client.TMDB
initialized bool initialized bool
focused string focused string
@ -20,9 +23,10 @@ type tabTMDB struct {
logger *Logger 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{ m := tabTMDB{
emdb: emdb, movieRepo: movieRepo,
jobQueue: jobQueue,
tmdb: tmdb, tmdb: tmdb,
logger: logger, logger: logger,
} }
@ -127,15 +131,14 @@ func (m *tabTMDB) SearchTMDBCmd(query string) tea.Cmd {
func (m *tabTMDB) ImportMovieCmd(movie Movie) tea.Cmd { func (m *tabTMDB) ImportMovieCmd(movie Movie) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
newMovie, err := m.emdb.CreateMovie(movie.m) if err := m.movieRepo.Store(movie.m); err != nil {
if err != nil {
return err 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 err
} }
return NewMovie(Movie{m: newMovie}) return NewMovie(movie)
} }
} }

View File

@ -2,6 +2,8 @@ package tui
import ( import (
"code.ewintr.nl/emdb/client" "code.ewintr.nl/emdb/client"
"code.ewintr.nl/emdb/job"
"code.ewintr.nl/emdb/storage"
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "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 := viewport.New(0, 0)
logViewport.KeyMap = viewport.KeyMap{} logViewport.KeyMap = viewport.KeyMap{}
m, _ := NewBaseModel(emdb, tmdb, logger) m, _ := NewBaseModel(movieRepo, reviewRepo, jobQueue, tmdb, logger)
p := tea.NewProgram(m, tea.WithAltScreen()) p := tea.NewProgram(m, tea.WithAltScreen())
return p, nil return p, nil