Initial db controllers
parent
9df45cf83a
commit
c7e33c8e72
@ -0,0 +1,23 @@
|
|||||||
|
# Architecture
|
||||||
|
|
||||||
|
## Server, Database and File Uploads
|
||||||
|
|
||||||
|
- **server**
|
||||||
|
|
||||||
|
The server module defines all routes and extracts data from HTTP request and passes data to the **handler** module.
|
||||||
|
|
||||||
|
- **handler**
|
||||||
|
|
||||||
|
This is the main controller module that dispatches requests to various services and constructs back responses to send to the client.
|
||||||
|
|
||||||
|
### Services
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
- **appunti**
|
||||||
|
|
||||||
|
This module manages all things relating the _appunti condivisi_ pages, it talks with the **database** and **auth** modules.
|
||||||
|
|
||||||
|
- **database**
|
||||||
|
|
||||||
|
This is one of the "leaf" modules that doesn't depend on anything, its purpose is to talk to the database and return "low level data" stored there. For example instances of `time.Time` are represented as `string`s at this layer.
|
@ -1,187 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -0,0 +1,31 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.phc.dm.unipi.it/phc/website/database"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type migration struct {
|
||||||
|
Timestamp string
|
||||||
|
Filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(filename string) (*database.DB, error) {
|
||||||
|
db, err := sqlx.Open("sqlite3", filename+"?_fk=1")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &database.DB{
|
||||||
|
DBMigrate: &sqliteDBMigrate{db},
|
||||||
|
DBDispense: &sqliteDBDispense{db},
|
||||||
|
DBUploads: &sqliteDBUploads{db},
|
||||||
|
DBFileApprovals: &sqliteDBFileApprovals{db},
|
||||||
|
DBDownloads: &sqliteDBDownloads{db},
|
||||||
|
}, nil
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.phc.dm.unipi.it/phc/website/database"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqliteDBMigrate struct{ *sqlx.DB }
|
||||||
|
|
||||||
|
var _ database.DBMigrate = sqliteDBMigrate{}
|
||||||
|
|
||||||
|
func (db sqliteDBMigrate) 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
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.phc.dm.unipi.it/phc/website/database"
|
||||||
|
"git.phc.dm.unipi.it/phc/website/util"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqliteDBDispense struct{ *sqlx.DB }
|
||||||
|
|
||||||
|
var _ database.DBDispense = sqliteDBDispense{}
|
||||||
|
|
||||||
|
func (d sqliteDBDispense) Create(template database.Dispensa) (string, error) {
|
||||||
|
template.Id = "dispensa/" + util.GenerateRandomString(8)
|
||||||
|
template.CreatedAt = time.Now().Format(time.RFC3339)
|
||||||
|
|
||||||
|
if _, err := d.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 (d sqliteDBDispense) Get(id string) (database.Dispensa, error) {
|
||||||
|
var dispensa database.Dispensa
|
||||||
|
if err := d.DB.Get(&dispensa, `SELECT * FROM dispense WHERE id = ?`, id); err != nil {
|
||||||
|
return database.Dispensa{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dispensa, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d sqliteDBDispense) All() ([]database.Dispensa, error) {
|
||||||
|
var dispense []database.Dispensa
|
||||||
|
if err := d.DB.Select(&dispense, `SELECT * FROM dispense`); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dispense, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d sqliteDBDispense) Update(dispensa database.Dispensa) error {
|
||||||
|
if _, err := d.DB.NamedExec(`
|
||||||
|
UPDATE
|
||||||
|
dispense
|
||||||
|
SET
|
||||||
|
owner_id = :owner_id
|
||||||
|
title = :title,
|
||||||
|
description = :description
|
||||||
|
WHERE
|
||||||
|
id = :id
|
||||||
|
`, &dispensa); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d sqliteDBDispense) Delete(id string) error {
|
||||||
|
panic("TODO: Not implemented")
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.phc.dm.unipi.it/phc/website/database"
|
||||||
|
"git.phc.dm.unipi.it/phc/website/util"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqliteDBUploads struct{ *sqlx.DB }
|
||||||
|
|
||||||
|
var _ database.DBUploads = sqliteDBUploads{}
|
||||||
|
|
||||||
|
func (u sqliteDBUploads) Create(template database.Upload) (string, error) {
|
||||||
|
template.Id = "upload/" + util.GenerateRandomString(8)
|
||||||
|
|
||||||
|
if _, err := u.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 (u sqliteDBUploads) Get(id string) (database.Upload, error) {
|
||||||
|
var upload database.Upload
|
||||||
|
if err := u.DB.Select(`SELECT * FROM uploads WHERE id = ?`, id); err != nil {
|
||||||
|
return database.Upload{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return upload, nil
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.phc.dm.unipi.it/phc/website/database"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqliteDBFileApprovals struct{ *sqlx.DB }
|
||||||
|
|
||||||
|
func (f sqliteDBFileApprovals) Create(template database.FileApproval) (string, error) {
|
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f sqliteDBFileApprovals) Get(id string) (database.FileApproval, error) {
|
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f sqliteDBFileApprovals) All() ([]database.FileApproval, error) {
|
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f sqliteDBFileApprovals) Update(d database.FileApproval) error {
|
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f sqliteDBFileApprovals) Delete(id string) error {
|
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.phc.dm.unipi.it/phc/website/database"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqliteDBDownloads struct{ *sqlx.DB }
|
||||||
|
|
||||||
|
var _ database.DBDownloads = sqliteDBDownloads{}
|
||||||
|
|
||||||
|
func (d sqliteDBDownloads) Create(template database.Download) error {
|
||||||
|
if _, err := d.DB.NamedExec(`
|
||||||
|
INSERT INTO
|
||||||
|
downloads(dispensa_id, timestamp)
|
||||||
|
VALUES
|
||||||
|
(:dispensa_id, :timestamp)
|
||||||
|
`, &template); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue