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 }