add change table.
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type changeRepository struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *changeRepository) Find(ctx context.Context, id string) (*models.Change, error) {
|
||||
row, err := psqlcore.New(r.db).SelectChangeByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.change(row)
|
||||
}
|
||||
|
||||
func (r *changeRepository) List(ctx context.Context, filter models.ChangeFilter) ([]*models.Change, error) {
|
||||
params := psqlcore.SelectChangesParams{LimitSize: 100}
|
||||
if filter.Keys != nil {
|
||||
params.FilterKeys = true
|
||||
keys := make([]string, len(filter.Keys))
|
||||
for i, ck := range filter.Keys {
|
||||
keys[i] = ck.String()
|
||||
}
|
||||
params.Keys = keys
|
||||
}
|
||||
if filter.Author != nil {
|
||||
params.FilterAuthor = true
|
||||
params.Author = *filter.Author
|
||||
}
|
||||
if filter.EarliestDate != nil {
|
||||
params.FilterEarliestDate = true
|
||||
params.EarliestDate = filter.EarliestDate.In(time.UTC)
|
||||
}
|
||||
if filter.LatestDate != nil {
|
||||
params.FilterLatestDate = true
|
||||
params.LatestDate = filter.LatestDate.In(time.UTC)
|
||||
}
|
||||
if filter.Limit != nil {
|
||||
if *filter.Limit <= 0 {
|
||||
params.LimitSize = 10000
|
||||
} else {
|
||||
params.LimitSize = int32(*filter.Limit)
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := psqlcore.New(r.db).SelectChanges(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.changes(rows)
|
||||
}
|
||||
|
||||
func (r *changeRepository) Insert(ctx context.Context, change models.Change) (*models.Change, error) {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
if !r.insertWithIDs || change.ID == "" {
|
||||
next, err := q.IncrementCounter(ctx, "data_change_id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.ID = fmt.Sprintf("Change_%d", next)
|
||||
} else {
|
||||
if len(change.ID) < 8 {
|
||||
return nil, errors.New("invalid change id")
|
||||
}
|
||||
|
||||
n, err := strconv.Atoi(change.ID[7:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = q.BumpCounter(ctx, psqlcore.BumpCounterParams{ID: "data_change_id", Value: int32(n)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
data, err := r.dataChange(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = q.InsertChange(ctx, psqlcore.InsertChangeParams(*data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &change, tx.Commit()
|
||||
}
|
||||
|
||||
func (r *changeRepository) Remove(ctx context.Context, change models.Change) error {
|
||||
return psqlcore.New(r.db).DeleteChange(ctx, change.ID)
|
||||
}
|
||||
|
||||
func (r *changeRepository) dataChange(change models.Change) (*psqlcore.DataChange, error) {
|
||||
objectsJSON, err := json.Marshal(change.ChangeObjectSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := make([]string, len(change.Keys))
|
||||
for i, ck := range change.Keys {
|
||||
keys[i] = ck.String()
|
||||
}
|
||||
|
||||
return &psqlcore.DataChange{
|
||||
ID: change.ID,
|
||||
Model: string(change.Model),
|
||||
Op: change.Op,
|
||||
Author: change.Author,
|
||||
Listed: change.Listed,
|
||||
Date: change.Date.In(time.UTC),
|
||||
Keys: keys,
|
||||
Objects: objectsJSON,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *changeRepository) change(row psqlcore.DataChange) (*models.Change, error) {
|
||||
objects := models.ChangeObjectSet{}
|
||||
|
||||
err := json.Unmarshal(row.Objects, &objects)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := make([]models.ChangeKey, len(row.Keys))
|
||||
for i := range keys {
|
||||
err := keys[i].Decode(row.Keys[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
model := models.ChangeModel(row.Model)
|
||||
if !model.IsValid() {
|
||||
return nil, errors.New("invalid model")
|
||||
}
|
||||
|
||||
return &models.Change{
|
||||
ID: row.ID,
|
||||
Model: model,
|
||||
Op: row.Op,
|
||||
Author: row.Author,
|
||||
Listed: row.Listed,
|
||||
Date: row.Date,
|
||||
Keys: keys,
|
||||
ChangeObjectSet: objects,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *changeRepository) changes(rows []psqlcore.DataChange) ([]*models.Change, error) {
|
||||
results := make([]*models.Change, len(rows))
|
||||
for i, row := range rows {
|
||||
change, err := r.change(row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results[i] = change
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -34,6 +34,12 @@ func Connect(cfg config.Database) (*DB, error) {
|
||||
if err := q.EnsureCounter(timeout, "data_character_id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := q.EnsureCounter(timeout, "data_change_id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := q.EnsureCounter(timeout, "data_log_short_id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DB{
|
||||
db: db,
|
||||
@@ -47,7 +53,7 @@ type DB struct {
|
||||
}
|
||||
|
||||
func (d *DB) Changes() repositories.ChangeRepository {
|
||||
panic("implement me")
|
||||
return &changeRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Channels() repositories.ChannelRepository {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE data_change (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
model TEXT NOT NULL,
|
||||
op TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
listed BOOLEAN NOT NULL,
|
||||
date TIMESTAMP NOT NULL,
|
||||
keys TEXT[] NOT NULL,
|
||||
objects JSONB NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS data_change;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,138 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// source: changes.sql
|
||||
|
||||
package psqlcore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const deleteChange = `-- name: DeleteChange :exec
|
||||
DELETE FROM data_change WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteChange(ctx context.Context, id string) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteChange, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const insertChange = `-- name: InsertChange :exec
|
||||
INSERT INTO data_change (id, model, op, author, listed, date, keys, objects)
|
||||
VALUES (
|
||||
$1::text, $2::text, $3::text, $4::text,
|
||||
$5::boolean, $6::timestamp, $7::text[],
|
||||
$8::jsonb
|
||||
)
|
||||
`
|
||||
|
||||
type InsertChangeParams struct {
|
||||
ID string `json:"id"`
|
||||
Model string `json:"model"`
|
||||
Op string `json:"op"`
|
||||
Author string `json:"author"`
|
||||
Listed bool `json:"listed"`
|
||||
Date time.Time `json:"date"`
|
||||
Keys []string `json:"keys"`
|
||||
Objects json.RawMessage `json:"objects"`
|
||||
}
|
||||
|
||||
func (q *Queries) InsertChange(ctx context.Context, arg InsertChangeParams) error {
|
||||
_, err := q.db.ExecContext(ctx, insertChange,
|
||||
arg.ID,
|
||||
arg.Model,
|
||||
arg.Op,
|
||||
arg.Author,
|
||||
arg.Listed,
|
||||
arg.Date,
|
||||
pq.Array(arg.Keys),
|
||||
arg.Objects,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const selectChangeByID = `-- name: SelectChangeByID :one
|
||||
SELECT id, model, op, author, listed, date, keys, objects FROM data_change WHERE id = $1 LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) SelectChangeByID(ctx context.Context, id string) (DataChange, error) {
|
||||
row := q.db.QueryRowContext(ctx, selectChangeByID, id)
|
||||
var i DataChange
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Model,
|
||||
&i.Op,
|
||||
&i.Author,
|
||||
&i.Listed,
|
||||
&i.Date,
|
||||
pq.Array(&i.Keys),
|
||||
&i.Objects,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const selectChanges = `-- name: SelectChanges :many
|
||||
SELECT id, model, op, author, listed, date, keys, objects FROM data_change
|
||||
WHERE ($1::bool = false OR keys && ($2::text[]))
|
||||
AND ($3::bool = false OR date >= $4::timestamp)
|
||||
AND ($5::bool = false OR date <= $6::timestamp)
|
||||
AND ($7::bool = false OR author = $8::text)
|
||||
LIMIT $9::int
|
||||
`
|
||||
|
||||
type SelectChangesParams struct {
|
||||
FilterKeys bool `json:"filter_keys"`
|
||||
Keys []string `json:"keys"`
|
||||
FilterEarliestDate bool `json:"filter_earliest_date"`
|
||||
EarliestDate time.Time `json:"earliest_date"`
|
||||
FilterLatestDate bool `json:"filter_latest_date"`
|
||||
LatestDate time.Time `json:"latest_date"`
|
||||
FilterAuthor bool `json:"filter_author"`
|
||||
Author string `json:"author"`
|
||||
LimitSize int32 `json:"limit_size"`
|
||||
}
|
||||
|
||||
func (q *Queries) SelectChanges(ctx context.Context, arg SelectChangesParams) ([]DataChange, error) {
|
||||
rows, err := q.db.QueryContext(ctx, selectChanges,
|
||||
arg.FilterKeys,
|
||||
pq.Array(arg.Keys),
|
||||
arg.FilterEarliestDate,
|
||||
arg.EarliestDate,
|
||||
arg.FilterLatestDate,
|
||||
arg.LatestDate,
|
||||
arg.FilterAuthor,
|
||||
arg.Author,
|
||||
arg.LimitSize,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []DataChange{}
|
||||
for rows.Next() {
|
||||
var i DataChange
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Model,
|
||||
&i.Op,
|
||||
&i.Author,
|
||||
&i.Listed,
|
||||
&i.Date,
|
||||
pq.Array(&i.Keys),
|
||||
&i.Objects,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
@@ -4,6 +4,8 @@ package psqlcore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CoreCounter struct {
|
||||
@@ -11,6 +13,17 @@ type CoreCounter struct {
|
||||
Value sql.NullInt32 `json:"value"`
|
||||
}
|
||||
|
||||
type DataChange struct {
|
||||
ID string `json:"id"`
|
||||
Model string `json:"model"`
|
||||
Op string `json:"op"`
|
||||
Author string `json:"author"`
|
||||
Listed bool `json:"listed"`
|
||||
Date time.Time `json:"date"`
|
||||
Keys []string `json:"keys"`
|
||||
Objects json.RawMessage `json:"objects"`
|
||||
}
|
||||
|
||||
type DataChannel struct {
|
||||
Name string `json:"name"`
|
||||
Logged bool `json:"logged"`
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
-- name: SelectChangeByID :one
|
||||
SELECT * FROM data_change WHERE id = $1 LIMIT 1;
|
||||
|
||||
-- name: SelectChanges :many
|
||||
SELECT * FROM data_change
|
||||
WHERE (sqlc.arg(filter_keys)::bool = false OR keys && (sqlc.arg(keys)::text[]))
|
||||
AND (sqlc.arg(filter_earliest_date)::bool = false OR date >= sqlc.arg(earliest_date)::timestamp)
|
||||
AND (sqlc.arg(filter_latest_date)::bool = false OR date <= sqlc.arg(latest_date)::timestamp)
|
||||
AND (sqlc.arg(filter_author)::bool = false OR author = sqlc.arg(author)::text)
|
||||
LIMIT sqlc.arg(limit_size)::int;
|
||||
|
||||
-- name: InsertChange :exec
|
||||
INSERT INTO data_change (id, model, op, author, listed, date, keys, objects)
|
||||
VALUES (
|
||||
sqlc.arg(id)::text, sqlc.arg(model)::text, sqlc.arg(op)::text, sqlc.arg(author)::text,
|
||||
sqlc.arg(listed)::boolean, sqlc.arg(date)::timestamp, sqlc.arg(keys)::text[],
|
||||
sqlc.arg(objects)::jsonb
|
||||
);
|
||||
|
||||
-- name: DeleteChange :exec
|
||||
DELETE FROM data_change WHERE id = $1;
|
||||
Reference in New Issue
Block a user