diff --git a/appunti/appunti.go b/appunti/appunti.go index d2de2be..fb782f0 100644 --- a/appunti/appunti.go +++ b/appunti/appunti.go @@ -9,8 +9,6 @@ import ( type Service interface { GetDispensa(id string) (*model.Dispensa, error) CreateDispensa(template model.Dispensa) (string, error) - SetDispensaTags(dispensaId string, tags []string) error - // GetDispensaTags(dispensaId string) ([]string, error) } type DefaultService struct { @@ -20,12 +18,12 @@ type DefaultService struct { var _ Service = &DefaultService{} 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 { return nil, err } - tags, err := s.DB.GetTags(id) + tags, err := s.DB.DispensaTags.Get(id) if err != nil { 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) { - return s.DB.CreateDispensa(database.Dispensa{ + dispensaId, err := s.DB.Dispense.Create(database.Dispensa{ OwnerId: template.OwnerId, Title: template.Title, 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 s.DB.SetTags(dispensaId, tags) + return dispensaId, nil } diff --git a/cmd/phc-website-server/main.go b/cmd/phc-website-server/main.go index fc64820..f3e55a9 100644 --- a/cmd/phc-website-server/main.go +++ b/cmd/phc-website-server/main.go @@ -7,7 +7,7 @@ import ( "git.phc.dm.unipi.it/phc/website/articles" "git.phc.dm.unipi.it/phc/website/auth" "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/lista_utenti" "git.phc.dm.unipi.it/phc/website/server" @@ -22,7 +22,7 @@ func main() { auth := auth.NewDefaultService(config.AuthServiceHost) // 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 { panic(err) } @@ -40,7 +40,7 @@ func main() { Path: "./_content/storia.yaml", }, ListaUtenti: util.Must(lista_utenti.New(auth, config.ListaUtenti)), - AppuntiService: &appunti.DefaultService{ + Appunti: &appunti.DefaultService{ DB: db, }, }) diff --git a/database/database.go b/database/database.go index 08f501a..bbf68d2 100644 --- a/database/database.go +++ b/database/database.go @@ -5,7 +5,7 @@ type DBMigrate interface { } type DBQueryAppunti interface { - AllDispenseFile() ([]DispensaFile, error) + AllDispensaFile() ([]DispensaFile, error) } type DBDispense interface { @@ -38,12 +38,13 @@ 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. type DB struct { DBMigrate - DBDispense - DBUploads - DBFileApprovals - DBDispensaTags - DBDownloads + DBQueryAppunti + + Dispense DBDispense + Uploads DBUploads + FileApprovals DBFileApprovals + DispensaTags DBDispensaTags + Downloads DBDownloads } diff --git a/database/model.go b/database/model.go index 9004269..558d263 100644 --- a/database/model.go +++ b/database/model.go @@ -1,5 +1,9 @@ package database +// +// Tables +// + type Dispensa struct { Id string `db:"id"` CreatedAt string `db:"created_at"` @@ -8,15 +12,6 @@ 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"` @@ -42,3 +37,17 @@ type Tag struct { DispensaId string `db:"dispensa_id"` 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"` +} diff --git a/database/sqlite/000_sqlite.go b/database/sqlite/000_sqlite.go index 87d8deb..bbe5151 100644 --- a/database/sqlite/000_sqlite.go +++ b/database/sqlite/000_sqlite.go @@ -22,10 +22,13 @@ func New(filename string) (*database.DB, error) { } return &database.DB{ - DBMigrate: &sqliteDBMigrate{db}, - DBDispense: &sqliteDBDispense{db}, - DBUploads: &sqliteDBUploads{db}, - DBFileApprovals: &sqliteDBFileApprovals{db}, - DBDownloads: &sqliteDBDownloads{db}, + DBMigrate: &sqliteDBMigrate{db}, + // DBQueryAppunti: ..., + + Dispense: &sqliteDBDispense{db}, + Uploads: &sqliteDBUploads{db}, + FileApprovals: &sqliteDBFileApprovals{db}, + Downloads: &sqliteDBDownloads{db}, + DispensaTags: &sqliteDBTags{db}, }, nil } diff --git a/database/sqlite/002_dispense.go b/database/sqlite/002_dispense.go index 707c6a3..fc3d212 100644 --- a/database/sqlite/002_dispense.go +++ b/database/sqlite/002_dispense.go @@ -13,7 +13,7 @@ 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.Id = util.GenerateRandomString(8) template.CreatedAt = time.Now().Format(time.RFC3339) if _, err := d.DB.NamedExec(` diff --git a/database/sqlite/003_uploads.go b/database/sqlite/003_uploads.go index b8641f1..2da8e22 100644 --- a/database/sqlite/003_uploads.go +++ b/database/sqlite/003_uploads.go @@ -11,7 +11,7 @@ type sqliteDBUploads struct{ *sqlx.DB } var _ database.DBUploads = sqliteDBUploads{} 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(` INSERT INTO diff --git a/database/sqlite/004_fileapprovals.go b/database/sqlite/004_fileapprovals.go index 05bcc77..edf7e09 100644 --- a/database/sqlite/004_fileapprovals.go +++ b/database/sqlite/004_fileapprovals.go @@ -1,26 +1,64 @@ 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 sqliteDBFileApprovals struct{ *sqlx.DB } 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) { - 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) { - 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 { - panic("not implemented") // TODO: Implement +func (f sqliteDBFileApprovals) Update(fileApproval database.FileApproval) error { + 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 { diff --git a/database/sqlite/006_tags.go b/database/sqlite/006_tags.go new file mode 100644 index 0000000..8abf6ff --- /dev/null +++ b/database/sqlite/006_tags.go @@ -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 +} diff --git a/handler/handler.go b/handler/handler.go index 5c307ad..069c49e 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -61,7 +61,8 @@ type Service interface { HandleListaUtenti() ([]*model.ListUser, error) // 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) type DefaultHandler struct { AuthService auth.Service - AppuntiService appunti.Service + Appunti appunti.Service Renderer *templates.TemplateRenderer NewsArticlesRegistry *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 { - dispensa, err := h.AppuntiService.GetDispensa(id) + dispensa, err := h.Appunti.GetDispensa(id) if err != nil { return err } @@ -271,23 +272,22 @@ func (h *DefaultHandler) HandleGuideFeedPage(w io.Writer) error { // 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() if user == nil { - return nil, ErrNoUser + return "", ErrNoUser } template.OwnerId = user.Username - id, err := h.AppuntiService.CreateDispensa(template) + + dispensaId, err := h.Appunti.CreateDispensa(template) if err != nil { - return nil, err + return "", err } - template.Id = id - - if len(template.Tags) > 0 { - h.AppuntiService.SetDispensaTags(id, template.Tags) - } + return dispensaId, nil +} - return &template, nil +func (h *DefaultHandler) HandleGetDispensa(id string) (*model.Dispensa, error) { + return h.Appunti.GetDispensa(id) } diff --git a/server/fiber.go b/server/fiber.go index 55f1d58..af9810a 100644 --- a/server/fiber.go +++ b/server/fiber.go @@ -10,7 +10,6 @@ import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/etag" "github.com/gofiber/fiber/v2/middleware/logger" - "github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/redirect/v2" ) @@ -46,7 +45,7 @@ func routes(h handler.Service, r fiber.Router) { // r.Use(logger.New()) - r.Use(recover.New()) + // r.Use(recover.New()) // Remove trailing slash from URLs r.Use(redirect.New(redirect.Config{ @@ -223,13 +222,23 @@ func routesApi(h handler.Service, r fiber.Router) { 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 if err := c.BodyParser(&dispensaTemplate); err != nil { 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 { return err }