|
|
|
package database
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.phc.dm.unipi.it/phc/website/util"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
)
|
|
|
|
|
|
|
|
type migration struct {
|
|
|
|
Timestamp string
|
|
|
|
Filename string
|
|
|
|
}
|
|
|
|
|
|
|
|
type sqliteDB struct {
|
|
|
|
*sqlx.DB
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *sqliteDB) Migrate(migrationFolder string) error {
|
|
|
|
log.Printf(`Creating migrations table`)
|
|
|
|
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS migrations(timestamp TEXT, filename TEXT)`); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf(`Loading applied migrations`)
|
|
|
|
appliedMigrations := []migration{}
|
|
|
|
if err := db.Select(&appliedMigrations, `SELECT * FROM migrations`); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
entries, err := os.ReadDir(migrationFolder)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, entry := range entries {
|
|
|
|
if entry.IsDir() {
|
|
|
|
return fmt.Errorf("no dirs in migrations folder")
|
|
|
|
}
|
|
|
|
|
|
|
|
if i < len(appliedMigrations) {
|
|
|
|
if appliedMigrations[i].Filename != entry.Name() {
|
|
|
|
return fmt.Errorf("misapplied migration %q with %q", appliedMigrations[i].Filename, entry.Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Found applied migration %q", entry.Name())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Applying new migration %q", entry.Name())
|
|
|
|
|
|
|
|
migrationPath := path.Join(migrationFolder, entry.Name())
|
|
|
|
|
|
|
|
sqlStmts, err := os.ReadFile(migrationPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx, err := db.Beginx()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := tx.Exec(string(sqlStmts)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := tx.Exec(`INSERT INTO migrations VALUES (?, ?)`,
|
|
|
|
time.Now().Format(time.RFC3339),
|
|
|
|
entry.Name(),
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("All migrations applied successfully, database up to date")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *sqliteDB) CreateDispensa(template Dispensa) (string, error) {
|
|
|
|
template.Id = "dispensa/" + util.GenerateRandomString(8)
|
|
|
|
template.CreatedAt = time.Now().Format(time.RFC3339)
|
|
|
|
|
|
|
|
if _, err := db.NamedExec(`
|
|
|
|
INSERT INTO
|
|
|
|
dispense(id, created_at, owner_id, title, description)
|
|
|
|
VALUES
|
|
|
|
(:id, :created_at, :owner_id, :title, :description)
|
|
|
|
`, &template); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return template.Id, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *sqliteDB) GetDispensa(id string) (Dispensa, error) {
|
|
|
|
var dispensa Dispensa
|
|
|
|
if err := db.Get(&dispensa, `SELECT * FROM dispense WHERE id = ?`, id); err != nil {
|
|
|
|
return Dispensa{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dispensa, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *sqliteDB) AllDispense() ([]Dispensa, error) {
|
|
|
|
var dispense []Dispensa
|
|
|
|
if err := db.Select(&dispense, `SELECT * FROM dispense`); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dispense, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *sqliteDB) UpdateDispensa(d Dispensa) error {
|
|
|
|
if _, err := db.NamedExec(`
|
|
|
|
UPDATE
|
|
|
|
dispense
|
|
|
|
SET
|
|
|
|
owner_id = :owner_id
|
|
|
|
title = :title,
|
|
|
|
description = :description
|
|
|
|
WHERE
|
|
|
|
id = :id
|
|
|
|
`, &d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *sqliteDB) DeleteDispensa(id string) error {
|
|
|
|
panic("TODO: Not implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *sqliteDB) CreateUpload(template Upload) (string, error) {
|
|
|
|
template.Id = "upload/" + util.GenerateRandomString(8)
|
|
|
|
|
|
|
|
if _, err := db.NamedExec(`
|
|
|
|
INSERT INTO
|
|
|
|
uploads(id, created_at, owner_id, dispensa_id, file)
|
|
|
|
VALUES
|
|
|
|
(:id, :created_at, :owner_id, :dispensa_id, :file)
|
|
|
|
`, &template); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return template.Id, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *sqliteDB) GetUpload(id string) (Upload, error) {
|
|
|
|
var upload Upload
|
|
|
|
if err := db.Select(`SELECT * FROM uploads WHERE id = ?`, id); err != nil {
|
|
|
|
return Upload{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return upload, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewSqlite3Database(filename string) (*DB, error) {
|
|
|
|
sqlDB, err := sqlx.Open("sqlite3", filename+"?_fk=1")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := sqlDB.Ping(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
db := &sqliteDB{sqlDB}
|
|
|
|
|
|
|
|
// in this case the type "sqliteDB" implements all the interfaces declared in "database.DB" so we can pass "db" to all the fields.
|
|
|
|
return &DB{
|
|
|
|
DBMigrate: db,
|
|
|
|
DBDispense: db,
|
|
|
|
DBUploads: db,
|
|
|
|
}, nil
|
|
|
|
}
|