Database prototype

feat/db
Antonio De Lucreziis 2 years ago
parent 03f976a8f9
commit 2ce1ac9bf1

@ -0,0 +1,17 @@
package appunti
import (
"git.phc.dm.unipi.it/phc/website/database"
)
type Service interface {
ViewDispense() error
}
type DefaultService struct {
DB database.DB
}
func (s *DefaultService) CreateDispensa(user string, template database.Dispensa) {
}

@ -0,0 +1,14 @@
package main
import "git.phc.dm.unipi.it/phc/website/database"
func main() {
db, err := database.NewSqlite3Database("phc-server.local.db")
if err != nil {
panic(err)
}
if err := db.Migrate("database/migrations"); err != nil {
panic(err)
}
}

@ -0,0 +1,91 @@
package database
import "time"
// Tools
type DBMigrate interface {
Migrate(migrationDir string) error
}
// Entities
type DBDispense interface {
CreateDispensa(template Dispensa) (string, error)
GetDispensa(id string) (Dispensa, error)
AllDispense() ([]Dispensa, error)
UpdateDispensa(d Dispensa) error
DeleteDispensa(id string) error
}
type DBUpload interface {
CreateUpload(template UploadedContent) (string, error)
DeleteUpload(id UploadedContent) (UploadedContent, error)
}
type DBHashApprovals interface {
CreateApprovedHash(a HashApproval) error
GetApprovedHash(hash string) (HashApproval, error)
AllApprovedHash(a HashApproval) error
}
type DBHashRejections interface {
CreateRejectedHash(a HashRejection) error
GetRejectedHash(hash string) (HashRejection, error)
AllRejectedHash(a HashRejection) error
}
// Relations
type DBTags interface {
SetTags(dispensaId string, tag []string) error
GetTags(dispensaId string) ([]string, error)
}
type DBOwners interface {
CreateOwner(owner, owned string) error
GetOwner(ownedId string) (string, error)
}
type DBAuthors interface {
CreateAuthor(userId, dispensaId string) error
GetAuthorId(dispensaId string) (string, error)
GetUserDispenseIds(user string) ([]string, error)
}
type DBCreationTimes interface {
CreateCreationTime(entityId string, createdAt time.Time) error
GetCreationTime(entityId string) (time.Time, error)
}
type DBOther interface {
// AllDispenseOfUser ritorna tutte le dispense di un certo utente
AllDispenseOfUser(username string) ([]Dispensa, error)
// AllLatestsApprovedDispenseUploads ritorna una lista contenente tutte le dispense con almeno un upload approvato e ritorna la coppia della (dispensa, upload più recente)
AllLatestsApprovedDispenseUploads() ([]struct {
Dispensa
UploadedContent
}, error)
// AllDispenseWithState ...
AllDispenseWithState(username string) ([]struct {
Dispensa
State string
}, error)
}
// DB main "interface group" for interacting with the database, with this technique we can test each "table" api of the DB in isolation.
type DB struct {
DBMigrate
DBDispense
DBUpload
DBHashApprovals
DBHashRejections
DBTags
DBOwners
DBAuthors
DBCreationTimes
DBOther
}

@ -0,0 +1,48 @@
--
-- Entities
--
-- Dispense
CREATE TABLE IF NOT EXISTS "dispense"(
"id" TEXT NOT NULL PRIMARY KEY,
"created_at" TEXT NOT NULL,
"owner_id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL
);
-- Contenuto caricato
CREATE TABLE IF NOT EXISTS "uploaded_contents"(
"id" TEXT NOT NULL PRIMARY KEY,
"created_at" TEXT NOT NULL,
"owner_id" TEXT NOT NULL,
"dispensa_id" TEXT NOT NULL,
"hash" TEXT NOT NULL
);
-- Contenuti con hash approvati
CREATE TABLE IF NOT EXISTS "hash_approvals"(
"id" TEXT NOT NULL PRIMARY KEY,
"created_at" TEXT NOT NULL,
"owner_id" TEXT NOT NULL,
"hash" TEXT NOT NULL
);
-- Contenuti con hash rifiutati
CREATE TABLE IF NOT EXISTS "hash_rejections"(
"id" TEXT NOT NULL PRIMARY KEY,
"created_at" TEXT NOT NULL,
"owner_id" TEXT NOT NULL,
"hash" TEXT NOT NULL
);
--
-- Relations
--
-- Tags per le dispense
CREATE TABLE IF NOT EXISTS "tags"(
"dispensa_id" TEXT NOT NULL,
"tags" TEXT NOT NULL
);

@ -0,0 +1,44 @@
package database
type Dispensa struct {
Id string `db:"id"`
Title string `db:"title"`
Description string `db:"description"`
}
type Tag struct {
DispensaId string `db:"dispensa_id"`
Tag string `db:"tag"`
}
type UploadedContent struct {
Id string `db:"id"`
Hash string `db:"hash"`
}
type HashApproval struct {
Id string `db:"id"`
Hash string `db:"hash"`
}
type HashRejection struct {
Id string `db:"id"`
Hash string `db:"hash"`
}
// Relations
type Owner struct {
OwnerId string `db:"owner_id"`
OwnedId string `db:"owned_id"`
}
type Author struct {
UserId string `db:"user_id"`
DispensaId string `db:"dispensa_id"`
}
type CreationTime struct {
EntityId string `db:"entity_id"`
CreatedAt string `db:"created_at"`
}

@ -0,0 +1,161 @@
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 {
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS migrations(timestamp TEXT, filename TEXT)`); err != nil {
return err
}
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(15)
if _, err := db.NamedExec(`
INSERT INTO
dispense(id, title, description)
VALUES
(: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 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 NewSqlite3Database(filename string) (*DB, error) {
sqlDB, err := sqlx.Open("sqlite3", filename)
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,
// DBUpload: db,
// DBApprovedHashes: db,
// DBRejectedHashes: db,
// DBOther: db,
}, nil
}

@ -18,7 +18,9 @@ require (
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/gorilla/feeds v1.1.1 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/klauspost/compress v1.15.6 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.37.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect

@ -15,21 +15,28 @@ github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gofiber/fiber/v2 v2.34.0 h1:96BJMw6uaxQhJsHY54SFGOtGgp9pgombK5Hbi4JSEQA=
github.com/gofiber/fiber/v2 v2.34.0/go.mod h1:ozRQfS+D7EL1+hMH+gutku0kfx1wLX4hAxDCtDzpj4U=
github.com/gofiber/redirect/v2 v2.1.23 h1:MqRyyeKyGqkF4GIFgTB4SuqIeeXviUglgRL2HCOFofM=
github.com/gofiber/redirect/v2 v2.1.23/go.mod h1:IYF5pPLDLYrrHMcxajDyWV+nHMbyPd6agCXkCnfLxS0=
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY=
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/litao91/goldmark-mathjax v0.0.0-20210217064022-a43cf739a50f h1:plCPYXRXDCO57qjqegCzaVf1t6aSbgCMD+zfz18POfs=
github.com/litao91/goldmark-mathjax v0.0.0-20210217064022-a43cf739a50f/go.mod h1:leg+HM7jUS84JYuY120zmU68R6+UeU6uZ/KAW7cViKE=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

@ -0,0 +1,44 @@
package model
import "time"
// Tables
type Dispensa struct {
// Id is a unique identifier of this object
Id string
// CreatedAt is the time of creation of this object
CreatedAt time.Time
// Title of this dispensa
Title string
// Description of this dispensa
Description string
// Tags for this dispensa, used by search to easily categorize dispense
Tags string
}
// Upload represents a file identified by its hash and stored in the appunti content store
type Upload struct {
// Id is a unique identifier of this object
Id string
// CreatedAt is the time of creation of this object
CreatedAt time.Time
// Hash of this file
Hash string
}
// ApprovedHash represents an approved hash
type ApprovedHash struct {
// Hash being approved
Hash string
// CreatedAt is the creation time of this object
CreatedAt time.Time
}
// RejectedHash represents a rejected hash
type RejectedHash struct {
// Hash being rejected
Hash string
// CreatedAt is the creation time of this object
CreatedAt time.Time
}
Loading…
Cancel
Save