253 lines
4.0 KiB
Go
253 lines
4.0 KiB
Go
package towndb
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"time"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
const dsn = "/town/var/town.db?mode=rw"
|
|
|
|
type UserState string
|
|
|
|
const (
|
|
StateActive = "active"
|
|
StateTempBan = "temp_banned"
|
|
StateBan = "banned"
|
|
StateDeleted = "deleted" // some users request deletion
|
|
)
|
|
|
|
type AdminNote struct {
|
|
ID int64
|
|
Created time.Time
|
|
AuthorID int64
|
|
Content string
|
|
UserID int64
|
|
}
|
|
|
|
func (n *AdminNote) Insert(db *sql.DB) error {
|
|
var (
|
|
err error
|
|
stmt *sql.Stmt
|
|
result sql.Result
|
|
liid int64
|
|
)
|
|
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
stmt, err = tx.Prepare(`
|
|
INSERT INTO notes (created, author, content)
|
|
VALUES (?, ?, ?)`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
result, err = stmt.Exec(
|
|
n.Created.Unix(),
|
|
n.AuthorID,
|
|
n.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
liid, err = result.LastInsertId()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
n.ID = liid
|
|
|
|
stmt, err = tx.Prepare(`
|
|
INSERT INTO user_notes (noteid, userid) VALUES (?, ?)`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = stmt.Exec(n.ID, n.UserID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
type TownUser struct {
|
|
ID int64
|
|
Created time.Time
|
|
Emails []string
|
|
Username string
|
|
Notes []AdminNote
|
|
State UserState
|
|
IsAdmin bool
|
|
}
|
|
|
|
func (u *TownUser) Insert(db *sql.DB) (err error) {
|
|
var tx *sql.Tx
|
|
var stmt *sql.Stmt
|
|
var result sql.Result
|
|
var liid int64
|
|
|
|
if tx, err = db.Begin(); err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
if stmt, err = tx.Prepare(`
|
|
INSERT INTO users (created, username, state, admin)
|
|
VALUES (?, ?, ?, ?)`); err != nil {
|
|
return err
|
|
}
|
|
|
|
if result, err = stmt.Exec(
|
|
u.Created.Unix(),
|
|
u.Username,
|
|
u.State,
|
|
u.IsAdmin); err != nil {
|
|
return err
|
|
}
|
|
|
|
if liid, err = result.LastInsertId(); err != nil {
|
|
return err
|
|
}
|
|
|
|
u.ID = liid
|
|
|
|
if len(u.Emails) > 0 {
|
|
for _, e := range u.Emails {
|
|
if stmt, err = tx.Prepare(`
|
|
INSERT INTO emails (address, userid)
|
|
VALUES (?, ?)`); err != nil {
|
|
return err
|
|
}
|
|
if result, err = stmt.Exec(e, u.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// UserForEmail returns the user associated with an email or nil if no matching user is found
|
|
func UserForEmail(db *sql.DB, address string) (*TownUser, error) {
|
|
stmt, err := db.Prepare(`
|
|
SELECT u.id, u.username FROM users u
|
|
JOIN emails e ON e.userid = u.id
|
|
WHERE e.address = ?
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer stmt.Close()
|
|
row := stmt.QueryRow(address)
|
|
u := &TownUser{}
|
|
if err = row.Scan(&u.ID, &u.Username); err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return u, nil
|
|
}
|
|
|
|
func ConnectDB() (*sql.DB, error) {
|
|
db, err := sql.Open("sqlite3", dsn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
type AuthCode struct {
|
|
ID int64
|
|
Code string
|
|
Email string
|
|
Used bool
|
|
Created time.Time
|
|
}
|
|
|
|
func (c *AuthCode) Insert(db *sql.DB) error {
|
|
stmt, err := db.Prepare(`
|
|
INSERT INTO auth_codes (code, email)
|
|
VALUES ?, ?`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer stmt.Close()
|
|
|
|
result, err := stmt.Exec(c.Code, c.Email)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
liid, err := result.LastInsertId()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.ID = liid
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *AuthCode) Hydrate(db *sql.DB) error {
|
|
stmt, err := db.Prepare(`
|
|
SELECT id, used, created
|
|
FROM auth_codes
|
|
WHERE code = ? AND email = ?`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
return stmt.QueryRow(c.Code).Scan(&c.ID, &c.Used, &c.Created)
|
|
}
|
|
|
|
func (c *AuthCode) MarkUsed(db *sql.DB) error {
|
|
if c.ID == 0 {
|
|
return errors.New("not hydrated")
|
|
}
|
|
|
|
stmt, err := db.Prepare(`
|
|
UPDATE auth_codes SET used = 1 WHERE id = ?`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
result, err := stmt.Exec(c.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var rowsAffected int64
|
|
|
|
if rowsAffected, err = result.RowsAffected(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if rowsAffected == 0 {
|
|
return errors.New("no rows affected")
|
|
}
|
|
|
|
return nil
|
|
}
|