rum-goggles/v1/internal/models/account.go
2024-05-02 15:30:25 -04:00

356 lines
6.8 KiB
Go

package models
import (
"database/sql"
"fmt"
)
const (
accountColumns = "id, uid, username, cookies, profile_image, api_key"
accountTable = "account"
)
type Account struct {
ID *int64 `json:"id"`
UID *string `json:"uid"`
Username *string `json:"username"`
Cookies *string `json:"cookies"`
ProfileImage *string `json:"profile_image"`
ApiKey *string `json:"api_key"`
}
func (a *Account) Id() *int64 {
return a.ID
}
func (a *Account) KeyUrl() *string {
return a.ApiKey
}
func (a *Account) LoggedIn() bool {
return a.Cookies != nil
}
func (a *Account) String() *string {
if a.Username == nil {
return nil
}
s := "/user/" + *a.Username
return &s
}
func (a *Account) Title() *string {
return a.Username
}
func (a *Account) Type() string {
return "Account"
}
func (a *Account) values() []any {
return []any{a.ID, a.UID, a.Username, a.Cookies, a.ProfileImage, a.ApiKey}
}
func (a *Account) valuesNoID() []any {
return a.values()[1:]
}
func (a *Account) valuesEndID() []any {
vals := a.values()
return append(vals[1:], vals[0])
}
type sqlAccount struct {
id sql.NullInt64
uid sql.NullString
username sql.NullString
cookies sql.NullString
profileImage sql.NullString
apiKey sql.NullString
}
func (sa *sqlAccount) scan(r Row) error {
return r.Scan(&sa.id, &sa.uid, &sa.username, &sa.cookies, &sa.profileImage, &sa.apiKey)
}
func (sa sqlAccount) toAccount() *Account {
var a Account
a.ID = toInt64(sa.id)
a.UID = toString(sa.uid)
a.Username = toString(sa.username)
a.Cookies = toString(sa.cookies)
a.ProfileImage = toString(sa.profileImage)
a.ApiKey = toString(sa.apiKey)
return &a
}
type AccountService interface {
All() ([]Account, error)
AutoMigrate() error
ByID(id int64) (*Account, error)
ByUsername(username string) (*Account, error)
Create(a *Account) (int64, error)
Delete(a *Account) error
DestructiveReset() error
Update(a *Account) error
}
func NewAccountService(db *sql.DB) AccountService {
return &accountService{
Database: db,
}
}
var _ AccountService = &accountService{}
type accountService struct {
Database *sql.DB
}
func (as *accountService) All() ([]Account, error) {
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
`, accountColumns, accountTable)
rows, err := as.Database.Query(selectQ)
if err != nil {
return nil, pkgErr("error executing select query", err)
}
defer rows.Close()
accounts := []Account{}
for rows.Next() {
sa := &sqlAccount{}
err = sa.scan(rows)
if err != nil {
return nil, pkgErr("error scanning row", err)
}
accounts = append(accounts, *sa.toAccount())
}
err = rows.Err()
if err != nil && err != sql.ErrNoRows {
return nil, pkgErr("error iterating over rows", err)
}
return accounts, nil
}
func (as *accountService) AutoMigrate() error {
err := as.createAccountTable()
if err != nil {
return pkgErr(fmt.Sprintf("error creating %s table", accountTable), err)
}
return nil
}
func (as *accountService) createAccountTable() error {
createQ := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS "%s" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
uid TEXT UNIQUE,
username TEXT UNIQUE NOT NULL,
cookies TEXT,
profile_image TEXT,
api_key TEXT
)
`, accountTable)
_, err := as.Database.Exec(createQ)
if err != nil {
return fmt.Errorf("error executing create query: %v", err)
}
return nil
}
func (as *accountService) ByID(id int64) (*Account, error) {
err := runAccountValFuncs(
&Account{ID: &id},
accountRequireID,
)
if err != nil {
return nil, pkgErr("", err)
}
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
WHERE id=?
`, accountColumns, accountTable)
var sa sqlAccount
row := as.Database.QueryRow(selectQ, id)
err = sa.scan(row)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, pkgErr("error executing select query", err)
}
return sa.toAccount(), nil
}
func (as *accountService) ByUsername(username string) (*Account, error) {
err := runAccountValFuncs(
&Account{Username: &username},
accountRequireUsername,
)
if err != nil {
return nil, pkgErr("", err)
}
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
WHERE username=?
`, accountColumns, accountTable)
var sa sqlAccount
row := as.Database.QueryRow(selectQ, username)
err = sa.scan(row)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, pkgErr("error executing select query", err)
}
return sa.toAccount(), nil
}
func (as *accountService) Create(a *Account) (int64, error) {
err := runAccountValFuncs(
a,
accountRequireUsername,
)
if err != nil {
return -1, pkgErr("invalid account", err)
}
columns := columnsNoID(accountColumns)
insertQ := fmt.Sprintf(`
INSERT INTO "%s" (%s)
VALUES (%s)
RETURNING id
`, accountTable, columns, values(columns))
// _, err = as.Database.Exec(insertQ, a.valuesNoID()...)
var id int64
row := as.Database.QueryRow(insertQ, a.valuesNoID()...)
err = row.Scan(&id)
if err != nil {
return -1, pkgErr("error executing insert query", err)
}
return id, nil
}
func (as *accountService) Delete(a *Account) error {
err := runAccountValFuncs(
a,
accountRequireID,
)
if err != nil {
return pkgErr("invalid account", err)
}
deleteQ := fmt.Sprintf(`
DELETE FROM "%s"
WHERE id=?
`, accountTable)
_, err = as.Database.Exec(deleteQ, a.ID)
if err != nil {
return pkgErr("error executing delete query", err)
}
return nil
}
func (as *accountService) DestructiveReset() error {
err := as.dropAccountTable()
if err != nil {
return pkgErr(fmt.Sprintf("error dropping %s table", accountTable), err)
}
return nil
}
func (as *accountService) dropAccountTable() error {
dropQ := fmt.Sprintf(`
DROP TABLE IF EXISTS "%s"
`, accountTable)
_, err := as.Database.Exec(dropQ)
if err != nil {
return fmt.Errorf("error executing drop query: %v", err)
}
return nil
}
func (as *accountService) Update(a *Account) error {
err := runAccountValFuncs(
a,
accountRequireID,
accountRequireUsername,
)
if err != nil {
return pkgErr("invalid account", err)
}
columns := columnsNoID(accountColumns)
updateQ := fmt.Sprintf(`
UPDATE "%s"
SET %s
WHERE id=?
`, accountTable, set(columns))
_, err = as.Database.Exec(updateQ, a.valuesEndID()...)
if err != nil {
return pkgErr("error executing update query", err)
}
return nil
}
type accountValFunc func(*Account) error
func runAccountValFuncs(a *Account, fns ...accountValFunc) error {
if a == nil {
return fmt.Errorf("account is nil")
}
for _, fn := range fns {
err := fn(a)
if err != nil {
return err
}
}
return nil
}
func accountRequireID(a *Account) error {
if a.ID == nil || *a.ID < 1 {
return ErrAccountInvalidID
}
return nil
}
func accountRequireUsername(a *Account) error {
if a.Username == nil || *a.Username == "" {
return ErrAccountInvalidUsername
}
return nil
}