package users import ( "fmt" "time" "github.com/jmoiron/sqlx" "gitlab.quimbo.fr/odwrtw/canape-sql/random" ) const usersCreate = ` CREATE TABLE users ( id SERIAL PRIMARY KEY, name text NOT NULL UNIQUE, updated timestamp DEFAULT current_timestamp, created timestamp DEFAULT current_timestamp ); CREATE TABLE tokens ( id SERIAL, value text NOT NULL UNIQUE, users_id integer REFERENCES users (id) ON DELETE CASCADE, updated timestamp DEFAULT current_timestamp, created timestamp DEFAULT current_timestamp ); CREATE OR REPLACE FUNCTION update_modified_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated = now(); RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER update_users BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE update_modified_column(); CREATE TRIGGER update_tokens BEFORE UPDATE ON tokens FOR EACH ROW EXECUTE PROCEDURE update_modified_column(); ` const ( addUserQuery = `INSERT INTO users (name) VALUES ($1) RETURNING id;` getUserQuery = `SELECT * FROM users WHERE name=$1;` updateUserQuery = `UPDATE users SET name=$1 RETURNING *;` deleteUseQuery = `DELETE FROM users WHERE id=:id;` addTokenQuery = `INSERT INTO tokens (value, users_id) VALUES ($1, $2) RETURNING id;` getTokensQuery = `SELECT id, value FROM tokens WHERE users_id=$1;` checkTokenQuery = `SELECT count(*) FROM tokens WHERE users_id=$1 AND value=$2;` deleteTokenQuery = `DELETE FROM tokens WHERE users_id=$1 AND value=$2;` ) // BaseTable have to be embeded in all your struct which reflect a table type BaseTable struct { Updated time.Time Created time.Time } // User represents an user type User struct { BaseTable ID int Name string } // Token represents a token type Token struct { BaseTable ID int Value string } // Get returns user with specified name func Get(q sqlx.Queryer, name string) (*User, error) { u := &User{} err := q.QueryRowx(getUserQuery, name).StructScan(u) if err != nil { return nil, err } return u, nil } // Add user to database or raises an error func (u *User) Add(q sqlx.Queryer) error { var id int err := q.QueryRowx(addUserQuery, u.Name).Scan(&id) if err != nil { return err } u.ID = id return nil } // Update user on database or raise an error func (u *User) Update(ex *sqlx.DB) error { err := ex.Get(u, updateUserQuery, u.Name) if err != nil { return err } return nil } // Delete user from database or raise an error func (u *User) Delete(ex *sqlx.DB) error { _, err := ex.NamedExec(deleteUseQuery, u) if err != nil { return err } return nil } // GetTokens returns all tokens owned by the user func (u *User) GetTokens(ex *sqlx.DB) ([]*Token, error) { tokens := []*Token{} err := ex.Select(&tokens, getTokensQuery, u.ID) if err != nil { return nil, err } return tokens, nil } // NewToken generates a new token for the user func (u *User) NewToken(ex *sqlx.DB) (*Token, error) { t := &Token{ Value: random.String(50), } var id int err := ex.QueryRowx(addTokenQuery, t.Value, u.ID).Scan(&id) if err != nil { return nil, err } t.ID = id return t, nil } // CheckToken checks if specified value exists in token's values for the user func (u *User) CheckToken(ex *sqlx.DB, value string) (bool, error) { var count int err := ex.QueryRowx(checkTokenQuery, u.ID, value).Scan(&count) if err != nil { return false, err } if count != 1 { return false, nil } return true, nil } // DeleteToken delete token by value func (u *User) DeleteToken(ex *sqlx.DB, value string) error { r, err := ex.Exec(deleteTokenQuery, u.ID, value) if err != nil { return err } count, _ := r.RowsAffected() if count != 1 { return fmt.Errorf("Unexpected number of row deleted: %d", count) } return nil }