feeds from empty struct
parent
e54c7a76f9
commit
19b6d180e7
422
src/feeds/db.go
422
src/feeds/db.go
|
|
@ -11,10 +11,227 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Feeds struct{}
|
type (
|
||||||
|
Feed struct {
|
||||||
|
Entry Entry
|
||||||
|
Version Version
|
||||||
|
Execution Execution
|
||||||
|
}
|
||||||
|
|
||||||
func New(ctx context.Context) (Feeds, error) {
|
Entry struct {
|
||||||
return Feeds{}, initDB(ctx)
|
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 ForEach(ctx context.Context, cb func(Feed) error) error {
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type id struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
ids, err := db.Query[id](ctx, `SELECT id FROM "feed.entries" WHERE deleted_at IS NULL`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
feed, err := Get(ctx, id.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := cb(feed); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(ctx context.Context, id string) (Feed, error) {
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return Feed{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 oldGet(ctx context.Context, id string) (Feed, error) {
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return Feed{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := 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 Executed(ctx context.Context, id string, version time.Time) error {
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
return db.Exec(ctx, `
|
||||||
|
INSERT INTO "feed.executions" (
|
||||||
|
entries_id,
|
||||||
|
versions_created_at,
|
||||||
|
executed_at
|
||||||
|
) VALUES (?, ?, ?);
|
||||||
|
`,
|
||||||
|
id, version, now,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Insert(ctx context.Context, url, cron string) (string, error) {
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Update(ctx context.Context, id string, url, cron *string) error {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(ctx context.Context, id string) error {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEntry(ctx context.Context, id string) (Entry, error) {
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return Entry{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initDB(ctx context.Context) error {
|
func initDB(ctx context.Context) error {
|
||||||
|
|
@ -72,202 +289,3 @@ func initDB(ctx context.Context) error {
|
||||||
|
|
||||||
return nil
|
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) ForEach(ctx context.Context, cb func(Feed) error) error {
|
|
||||||
type id struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
}
|
|
||||||
ids, err := db.Query[id](ctx, `SELECT id FROM "feed.entries" WHERE deleted_at IS NULL`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, id := range ids {
|
|
||||||
feed, err := f.Get(ctx, id.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := cb(feed); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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) Executed(ctx context.Context, id string, version time.Time) error {
|
|
||||||
now := time.Now()
|
|
||||||
return db.Exec(ctx, `
|
|
||||||
INSERT INTO "feed.executions" (
|
|
||||||
entries_id,
|
|
||||||
versions_created_at,
|
|
||||||
executed_at
|
|
||||||
) VALUES (?, ?, ?);
|
|
||||||
`,
|
|
||||||
id, version, now,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"show-rss/src/db"
|
"show-rss/src/db"
|
||||||
"show-rss/src/feeds"
|
"show-rss/src/feeds"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -13,41 +12,15 @@ func TestFeeds(t *testing.T) {
|
||||||
ctx, can := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, can := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer can()
|
defer can()
|
||||||
|
|
||||||
t.Run("same ctx", func(t *testing.T) {
|
|
||||||
ctx := db.Test(t, ctx)
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
if _, err := feeds.New(ctx); err != nil && ctx.Err() == nil {
|
|
||||||
t.Fatalf("failed %d: %v", i, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("new ctx", func(t *testing.T) {
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
if _, err := feeds.New(db.Test(t, ctx)); err != nil && ctx.Err() == nil {
|
|
||||||
t.Fatalf("failed %d: %v", i, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("crud", func(t *testing.T) {
|
t.Run("crud", func(t *testing.T) {
|
||||||
ctx := db.Test(t, ctx)
|
ctx := db.Test(t, ctx)
|
||||||
|
|
||||||
f, err := feeds.New(ctx)
|
id, err := feeds.Insert(ctx, "url", "cron")
|
||||||
if err != nil && ctx.Err() == nil {
|
|
||||||
t.Fatalf("failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := f.Insert(ctx, "url", "cron")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("cannot insert:", err)
|
t.Fatal("cannot insert:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := f.Get(ctx, id)
|
got, err := feeds.Get(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("cannot get:", err)
|
t.Fatal("cannot get:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -83,11 +56,11 @@ func TestFeeds(t *testing.T) {
|
||||||
t.Error("execution.version")
|
t.Error("execution.version")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := f.Executed(ctx, got.Entry.ID, got.Version.Created); err != nil {
|
if err := feeds.Executed(ctx, got.Entry.ID, got.Version.Created); err != nil {
|
||||||
t.Fatal("cannot executed:", err)
|
t.Fatal("cannot executed:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
got2, err := f.Get(ctx, id)
|
got2, err := feeds.Get(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("cannot get w executed:", err)
|
t.Fatal("cannot get w executed:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -105,10 +78,10 @@ func TestFeeds(t *testing.T) {
|
||||||
t.Errorf("changes after execution: was \n\t%+v but now \n\t%+v", got, got2)
|
t.Errorf("changes after execution: was \n\t%+v but now \n\t%+v", got, got2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := f.Executed(ctx, got.Entry.ID, got.Version.Created); err != nil {
|
if err := feeds.Executed(ctx, got.Entry.ID, got.Version.Created); err != nil {
|
||||||
t.Fatal("cannot executed again:", err)
|
t.Fatal("cannot executed again:", err)
|
||||||
}
|
}
|
||||||
got3, err := f.Get(ctx, id)
|
got3, err := feeds.Get(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("cannot get w executed again:", err)
|
t.Fatal("cannot get w executed again:", err)
|
||||||
} else if got2.Execution == got3.Execution {
|
} else if got2.Execution == got3.Execution {
|
||||||
|
|
@ -116,7 +89,7 @@ func TestFeeds(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
if err := f.ForEach(ctx, func(feed feeds.Feed) error {
|
if err := feeds.ForEach(ctx, func(feed feeds.Feed) error {
|
||||||
n += 1
|
n += 1
|
||||||
if feed != got3 {
|
if feed != got3 {
|
||||||
t.Errorf("for each yielded difference than last get")
|
t.Errorf("for each yielded difference than last get")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue