package feeds import ( "context" "fmt" "io" "show-rss/src/db" "strings" "time" "github.com/google/uuid" ) type Feeds struct{} func New(ctx context.Context) (Feeds, error) { return Feeds{}, initDB(ctx) } func initDB(ctx context.Context) error { if err := db.Exec(ctx, `CREATE TABLE IF NOT EXISTS database_version (v NUMBER, t TIMESTAMP)`); err != nil { return fmt.Errorf("failed to create database_version table: %w", err) } type DatabaseVersion struct { V int `json:"v"` T time.Time `json:"t"` } vs, err := db.Query[DatabaseVersion](ctx, `SELECT v, t FROM database_version ORDER BY v DESC LIMIT 1`) if err != nil { return err } var v DatabaseVersion if len(vs) > 0 { v = vs[0] } mods := []string{ `CREATE TABLE "feed.entries" ( id TEXT PRIMARY KEY NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, deleted_at TIMESTAMP )`, `CREATE TABLE "feed.versions" ( entries_id TEXT NOT NULL, created_at TIMESTAMP NOT NULL, PRIMARY KEY (entries_id, created_at), FOREIGN KEY (entries_id) REFERENCES "feed.entries" (id) )`, `ALTER TABLE "feed.versions" ADD COLUMN url TEXT NOT NULL`, `ALTER TABLE "feed.versions" ADD COLUMN cron TEXT NOT NULL DEFAULT '0 0 * * *'`, `CREATE TABLE "feed.executions" ( entries_id TEXT, versions_created_at TIMESTAMP NOT NULL, executed_at TIMESTAMP, FOREIGN KEY (entries_id, versions_created_at) REFERENCES "feed.versions" (entries_id, created_at) )`, } mods = append([]string{""}, mods...) for i := v.V + 1; i < len(mods); i++ { q := mods[i] q = strings.TrimSpace(q) q = strings.TrimSuffix(q, ";") q = fmt.Sprintf("BEGIN; %s; INSERT INTO database_version (v, t) VALUES (?, ?); COMMIT;", q) if err := db.Exec(ctx, q, i, time.Now()); err != nil { return fmt.Errorf("[%d] failed mod %s: %w", i, mods[i], err) } } return nil } type ( Feed struct { Entry Entry Version Version Execution Execution } Entry struct { ID string Created time.Time Updated time.Time Deleted time.Time } Version struct { Created time.Time URL string Cron string } Execution struct { Executed time.Time Version time.Time } ) func (f *Feeds) Get(ctx context.Context, id string) (Feed, error) { return db.QueryOne[Feed](ctx, ` WITH entry AS ( SELECT id AS ID, created_at AS Created, updated_at AS Updated, deleted_at AS Deleted FROM "feed.entries" WHERE id = ? ), execution AS ( SELECT executed_at AS Executed, versions_created_at AS Version FROM "feed.executions" WHERE entries_id = ? ORDER BY executed DESC LIMIT 1 ) SELECT entry.ID AS "Entry.ID", entry.Created AS "Entry.Created", entry.Updated AS "Entry.Updated", entry.Deleted AS "Entry.Deleted", versions.created_at AS "Version.Created", versions.url AS "Version.URL", versions.cron AS "Version.Cron", ( SELECT executed_at FROM "feed.executions" WHERE entries_id = entry.ID ORDER BY executed_at DESC LIMIT 1 ) AS "Execution.Executed", ( SELECT versions_created_at FROM "feed.executions" WHERE entries_id = entry.ID ORDER BY executed_at DESC LIMIT 1 ) AS "Execution.Version" FROM entry JOIN "feed.versions" version_entries_id ON version_entries_id.entries_id=entry.ID JOIN "feed.versions" versions ON versions.created_at=entry.Updated `, id, id) } func (f *Feeds) oldGet(ctx context.Context, id string) (Feed, error) { entry, err := f.getEntry(ctx, id) if err != nil { return Feed{}, err } version, err := db.QueryOne[Version](ctx, ` SELECT "feed.current_versions".versions_created_at AS Created, "feed.current_versions" AS URL, "feed.current_versions" AS Cron FROM "feed.current_versions" JOIN "feed.versions" versions_a ON "feed.current_versions".entries_id=versions_a.entries_id JOIN "feed.versions" versions_b ON "feed.current_versions".versions_created_at=versions_b.created_at WHERE "feed.current_versions".entries_id = ? `, id) if err != nil { return Feed{}, err } execution, err := db.QueryOne[Execution](ctx, ` SELECT "feed.executed_at" AS Executed, "feed.versions_created_at" AS VersionsCreated FROM "feed.executions" WHERE "feed.executions".entries_id = ? ORDER BY "feed.executions".executed_at DESC `, id) if err != nil { return Feed{}, err } return Feed{}, fmt.Errorf("%+v, %+v, %+v", entry, version, execution) } func (f *Feeds) Insert(ctx context.Context, url, cron string) (string, error) { now := time.Now() id := uuid.New().String() return id, db.Exec(ctx, ` BEGIN; INSERT INTO "feed.entries" ( id, created_at, updated_at ) VALUES ($1, $2, $3); INSERT INTO "feed.versions" ( entries_id, created_at, url, cron ) VALUES ($4, $5, $6, $7); COMMIT; `, id, now, now, id, now, url, cron, ) } func (f *Feeds) Update(ctx context.Context, id string, url, cron *string) error { return io.EOF } func (f *Feeds) Delete(ctx context.Context, id string) error { return io.EOF } func (f *Feeds) getEntry(ctx context.Context, id string) (Entry, error) { return db.QueryOne[Entry](ctx, ` SELECT id AS ID, created_at AS Created, updated_at AS Updated, deleted_at AS Deleted FROM "feed.entries" WHERE id = ? `, id) }