rum-goggles/v1/internal/models/channel.go
2024-04-04 10:46:14 -04:00

392 lines
7.4 KiB
Go

package models
import (
"database/sql"
"fmt"
"strings"
)
const (
channelColumns = "id, account_id, cid, name, profile_image, api_key"
channelTable = "channel"
)
type Channel struct {
ID *int64 `json:"id"`
AccountID *int64 `json:"account_id"`
CID *string `json:"cid"`
Name *string `json:"name"`
ProfileImage *string `json:"profile_image"`
ApiKey *string `json:"api_key"`
}
func (c *Channel) Id() *int64 {
return c.ID
}
func (c *Channel) KeyUrl() *string {
return c.ApiKey
}
func (c *Channel) LoggedIn() bool {
return false
}
func (c *Channel) String() *string {
if c.Name == nil {
return nil
}
s := "/c/" + strings.ReplaceAll(*c.Name, " ", "")
return &s
}
func (c *Channel) Title() *string {
return c.Name
}
func (c *Channel) Type() string {
return "Channel"
}
func (c *Channel) values() []any {
return []any{c.ID, c.AccountID, c.CID, c.Name, c.ProfileImage, c.ApiKey}
}
func (c *Channel) valuesNoID() []any {
return c.values()[1:]
}
func (c *Channel) valuesEndID() []any {
vals := c.values()
return append(vals[1:], vals[0])
}
type sqlChannel struct {
id sql.NullInt64
accountID sql.NullInt64
cid sql.NullString
name sql.NullString
profileImage sql.NullString
apiKey sql.NullString
}
func (sc *sqlChannel) scan(r Row) error {
return r.Scan(&sc.id, &sc.accountID, &sc.cid, &sc.name, &sc.profileImage, &sc.apiKey)
}
func (sc sqlChannel) toChannel() *Channel {
var c Channel
c.ID = toInt64(sc.id)
c.AccountID = toInt64(sc.accountID)
c.CID = toString(sc.cid)
c.Name = toString(sc.name)
c.ProfileImage = toString(sc.profileImage)
c.ApiKey = toString(sc.apiKey)
return &c
}
type ChannelService interface {
AutoMigrate() error
ByAccount(a *Account) ([]Channel, error)
ByID(id int64) (*Channel, error)
ByName(name string) (*Channel, error)
Create(c *Channel) error
Delete(c *Channel) error
DestructiveReset() error
Update(c *Channel) error
}
func NewChannelService(db *sql.DB) ChannelService {
return &channelService{
Database: db,
}
}
var _ ChannelService = &channelService{}
type channelService struct {
Database *sql.DB
}
func (cs *channelService) AutoMigrate() error {
err := cs.createChannelTable()
if err != nil {
return pkgErr(fmt.Sprintf("error creating %s table", channelTable), err)
}
return nil
}
func (cs *channelService) createChannelTable() error {
createQ := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS "%s" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
cid TEXT UNIQUE NOT NULL,
name TEXT UNIQUE NOT NULL,
profile_image TEXT,
api_key TEXT NOT NULL,
FOREIGN KEY (account_id) REFERENCES "%s" (id)
)
`, channelTable, accountTable)
_, err := cs.Database.Exec(createQ)
if err != nil {
return fmt.Errorf("error executing create query: %v", err)
}
return nil
}
func (cs *channelService) ByAccount(a *Account) ([]Channel, error) {
err := runAccountValFuncs(
a,
accountRequireID,
)
if err != nil {
return nil, pkgErr("", err)
}
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
WHERE account_id=?
`, channelColumns, channelTable)
rows, err := cs.Database.Query(selectQ, a.ID)
if err != nil {
return nil, pkgErr("error executing select query", err)
}
defer rows.Close()
channels := []Channel{}
for rows.Next() {
sc := &sqlChannel{}
err = sc.scan(rows)
if err != nil {
return nil, pkgErr("error scanning row", err)
}
channels = append(channels, *sc.toChannel())
}
err = rows.Err()
if err != nil && err != sql.ErrNoRows {
return nil, pkgErr("error iterating over rows", err)
}
return channels, nil
}
func (cs *channelService) ByID(id int64) (*Channel, error) {
err := runChannelValFuncs(
&Channel{ID: &id},
channelRequireID,
)
if err != nil {
return nil, pkgErr("", err)
}
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
WHERE id=?
`, channelColumns, channelTable)
var sc sqlChannel
row := cs.Database.QueryRow(selectQ, id)
err = sc.scan(row)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, pkgErr("error executing select query", err)
}
return sc.toChannel(), nil
}
func (cs *channelService) ByName(name string) (*Channel, error) {
err := runChannelValFuncs(
&Channel{Name: &name},
channelRequireName,
)
if err != nil {
return nil, pkgErr("", err)
}
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
WHERE name=?
`, channelColumns, channelTable)
var sc sqlChannel
row := cs.Database.QueryRow(selectQ, name)
err = sc.scan(row)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, pkgErr("error executing select query", err)
}
return sc.toChannel(), nil
}
func (cs *channelService) Create(c *Channel) error {
err := runChannelValFuncs(
c,
channelRequireAccountID,
channelRequireApiKey,
channelRequireCID,
channelRequireName,
)
if err != nil {
return pkgErr("invalid channel", err)
}
columns := columnsNoID(channelColumns)
insertQ := fmt.Sprintf(`
INSERT INTO "%s" (%s)
VALUES (%s)
`, channelTable, columns, values(columns))
_, err = cs.Database.Exec(insertQ, c.valuesNoID()...)
if err != nil {
return pkgErr("error executing insert query", err)
}
return nil
}
func (cs *channelService) Delete(c *Channel) error {
err := runChannelValFuncs(
c,
channelRequireID,
)
if err != nil {
return pkgErr("invalid channel", err)
}
deleteQ := fmt.Sprintf(`
DELETE FROM "%s"
WHERE id=?
`, channelTable)
_, err = cs.Database.Exec(deleteQ, c.ID)
if err != nil {
return pkgErr("error executing delete query", err)
}
return nil
}
func (cs *channelService) DestructiveReset() error {
err := cs.dropChannelTable()
if err != nil {
return pkgErr(fmt.Sprintf("error dropping %s table", channelTable), err)
}
return nil
}
func (cs *channelService) dropChannelTable() error {
dropQ := fmt.Sprintf(`
DROP TABLE IF EXISTS "%s"
`, channelTable)
_, err := cs.Database.Exec(dropQ)
if err != nil {
return fmt.Errorf("error executing drop query: %v", err)
}
return nil
}
func (cs *channelService) Update(c *Channel) error {
err := runChannelValFuncs(
c,
channelRequireAccountID,
channelRequireApiKey,
channelRequireCID,
channelRequireName,
)
if err != nil {
return pkgErr("invalid channel", err)
}
columns := columnsNoID(channelColumns)
updateQ := fmt.Sprintf(`
UPDATE "%s"
SET %s
WHERE id=?
`, channelTable, set(columns))
_, err = cs.Database.Exec(updateQ, c.valuesEndID()...)
if err != nil {
return pkgErr("error executing update query", err)
}
return nil
}
type channelValFunc func(*Channel) error
func runChannelValFuncs(c *Channel, fns ...channelValFunc) error {
if c == nil {
return fmt.Errorf("channel cannot be nil")
}
for _, fn := range fns {
err := fn(c)
if err != nil {
return err
}
}
return nil
}
func channelRequireAccountID(c *Channel) error {
if c.AccountID == nil || *c.AccountID <= 0 {
return ErrChannelInvalidAccountID
}
return nil
}
func channelRequireApiKey(c *Channel) error {
if c.ApiKey == nil || *c.ApiKey == "" {
return ErrChannelInvalidApiKey
}
return nil
}
func channelRequireID(c *Channel) error {
if c.ID == nil || *c.ID < 1 {
return ErrChannelInvalidID
}
return nil
}
func channelRequireCID(c *Channel) error {
if c.CID == nil || *c.CID == "" {
return ErrChannelInvalidCID
}
return nil
}
func channelRequireName(c *Channel) error {
if c.Name == nil || *c.Name == "" {
return ErrChannelInvalidName
}
return nil
}