240 lines
5.3 KiB
Go
240 lines
5.3 KiB
Go
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)
|
|
}
|