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) } // TODO other auth code as needed