Initial db controllers

feat/db
Antonio De Lucreziis 2 years ago
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,9 +1,11 @@
package main
import "git.phc.dm.unipi.it/phc/website/database"
import (
"git.phc.dm.unipi.it/phc/website/database/sqlite"
)
func main() {
db, err := database.NewSqlite3Database("phc-server.local.db")
db, err := sqlite.New("phc-server.local.db")
if err != nil {
panic(err)
}

@ -1,39 +1,41 @@
package database
// Tools
type DBMigrate interface {
Migrate(migrationDir string) error
}
type DBQueryAppunti interface {
AllDispenseFile() ([]DispensaFile, error)
}
type DBDispense interface {
CreateDispensa(template Dispensa) (string, error)
GetDispensa(id string) (Dispensa, error)
AllDispense() ([]Dispensa, error)
UpdateDispensa(d Dispensa) error
DeleteDispensa(id string) error
Create(template Dispensa) (string, error)
Get(id string) (Dispensa, error)
All() ([]Dispensa, error)
Update(d Dispensa) error
Delete(id string) error
}
type DBUploads interface {
CreateUpload(template Upload) (string, error)
GetUpload(id string) (Upload, error)
Create(template Upload) (string, error)
Get(id string) (Upload, error)
}
type DBFileApprovals interface {
CreateFileApproval(template FileApproval) (string, error)
GetFileApproval(id string) (FileApproval, error)
AllFileApprovals() ([]FileApproval, error)
UpdateFileApproval(d FileApproval) error
DeleteFileApproval(id string) error
Create(template FileApproval) (string, error)
Get(id string) (FileApproval, error)
All() ([]FileApproval, error)
Update(d FileApproval) error
Delete(id string) error
}
type DBDownloads interface {
CreateDownload(template Download) (string, error)
type DBDispensaTags interface {
Set(dispensaId string, tags []string) error
Get(dispensaId string) ([]string, error)
}
type DBTags interface {
SetTags(dispensaId string, tags []string) error
GetTags(dispensaId string) ([]string, error)
type DBDownloads interface {
Create(template Download) error
}
// DB main "interface group" for interacting with the database, with this technique we can test each "table" api of the DB in isolation.
@ -42,6 +44,6 @@ type DB struct {
DBDispense
DBUploads
DBFileApprovals
DBDispensaTags
DBDownloads
DBTags
}

@ -8,6 +8,15 @@ type Dispensa struct {
Description string `db:"description"`
}
type DispensaFile struct {
DispensaId string `db:"dispensa_id"`
UploadId string `db:"upload_id"`
OwnerId string `db:"owner_id"`
Title string `db:"title"`
Description string `db:"description"`
File string `db:"file"`
}
type Upload struct {
Id string `db:"id"`
CreatedAt string `db:"created_at"`
@ -21,7 +30,7 @@ type FileApproval struct {
CreatedAt string `db:"created_at"`
OwnerId string `db:"owner_id"`
UploadId string `db:"upload_id"`
Status string `db:"status"`
Status string `db:"status"` // "approved" | "rejected"
}
type Download struct {

@ -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…
Cancel
Save