diff --git a/cmd/rpdata-restore/main.go b/cmd/rpdata-restore/main.go index 73429ae..af0847c 100644 --- a/cmd/rpdata-restore/main.go +++ b/cmd/rpdata-restore/main.go @@ -24,6 +24,7 @@ var flagPassword = flag.String("password", "", "") var flagMechanism = flag.String("mechanism", "", "") var flagInputFile = flag.String("infile", "dump.zip", "The file to read from.") var flagIncludeKeys = flag.Bool("include-keys", false, "Whether to include the keys.") +var flagReplace = flag.Bool("replace", false, "Replace existing content") func main() { flag.Parse() @@ -82,6 +83,8 @@ func main() { hideList := make(map[string]bool) + log.Println("Loaded", parts[1], parts[2], "from archive.") + switch parts[1] { case "character": { @@ -91,6 +94,10 @@ func main() { log.Fatalln("Could not parse character:", parts[2], err) } + if *flagReplace { + _ = db.Characters().Delete(ctx, character) + } + _, err = db.Characters().Insert(ctx, character) if err != nil { log.Fatalln("Could not insert character:", parts[2], err) @@ -106,6 +113,10 @@ func main() { log.Fatalln("Could not parse channel:", parts[2], err) } + if *flagReplace { + _ = db.Channels().Remove(ctx, channel) + } + _, err = db.Channels().Insert(ctx, channel) if err != nil { log.Fatalln("Could not insert channel:", parts[2], err) @@ -121,6 +132,10 @@ func main() { log.Fatalln("Could not parse character:", parts[2], err) } + if *flagReplace { + _ = db.Changes().Remove(ctx, change) + } + _, err = db.Changes().Insert(ctx, change) if err != nil { log.Fatalln("Could not insert character:", parts[2], err) diff --git a/database/postgres/channel.go b/database/postgres/channel.go new file mode 100644 index 0000000..b92a57a --- /dev/null +++ b/database/postgres/channel.go @@ -0,0 +1,100 @@ +package postgres + +import ( + "context" + "database/sql" + "git.aiterp.net/rpdata/api/database/postgres/psqlcore" + "git.aiterp.net/rpdata/api/models" +) + +type channelRepository struct { + insertWithIDs bool + db *sql.DB +} + +func (r *channelRepository) Find(ctx context.Context, name string) (*models.Channel, error) { + row, err := psqlcore.New(r.db).SelectChannelByName(ctx, name) + if err != nil { + return nil, err + } + + return r.channel(row), nil +} + +func (r *channelRepository) List(ctx context.Context, filter models.ChannelFilter) ([]*models.Channel, error) { + params := psqlcore.SelectChannelsParams{ + LimitSize: 1000, + } + + if filter.Names != nil { + params.FilterName = true + params.Names = filter.Names + } + if filter.LocationName != nil { + params.FilterLocationName = true + params.LocationName = *filter.LocationName + } + if filter.EventName != nil { + params.FilterEventName = true + params.EventName = *filter.EventName + } + if filter.Limit > 0 { + params.LimitSize = int32(filter.Limit) + } + + rows, err := psqlcore.New(r.db).SelectChannels(ctx, params) + if err != nil { + return nil, err + } + + return r.channels(rows), nil +} + +func (r *channelRepository) Insert(ctx context.Context, channel models.Channel) (*models.Channel, error) { + err := psqlcore.New(r.db).InsertChannel(ctx, psqlcore.InsertChannelParams(channel)) + if err != nil { + return nil, err + } + + return &channel, nil +} + +func (r *channelRepository) Update(ctx context.Context, channel models.Channel, update models.ChannelUpdate) (*models.Channel, error) { + channel.ApplyUpdate(update) + + err := psqlcore.New(r.db).UpdateChannel(ctx, psqlcore.UpdateChannelParams{ + Name: channel.Name, + Logged: channel.Logged, + Hub: channel.Hub, + EventName: channel.EventName, + LocationName: channel.LocationName, + }) + if err != nil { + return nil, err + } + + return &channel, nil +} + +func (r *channelRepository) Remove(ctx context.Context, channel models.Channel) error { + return psqlcore.New(r.db).DeleteChannel(ctx, channel.Name) +} + +func (r *channelRepository) channel(row psqlcore.DataChannel) *models.Channel { + return &models.Channel{ + Name: row.Name, + Logged: row.Logged, + Hub: row.Hub, + EventName: row.EventName, + LocationName: row.LocationName, + } +} + +func (r *channelRepository) channels(rows []psqlcore.DataChannel) []*models.Channel { + results := make([]*models.Channel, 0, len(rows)) + for _, row := range rows { + results = append(results, r.channel(row)) + } + + return results +} diff --git a/database/postgres/db.go b/database/postgres/db.go index 5848dc9..d484171 100644 --- a/database/postgres/db.go +++ b/database/postgres/db.go @@ -51,7 +51,7 @@ func (d *DB) Changes() repositories.ChangeRepository { } func (d *DB) Channels() repositories.ChannelRepository { - panic("implement me") + return &channelRepository{insertWithIDs: d.insertWithIDs, db: d.db} } func (d *DB) Characters() repositories.CharacterRepository { diff --git a/database/postgres/migrations/20210322124407_create_table_channel.sql b/database/postgres/migrations/20210322124407_create_table_channel.sql new file mode 100644 index 0000000..b115207 --- /dev/null +++ b/database/postgres/migrations/20210322124407_create_table_channel.sql @@ -0,0 +1,15 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE data_channel ( + name TEXT NOT NULL PRIMARY KEY, + logged BOOLEAN NOT NULL, + hub BOOLEAN NOT NULL, + event_name TEXT NOT NULL, + location_name TEXT NOT NULL +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE IF EXISTS data_channel; +-- +goose StatementEnd diff --git a/database/postgres/psqlcore/channels.sql.go b/database/postgres/psqlcore/channels.sql.go new file mode 100644 index 0000000..b9eebb8 --- /dev/null +++ b/database/postgres/psqlcore/channels.sql.go @@ -0,0 +1,152 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: channels.sql + +package psqlcore + +import ( + "context" + + "github.com/lib/pq" +) + +const deleteChannel = `-- name: DeleteChannel :exec +DELETE FROM data_channel WHERE name=$1 +` + +func (q *Queries) DeleteChannel(ctx context.Context, name string) error { + _, err := q.db.ExecContext(ctx, deleteChannel, name) + return err +} + +const insertChannel = `-- name: InsertChannel :exec +INSERT INTO data_channel (name, logged, hub, event_name, location_name) +VALUES ( + $1::text, + $2::boolean, $3::boolean, + $4::text, $5::text +) +` + +type InsertChannelParams struct { + Name string `json:"name"` + Logged bool `json:"logged"` + Hub bool `json:"hub"` + EventName string `json:"event_name"` + LocationName string `json:"location_name"` +} + +func (q *Queries) InsertChannel(ctx context.Context, arg InsertChannelParams) error { + _, err := q.db.ExecContext(ctx, insertChannel, + arg.Name, + arg.Logged, + arg.Hub, + arg.EventName, + arg.LocationName, + ) + return err +} + +const selectChannelByName = `-- name: SelectChannelByName :one +SELECT name, logged, hub, event_name, location_name FROM data_channel WHERE name = $1 LIMIT 1 +` + +func (q *Queries) SelectChannelByName(ctx context.Context, name string) (DataChannel, error) { + row := q.db.QueryRowContext(ctx, selectChannelByName, name) + var i DataChannel + err := row.Scan( + &i.Name, + &i.Logged, + &i.Hub, + &i.EventName, + &i.LocationName, + ) + return i, err +} + +const selectChannels = `-- name: SelectChannels :many +SELECT name, logged, hub, event_name, location_name FROM data_channel +WHERE ($1::bool = false OR name = ANY($2::text[])) + AND ($3::bool = false OR logged = $4) + AND ($5::bool = false OR event_name = $6) + AND ($7::bool = false OR location_name = $8) +LIMIT $9::int +` + +type SelectChannelsParams struct { + FilterName bool `json:"filter_name"` + Names []string `json:"names"` + FilterLogged bool `json:"filter_logged"` + Logged bool `json:"logged"` + FilterEventName bool `json:"filter_event_name"` + EventName string `json:"event_name"` + FilterLocationName bool `json:"filter_location_name"` + LocationName string `json:"location_name"` + LimitSize int32 `json:"limit_size"` +} + +func (q *Queries) SelectChannels(ctx context.Context, arg SelectChannelsParams) ([]DataChannel, error) { + rows, err := q.db.QueryContext(ctx, selectChannels, + arg.FilterName, + pq.Array(arg.Names), + arg.FilterLogged, + arg.Logged, + arg.FilterEventName, + arg.EventName, + arg.FilterLocationName, + arg.LocationName, + arg.LimitSize, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []DataChannel{} + for rows.Next() { + var i DataChannel + if err := rows.Scan( + &i.Name, + &i.Logged, + &i.Hub, + &i.EventName, + &i.LocationName, + ); 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 +} + +const updateChannel = `-- name: UpdateChannel :exec +UPDATE data_channel +SET logged=$1::boolean, + hub=$2::boolean, + event_name=$3::text, + location_name=$4::text +WHERE name=$5::text +` + +type UpdateChannelParams struct { + Logged bool `json:"logged"` + Hub bool `json:"hub"` + EventName string `json:"event_name"` + LocationName string `json:"location_name"` + Name string `json:"name"` +} + +func (q *Queries) UpdateChannel(ctx context.Context, arg UpdateChannelParams) error { + _, err := q.db.ExecContext(ctx, updateChannel, + arg.Logged, + arg.Hub, + arg.EventName, + arg.LocationName, + arg.Name, + ) + return err +} diff --git a/database/postgres/psqlcore/models.go b/database/postgres/psqlcore/models.go index be75933..f38fd5d 100644 --- a/database/postgres/psqlcore/models.go +++ b/database/postgres/psqlcore/models.go @@ -11,6 +11,14 @@ type CoreCounter struct { Value sql.NullInt32 `json:"value"` } +type DataChannel struct { + Name string `json:"name"` + Logged bool `json:"logged"` + Hub bool `json:"hub"` + EventName string `json:"event_name"` + LocationName string `json:"location_name"` +} + type DataCharacter struct { ID string `json:"id"` Nicks []string `json:"nicks"` diff --git a/database/postgres/queries/channels.sql b/database/postgres/queries/channels.sql new file mode 100644 index 0000000..99e3768 --- /dev/null +++ b/database/postgres/queries/channels.sql @@ -0,0 +1,29 @@ +-- name: SelectChannelByName :one +SELECT * FROM data_channel WHERE name = $1 LIMIT 1; + +-- name: InsertChannel :exec +INSERT INTO data_channel (name, logged, hub, event_name, location_name) +VALUES ( + sqlc.arg(name)::text, + sqlc.arg(logged)::boolean, sqlc.arg(hub)::boolean, + sqlc.arg(event_name)::text, sqlc.arg(location_name)::text +); + +-- name: SelectChannels :many +SELECT * FROM data_channel +WHERE (sqlc.arg(filter_name)::bool = false OR name = ANY(sqlc.arg(names)::text[])) + AND (sqlc.arg(filter_logged)::bool = false OR logged = sqlc.arg(logged)) + AND (sqlc.arg(filter_event_name)::bool = false OR event_name = sqlc.arg(event_name)) + AND (sqlc.arg(filter_location_name)::bool = false OR location_name = sqlc.arg(location_name)) +LIMIT sqlc.arg(limit_size)::int; + +-- name: UpdateChannel :exec +UPDATE data_channel +SET logged=sqlc.arg(logged)::boolean, + hub=sqlc.arg(hub)::boolean, + event_name=sqlc.arg(event_name)::text, + location_name=sqlc.arg(location_name)::text +WHERE name=sqlc.arg(name)::text; + +-- name: DeleteChannel :exec +DELETE FROM data_channel WHERE name=$1; diff --git a/models/channel.go b/models/channel.go index cae5d5d..d36a855 100644 --- a/models/channel.go +++ b/models/channel.go @@ -9,6 +9,21 @@ type Channel struct { LocationName string `bson:"locationName,omitempty"` } +func (channel *Channel) ApplyUpdate(update ChannelUpdate) { + if update.Logged != nil { + channel.Logged = *update.Logged + } + if update.Hub != nil { + channel.Hub = *update.Hub + } + if update.EventName != nil { + channel.EventName = *update.EventName + } + if update.LocationName != nil { + channel.LocationName = *update.LocationName + } +} + // IsChangeObject is an interface implementation to identify it as a valid // ChangeObject in GQL. func (*Channel) IsChangeObject() {