364 lines
6.4 KiB
Go
364 lines
6.4 KiB
Go
package noodle
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"go.mills.io/bitcask/v2"
|
|
)
|
|
|
|
var ErrUserNotFound = errors.New("user not found")
|
|
var ErrLastAdminRemoval = errors.New("cannot remove the last admin")
|
|
|
|
type Database struct {
|
|
connection *bitcask.Bitcask
|
|
Handle *bitcask.Collection
|
|
Users *bitcask.Collection
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewDatabase(path string) *Database {
|
|
db, err := bitcask.Open(path)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return &Database{
|
|
connection: db,
|
|
Handle: db.Collection("noodles"),
|
|
Users: db.Collection("users"),
|
|
}
|
|
}
|
|
|
|
func (db *Database) MakeID() string {
|
|
id, err := uuid.NewUUID()
|
|
if err != nil {
|
|
log.Print(err)
|
|
return ""
|
|
}
|
|
return id.String()
|
|
}
|
|
|
|
func (db *Database) GetAll() []Noodle {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var data []Noodle
|
|
if err := db.Handle.List(&data); err != nil {
|
|
log.Print(err)
|
|
return nil
|
|
}
|
|
return data
|
|
}
|
|
|
|
func (db *Database) GetAllGeneric() []interface{} {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var data []interface{}
|
|
if err := db.Handle.List(&data); err != nil {
|
|
log.Print(err)
|
|
return nil
|
|
}
|
|
return data
|
|
}
|
|
|
|
func (db *Database) Get(id string) Noodle {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var item Noodle
|
|
log.Printf("Looking up noodle key='%s'", id)
|
|
log.Printf("key='%s' exists=%t", id, db.Handle.Has(id))
|
|
if err := db.Handle.Get(id, &item); err != nil {
|
|
log.Print(err)
|
|
return Noodle{}
|
|
}
|
|
return item
|
|
}
|
|
|
|
func (db *Database) Add(item Noodle) error {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
if err := db.Handle.Add(item.Id, item); err != nil {
|
|
log.Print(err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) Update(item Noodle) error {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
if err := db.Handle.Add(item.Id, item); err != nil {
|
|
log.Print(err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) Delete(id string) error {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
if err := db.Handle.Delete(id); err != nil {
|
|
log.Print(err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) Close() error {
|
|
return db.connection.Close()
|
|
}
|
|
|
|
func (db *Database) GetAllUsers() []User {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var data []User
|
|
if err := db.Users.List(&data); err != nil {
|
|
log.Print(err)
|
|
return nil
|
|
}
|
|
return data
|
|
}
|
|
|
|
func (db *Database) GetUserByUsername(username string) (User, error) {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var data []User
|
|
if err := db.Users.List(&data); err != nil {
|
|
log.Print(err)
|
|
return User{}, err
|
|
}
|
|
|
|
for _, user := range data {
|
|
if user.Username == username {
|
|
return user, nil
|
|
}
|
|
}
|
|
|
|
return User{}, ErrUserNotFound
|
|
}
|
|
|
|
func (db *Database) AddUser(user User) error {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var data []User
|
|
if err := db.Users.List(&data); err != nil {
|
|
log.Print(err)
|
|
return err
|
|
}
|
|
for _, existing := range data {
|
|
if existing.Username == user.Username {
|
|
return fmt.Errorf("user %q already exists", user.Username)
|
|
}
|
|
}
|
|
|
|
if err := db.Users.Add(user.Id, user); err != nil {
|
|
log.Print(err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) UpdateUser(user User) error {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
if err := db.Users.Add(user.Id, user); err != nil {
|
|
log.Print(err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) SetUserRole(username, role string) (User, error) {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var data []User
|
|
if err := db.Users.List(&data); err != nil {
|
|
log.Print(err)
|
|
return User{}, err
|
|
}
|
|
|
|
var target User
|
|
found := false
|
|
adminCount := 0
|
|
for _, user := range data {
|
|
if user.Role == UserRoleAdmin {
|
|
adminCount++
|
|
}
|
|
if user.Username == username {
|
|
target = user
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return User{}, ErrUserNotFound
|
|
}
|
|
if target.Role == UserRoleAdmin && role != UserRoleAdmin && adminCount <= 1 {
|
|
return User{}, ErrLastAdminRemoval
|
|
}
|
|
|
|
target.Role = role
|
|
if err := db.Users.Add(target.Id, target); err != nil {
|
|
log.Print(err)
|
|
return User{}, err
|
|
}
|
|
return target, nil
|
|
}
|
|
|
|
func (db *Database) SetUserPassword(username, passwordHash string) (User, error) {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var data []User
|
|
if err := db.Users.List(&data); err != nil {
|
|
log.Print(err)
|
|
return User{}, err
|
|
}
|
|
|
|
for _, user := range data {
|
|
if user.Username != username {
|
|
continue
|
|
}
|
|
user.PasswordHash = passwordHash
|
|
if err := db.Users.Add(user.Id, user); err != nil {
|
|
log.Print(err)
|
|
return User{}, err
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
return User{}, ErrUserNotFound
|
|
}
|
|
|
|
func (db *Database) DeleteUser(username string) (User, error) {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var data []User
|
|
if err := db.Users.List(&data); err != nil {
|
|
log.Print(err)
|
|
return User{}, err
|
|
}
|
|
|
|
var target User
|
|
found := false
|
|
adminCount := 0
|
|
for _, user := range data {
|
|
if user.Role == UserRoleAdmin {
|
|
adminCount++
|
|
}
|
|
if user.Username == username {
|
|
target = user
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return User{}, ErrUserNotFound
|
|
}
|
|
if target.Role == UserRoleAdmin && adminCount <= 1 {
|
|
return User{}, ErrLastAdminRemoval
|
|
}
|
|
|
|
if err := db.Users.Delete(target.Id); err != nil {
|
|
log.Print(err)
|
|
return User{}, err
|
|
}
|
|
return target, nil
|
|
}
|
|
|
|
func (db *Database) SetIsUp(id string, isUp bool) (Noodle, error) {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var item Noodle
|
|
if err := db.Handle.Get(id, &item); err != nil {
|
|
log.Print(err)
|
|
return Noodle{}, err
|
|
}
|
|
|
|
item.IsUp = isUp
|
|
if err := db.Handle.Add(item.Id, item); err != nil {
|
|
log.Print(err)
|
|
return Noodle{}, err
|
|
}
|
|
|
|
return item, nil
|
|
}
|
|
|
|
func (db *Database) DeleteByID(id string) (Noodle, error) {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var item Noodle
|
|
if err := db.Handle.Get(id, &item); err != nil {
|
|
log.Print(err)
|
|
return Noodle{}, err
|
|
}
|
|
|
|
if err := db.Handle.Delete(id); err != nil {
|
|
log.Print(err)
|
|
return Noodle{}, err
|
|
}
|
|
|
|
return item, nil
|
|
}
|
|
|
|
func (db *Database) TickExpirations() []Noodle {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
var noodles []Noodle
|
|
if err := db.Handle.List(&noodles); err != nil {
|
|
log.Print(err)
|
|
return nil
|
|
}
|
|
|
|
var stopped []Noodle
|
|
for _, item := range noodles {
|
|
if !item.IsUp {
|
|
continue
|
|
}
|
|
|
|
if item.Expiration <= 0 {
|
|
item.IsUp = false
|
|
stopped = append(stopped, item)
|
|
if err := db.Handle.Delete(item.Id); err != nil {
|
|
log.Print(err)
|
|
}
|
|
continue
|
|
}
|
|
|
|
item.Expiration -= time.Second
|
|
if item.Expiration <= 0 {
|
|
item.Expiration = 0
|
|
item.IsUp = false
|
|
stopped = append(stopped, item)
|
|
if err := db.Handle.Delete(item.Id); err != nil {
|
|
log.Print(err)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if err := db.Handle.Add(item.Id, item); err != nil {
|
|
log.Print(err)
|
|
}
|
|
}
|
|
|
|
return stopped
|
|
}
|