working the dispense table and relative api route

feat/db
Antonio De Lucreziis 2 years ago
parent c7e33c8e72
commit 807a4c54ac

@ -9,8 +9,6 @@ import (
type Service interface { type Service interface {
GetDispensa(id string) (*model.Dispensa, error) GetDispensa(id string) (*model.Dispensa, error)
CreateDispensa(template model.Dispensa) (string, error) CreateDispensa(template model.Dispensa) (string, error)
SetDispensaTags(dispensaId string, tags []string) error
// GetDispensaTags(dispensaId string) ([]string, error)
} }
type DefaultService struct { type DefaultService struct {
@ -20,12 +18,12 @@ type DefaultService struct {
var _ Service = &DefaultService{} var _ Service = &DefaultService{}
func (s *DefaultService) GetDispensa(id string) (*model.Dispensa, error) { func (s *DefaultService) GetDispensa(id string) (*model.Dispensa, error) {
dispensa, err := s.DB.GetDispensa(id) dispensa, err := s.DB.Dispense.Get(id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tags, err := s.DB.GetTags(id) tags, err := s.DB.DispensaTags.Get(id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -40,13 +38,18 @@ func (s *DefaultService) GetDispensa(id string) (*model.Dispensa, error) {
} }
func (s *DefaultService) CreateDispensa(template model.Dispensa) (string, error) { func (s *DefaultService) CreateDispensa(template model.Dispensa) (string, error) {
return s.DB.CreateDispensa(database.Dispensa{ dispensaId, err := s.DB.Dispense.Create(database.Dispensa{
OwnerId: template.OwnerId, OwnerId: template.OwnerId,
Title: template.Title, Title: template.Title,
Description: template.Description, Description: template.Description,
}) })
} if err != nil {
return "", err
}
if err := s.DB.DispensaTags.Set(dispensaId, template.Tags); err != nil {
return "", err
}
func (s *DefaultService) SetDispensaTags(dispensaId string, tags []string) error { return dispensaId, nil
return s.DB.SetTags(dispensaId, tags)
} }

@ -7,7 +7,7 @@ import (
"git.phc.dm.unipi.it/phc/website/articles" "git.phc.dm.unipi.it/phc/website/articles"
"git.phc.dm.unipi.it/phc/website/auth" "git.phc.dm.unipi.it/phc/website/auth"
"git.phc.dm.unipi.it/phc/website/config" "git.phc.dm.unipi.it/phc/website/config"
"git.phc.dm.unipi.it/phc/website/database" "git.phc.dm.unipi.it/phc/website/database/sqlite"
"git.phc.dm.unipi.it/phc/website/handler" "git.phc.dm.unipi.it/phc/website/handler"
"git.phc.dm.unipi.it/phc/website/lista_utenti" "git.phc.dm.unipi.it/phc/website/lista_utenti"
"git.phc.dm.unipi.it/phc/website/server" "git.phc.dm.unipi.it/phc/website/server"
@ -22,7 +22,7 @@ func main() {
auth := auth.NewDefaultService(config.AuthServiceHost) auth := auth.NewDefaultService(config.AuthServiceHost)
// Create database connection and apply pending migrations // Create database connection and apply pending migrations
db := util.Must(database.NewSqlite3Database("phc-server.local.db")) db := util.Must(sqlite.New("phc-server.local.db"))
if err := db.Migrate("./database/migrations"); err != nil { if err := db.Migrate("./database/migrations"); err != nil {
panic(err) panic(err)
} }
@ -40,7 +40,7 @@ func main() {
Path: "./_content/storia.yaml", Path: "./_content/storia.yaml",
}, },
ListaUtenti: util.Must(lista_utenti.New(auth, config.ListaUtenti)), ListaUtenti: util.Must(lista_utenti.New(auth, config.ListaUtenti)),
AppuntiService: &appunti.DefaultService{ Appunti: &appunti.DefaultService{
DB: db, DB: db,
}, },
}) })

@ -5,7 +5,7 @@ type DBMigrate interface {
} }
type DBQueryAppunti interface { type DBQueryAppunti interface {
AllDispenseFile() ([]DispensaFile, error) AllDispensaFile() ([]DispensaFile, error)
} }
type DBDispense interface { type DBDispense interface {
@ -38,12 +38,13 @@ type DBDownloads interface {
Create(template Download) error 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.
type DB struct { type DB struct {
DBMigrate DBMigrate
DBDispense DBQueryAppunti
DBUploads
DBFileApprovals Dispense DBDispense
DBDispensaTags Uploads DBUploads
DBDownloads FileApprovals DBFileApprovals
DispensaTags DBDispensaTags
Downloads DBDownloads
} }

@ -1,5 +1,9 @@
package database package database
//
// Tables
//
type Dispensa struct { type Dispensa struct {
Id string `db:"id"` Id string `db:"id"`
CreatedAt string `db:"created_at"` CreatedAt string `db:"created_at"`
@ -8,15 +12,6 @@ type Dispensa struct {
Description string `db:"description"` 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 { type Upload struct {
Id string `db:"id"` Id string `db:"id"`
CreatedAt string `db:"created_at"` CreatedAt string `db:"created_at"`
@ -42,3 +37,17 @@ type Tag struct {
DispensaId string `db:"dispensa_id"` DispensaId string `db:"dispensa_id"`
Tag string `db:"tag"` Tag string `db:"tag"`
} }
//
// Common Joins
//
// DispensaFile is a join of dispensa with the most recent upload for that "dispensa_id"
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"`
}

@ -23,9 +23,12 @@ func New(filename string) (*database.DB, error) {
return &database.DB{ return &database.DB{
DBMigrate: &sqliteDBMigrate{db}, DBMigrate: &sqliteDBMigrate{db},
DBDispense: &sqliteDBDispense{db}, // DBQueryAppunti: ...,
DBUploads: &sqliteDBUploads{db},
DBFileApprovals: &sqliteDBFileApprovals{db}, Dispense: &sqliteDBDispense{db},
DBDownloads: &sqliteDBDownloads{db}, Uploads: &sqliteDBUploads{db},
FileApprovals: &sqliteDBFileApprovals{db},
Downloads: &sqliteDBDownloads{db},
DispensaTags: &sqliteDBTags{db},
}, nil }, nil
} }

@ -13,7 +13,7 @@ type sqliteDBDispense struct{ *sqlx.DB }
var _ database.DBDispense = sqliteDBDispense{} var _ database.DBDispense = sqliteDBDispense{}
func (d sqliteDBDispense) Create(template database.Dispensa) (string, error) { func (d sqliteDBDispense) Create(template database.Dispensa) (string, error) {
template.Id = "dispensa/" + util.GenerateRandomString(8) template.Id = util.GenerateRandomString(8)
template.CreatedAt = time.Now().Format(time.RFC3339) template.CreatedAt = time.Now().Format(time.RFC3339)
if _, err := d.DB.NamedExec(` if _, err := d.DB.NamedExec(`

@ -11,7 +11,7 @@ type sqliteDBUploads struct{ *sqlx.DB }
var _ database.DBUploads = sqliteDBUploads{} var _ database.DBUploads = sqliteDBUploads{}
func (u sqliteDBUploads) Create(template database.Upload) (string, error) { func (u sqliteDBUploads) Create(template database.Upload) (string, error) {
template.Id = "upload/" + util.GenerateRandomString(8) template.Id = util.GenerateRandomString(8)
if _, err := u.DB.NamedExec(` if _, err := u.DB.NamedExec(`
INSERT INTO INSERT INTO

@ -1,26 +1,64 @@
package sqlite package sqlite
import ( import (
"time"
"git.phc.dm.unipi.it/phc/website/database" "git.phc.dm.unipi.it/phc/website/database"
"git.phc.dm.unipi.it/phc/website/util"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
type sqliteDBFileApprovals struct{ *sqlx.DB } type sqliteDBFileApprovals struct{ *sqlx.DB }
func (f sqliteDBFileApprovals) Create(template database.FileApproval) (string, error) { func (f sqliteDBFileApprovals) Create(template database.FileApproval) (string, error) {
panic("not implemented") // TODO: Implement template.Id = util.GenerateRandomString(8)
template.CreatedAt = time.Now().Format(time.RFC3339)
if _, err := f.DB.NamedExec(`
INSERT INTO
file_approvals(id, created_at, owner_id, upload_id, status)
VALUES
(:id, :created_at, :owner_id, :upload_id, :status)
`, &template); err != nil {
return "", err
}
return template.Id, nil
} }
func (f sqliteDBFileApprovals) Get(id string) (database.FileApproval, error) { func (f sqliteDBFileApprovals) Get(id string) (database.FileApproval, error) {
panic("not implemented") // TODO: Implement var fileApproval database.FileApproval
if err := f.DB.Get(&fileApproval, `SELECT * FROM file_approvals WHERE id = ?`, id); err != nil {
return database.FileApproval{}, err
}
return fileApproval, nil
} }
func (f sqliteDBFileApprovals) All() ([]database.FileApproval, error) { func (f sqliteDBFileApprovals) All() ([]database.FileApproval, error) {
panic("not implemented") // TODO: Implement var fileApprovals []database.FileApproval
if err := f.DB.Select(&fileApprovals, `SELECT * FROM file_approvals`); err != nil {
return nil, err
}
return fileApprovals, nil
} }
func (f sqliteDBFileApprovals) Update(d database.FileApproval) error { func (f sqliteDBFileApprovals) Update(fileApproval database.FileApproval) error {
panic("not implemented") // TODO: Implement if _, err := f.DB.NamedExec(`
UPDATE
file_approvals
SET
owner_id = :owner_id,
upload_id = :upload_id,
status = :status
WHERE
id = :id
`, &fileApproval); err != nil {
return err
}
return nil
} }
func (f sqliteDBFileApprovals) Delete(id string) error { func (f sqliteDBFileApprovals) Delete(id string) error {

@ -0,0 +1,52 @@
package sqlite
import (
"git.phc.dm.unipi.it/phc/website/database"
"github.com/jmoiron/sqlx"
)
type sqliteDBTags struct{ *sqlx.DB }
var _ database.DBDispensaTags = sqliteDBTags{}
func (t sqliteDBTags) Set(dispensaId string, tags []string) error {
tagsRows := []map[string]any{}
for _, t := range tags {
tagsRows = append(tagsRows, map[string]any{
"dispensa_id": dispensaId,
"name": t,
})
}
tx, err := t.DB.Beginx()
if err != nil {
return err
}
if _, err := tx.Exec(`
DELETE FROM tags WHERE dispensa_id = ?
`, dispensaId); err != nil {
return err
}
if _, err := tx.NamedExec(`
INSERT INTO tags(dispensa_id, name) VALUES (:dispensa_id, :name)
`, tagsRows); err != nil {
return err
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (t sqliteDBTags) Get(dispensaId string) ([]string, error) {
var tags []string
if err := t.DB.Select(&tags, `SELECT name FROM tags WHERE dispensa_id = ?`, dispensaId); err != nil {
return nil, err
}
return tags, nil
}

@ -61,7 +61,8 @@ type Service interface {
HandleListaUtenti() ([]*model.ListUser, error) HandleListaUtenti() ([]*model.ListUser, error)
// Appunti // Appunti
HandleCreateDispensa(template model.Dispensa, ctx Context) (*model.Dispensa, error) HandleCreateDispensa(template model.Dispensa, ctx Context) (string, error)
HandleGetDispensa(dispensaId string) (*model.Dispensa, error)
} }
// //
@ -78,7 +79,7 @@ func (ctx Context) getUser() *model.User {
// Handler holds references to abstract services for easy testing provided by every module (TODO: Make every field an interface of -Service) // Handler holds references to abstract services for easy testing provided by every module (TODO: Make every field an interface of -Service)
type DefaultHandler struct { type DefaultHandler struct {
AuthService auth.Service AuthService auth.Service
AppuntiService appunti.Service Appunti appunti.Service
Renderer *templates.TemplateRenderer Renderer *templates.TemplateRenderer
NewsArticlesRegistry *articles.Registry NewsArticlesRegistry *articles.Registry
GuideArticlesRegistry *articles.Registry GuideArticlesRegistry *articles.Registry
@ -138,7 +139,7 @@ func (h *DefaultHandler) HandleAppuntiCondivisiPage(w io.Writer, ctx Context) er
} }
func (h *DefaultHandler) HandleDispensaPage(w io.Writer, id string, ctx Context) error { func (h *DefaultHandler) HandleDispensaPage(w io.Writer, id string, ctx Context) error {
dispensa, err := h.AppuntiService.GetDispensa(id) dispensa, err := h.Appunti.GetDispensa(id)
if err != nil { if err != nil {
return err return err
} }
@ -271,23 +272,22 @@ func (h *DefaultHandler) HandleGuideFeedPage(w io.Writer) error {
// API // API
// //
func (h *DefaultHandler) HandleCreateDispensa(template model.Dispensa, ctx Context) (*model.Dispensa, error) { func (h *DefaultHandler) HandleCreateDispensa(template model.Dispensa, ctx Context) (string, error) {
user := ctx.getUser() user := ctx.getUser()
if user == nil { if user == nil {
return nil, ErrNoUser return "", ErrNoUser
} }
template.OwnerId = user.Username template.OwnerId = user.Username
id, err := h.AppuntiService.CreateDispensa(template)
dispensaId, err := h.Appunti.CreateDispensa(template)
if err != nil { if err != nil {
return nil, err return "", err
} }
template.Id = id return dispensaId, nil
}
if len(template.Tags) > 0 {
h.AppuntiService.SetDispensaTags(id, template.Tags)
}
return &template, nil func (h *DefaultHandler) HandleGetDispensa(id string) (*model.Dispensa, error) {
return h.Appunti.GetDispensa(id)
} }

@ -10,7 +10,6 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/etag" "github.com/gofiber/fiber/v2/middleware/etag"
"github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/redirect/v2" "github.com/gofiber/redirect/v2"
) )
@ -46,7 +45,7 @@ func routes(h handler.Service, r fiber.Router) {
// //
r.Use(logger.New()) r.Use(logger.New())
r.Use(recover.New()) // r.Use(recover.New())
// Remove trailing slash from URLs // Remove trailing slash from URLs
r.Use(redirect.New(redirect.Config{ r.Use(redirect.New(redirect.Config{
@ -223,13 +222,23 @@ func routesApi(h handler.Service, r fiber.Router) {
return c.JSON(user) return c.JSON(user)
}) })
r.Post("/appunti", func(c *fiber.Ctx) error { r.Post("/appunti/dispense", func(c *fiber.Ctx) error {
var dispensaTemplate model.Dispensa var dispensaTemplate model.Dispensa
if err := c.BodyParser(&dispensaTemplate); err != nil { if err := c.BodyParser(&dispensaTemplate); err != nil {
return err return err
} }
dispensa, err := h.HandleCreateDispensa(dispensaTemplate, CreateContext(c)) dispensaId, err := h.HandleCreateDispensa(dispensaTemplate, CreateContext(c))
if err != nil {
return err
}
return c.JSON(dispensaId)
})
r.Get("/appunti/dispense", func(c *fiber.Ctx) error {
dispensaId := c.Query("id")
dispensa, err := h.HandleGetDispensa(dispensaId)
if err != nil { if err != nil {
return err return err
} }

Loading…
Cancel
Save