First table is working

feat/db
Antonio De Lucreziis 2 years ago
parent 2485133661
commit 9df45cf83a

@ -329,7 +329,7 @@ const TabellaApprovazioni = ({ pendingApprovazioni }) => {
return ( return (
<div class="table approvazioni"> <div class="table approvazioni">
<div class="download header"></div> <div class="download header"></div>
<div class="hash header">PDF (Hash)</div> <div class="hash header">PDF</div>
<div class="title header">Dispensa</div> <div class="title header">Dispensa</div>
<div class="owner header">Proprietario</div> <div class="owner header">Proprietario</div>
<div class="actions header">Azioni</div> <div class="actions header">Azioni</div>
@ -347,7 +347,7 @@ const TabellaApprovazioni = ({ pendingApprovazioni }) => {
</div> </div>
<div class="title"> <div class="title">
<a href={`/appunti/${id}`} title={id}> <a href={`/appunti/${id}`} title={id}>
<i class="fa-solid fa-book"></i> {title} {title}
</a> </a>
</div> </div>
<div class="owner"> <div class="owner">
@ -413,9 +413,7 @@ const App = ({}) => {
<div class="edit-container"> <div class="edit-container">
<div class="header"> <div class="header">
<a href="/appunti/6f82dca3d83b475c"> <a href="/appunti/6f82dca3d83b475c">
<span class="title"> <span class="title">Mezzedimi</span>
<i class="fa-solid fa-book"></i> Mezzedimi
</span>
</a> </a>
</div> </div>
<div class="form"> <div class="form">

@ -0,0 +1,14 @@
{{template "base" .}}
{{define "title"}}{{ .Dispensa.Title }} &bull; Appunti &bull; PHC{{end}}
{{define "body"}}
<section>
<h1>
<i class="fas fa-book"></i>
{{ .Dispensa.Title }}
</h1>
<p>
{{ .Dispensa.Description }}
</p>
{{end}}

@ -2,16 +2,51 @@ package appunti
import ( import (
"git.phc.dm.unipi.it/phc/website/database" "git.phc.dm.unipi.it/phc/website/database"
"git.phc.dm.unipi.it/phc/website/model"
) )
// Service isola l'handler dal modulo del database e si occupa solo delle interazioni riguardanti gli appunti e le dispense
type Service interface { type Service interface {
ViewDispense() error 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 { type DefaultService struct {
DB database.DB DB *database.DB
} }
func (s *DefaultService) CreateDispensa(user string, template database.Dispensa) { var _ Service = &DefaultService{}
func (s *DefaultService) GetDispensa(id string) (*model.Dispensa, error) {
dispensa, err := s.DB.GetDispensa(id)
if err != nil {
return nil, err
}
tags, err := s.DB.GetTags(id)
if err != nil {
return nil, err
}
return &model.Dispensa{
Id: dispensa.Id,
OwnerId: dispensa.OwnerId,
Title: dispensa.Title,
Description: dispensa.Description,
Tags: tags,
}, nil
}
func (s *DefaultService) CreateDispensa(template model.Dispensa) (string, error) {
return s.DB.CreateDispensa(database.Dispensa{
OwnerId: template.OwnerId,
Title: template.Title,
Description: template.Description,
})
}
func (s *DefaultService) SetDispensaTags(dispensaId string, tags []string) error {
return s.DB.SetTags(dispensaId, tags)
} }

@ -3,28 +3,32 @@ package main
import ( import (
"log" "log"
"git.phc.dm.unipi.it/phc/website/appunti"
"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/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"
"git.phc.dm.unipi.it/phc/website/storia" "git.phc.dm.unipi.it/phc/website/storia"
"git.phc.dm.unipi.it/phc/website/templates" "git.phc.dm.unipi.it/phc/website/templates"
"git.phc.dm.unipi.it/phc/website/util"
) )
func main() { func main() {
config.Load() config.Load()
authService := auth.NewDefaultService(config.AuthServiceHost) auth := auth.NewDefaultService(config.AuthServiceHost)
listaUtentiService, err := lista_utenti.New(authService, config.ListaUtenti) // Create database connection and apply pending migrations
if err != nil { db := util.Must(database.NewSqlite3Database("phc-server.local.db"))
log.Fatal(err) if err := db.Migrate("./database/migrations"); err != nil {
panic(err)
} }
h := &handler.DefaultHandler{ app := server.NewFiberServer(&handler.DefaultHandler{
AuthService: authService, AuthService: auth,
Renderer: templates.NewRenderer( Renderer: templates.NewRenderer(
"./_views/", "./_views/",
"./_views/base.html", "./_views/base.html",
@ -35,13 +39,14 @@ func main() {
Storia: &storia.JsonFileStoria{ Storia: &storia.JsonFileStoria{
Path: "./_content/storia.yaml", Path: "./_content/storia.yaml",
}, },
ListaUtenti: listaUtentiService, ListaUtenti: util.Must(lista_utenti.New(auth, config.ListaUtenti)),
} AppuntiService: &appunti.DefaultService{
DB: db,
app := server.NewFiberServer(h) },
})
log.Printf("Starting server on host %q", config.Host) log.Printf("Starting server on host %q", config.Host)
if err := app.Listen(config.Host); err != nil { if err := app.Listen(config.Host); err != nil {
log.Fatal(err) panic(err)
} }
} }

@ -1,15 +1,11 @@
package database package database
import "time"
// Tools // Tools
type DBMigrate interface { type DBMigrate interface {
Migrate(migrationDir string) error Migrate(migrationDir string) error
} }
// Entities
type DBDispense interface { type DBDispense interface {
CreateDispensa(template Dispensa) (string, error) CreateDispensa(template Dispensa) (string, error)
GetDispensa(id string) (Dispensa, error) GetDispensa(id string) (Dispensa, error)
@ -18,74 +14,34 @@ type DBDispense interface {
DeleteDispensa(id string) error DeleteDispensa(id string) error
} }
type DBUpload interface { type DBUploads interface {
CreateUpload(template UploadedContent) (string, error) CreateUpload(template Upload) (string, error)
DeleteUpload(id UploadedContent) (UploadedContent, error) GetUpload(id string) (Upload, error)
} }
type DBHashApprovals interface { type DBFileApprovals interface {
CreateApprovedHash(a HashApproval) error CreateFileApproval(template FileApproval) (string, error)
GetApprovedHash(hash string) (HashApproval, error) GetFileApproval(id string) (FileApproval, error)
AllApprovedHash(a HashApproval) error AllFileApprovals() ([]FileApproval, error)
UpdateFileApproval(d FileApproval) error
DeleteFileApproval(id string) error
} }
type DBHashRejections interface { type DBDownloads interface {
CreateRejectedHash(a HashRejection) error CreateDownload(template Download) (string, error)
GetRejectedHash(hash string) (HashRejection, error)
AllRejectedHash(a HashRejection) error
} }
// Relations
type DBTags interface { type DBTags interface {
SetTags(dispensaId string, tag []string) error SetTags(dispensaId string, tags []string) error
GetTags(dispensaId string) ([]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. // 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 DBDispense
DBUpload DBUploads
DBHashApprovals DBFileApprovals
DBHashRejections DBDownloads
DBTags DBTags
DBOwners
DBAuthors
DBCreationTimes
DBOther
} }

@ -1,8 +1,4 @@
--
-- Entities
--
-- Dispense -- Dispense
CREATE TABLE IF NOT EXISTS "dispense"( CREATE TABLE IF NOT EXISTS "dispense"(
"id" TEXT NOT NULL PRIMARY KEY, "id" TEXT NOT NULL PRIMARY KEY,
@ -22,7 +18,7 @@ CREATE TABLE IF NOT EXISTS "uploads"(
FOREIGN KEY (dispensa_id) REFERENCES dispense(id) FOREIGN KEY (dispensa_id) REFERENCES dispense(id)
); );
-- Contenuti con hash approvati -- Approvazioni contenuti caricati
CREATE TABLE IF NOT EXISTS "file_approvals"( CREATE TABLE IF NOT EXISTS "file_approvals"(
"id" TEXT NOT NULL PRIMARY KEY, "id" TEXT NOT NULL PRIMARY KEY,
"created_at" TEXT NOT NULL, "created_at" TEXT NOT NULL,
@ -37,11 +33,7 @@ CREATE TABLE IF NOT EXISTS "downloads"(
"dispensa_id" TEXT NOT NULL, "dispensa_id" TEXT NOT NULL,
"timestamp" TEXT NOT NULL, "timestamp" TEXT NOT NULL,
FOREIGN KEY (dispensa_id) REFERENCES dispense(id) FOREIGN KEY (dispensa_id) REFERENCES dispense(id)
) );
--
-- Relations
--
-- Tags per le dispense -- Tags per le dispense
CREATE TABLE IF NOT EXISTS "tags"( CREATE TABLE IF NOT EXISTS "tags"(

@ -2,43 +2,34 @@ package database
type Dispensa struct { type Dispensa struct {
Id string `db:"id"` Id string `db:"id"`
CreatedAt string `db:"created_at"`
OwnerId string `db:"owner_id"`
Title string `db:"title"` Title string `db:"title"`
Description string `db:"description"` Description string `db:"description"`
} }
type Tag struct { type Upload struct {
DispensaId string `db:"dispensa_id"`
Tag string `db:"tag"`
}
type UploadedContent struct {
Id string `db:"id"` Id string `db:"id"`
Hash string `db:"hash"` CreatedAt string `db:"created_at"`
} OwnerId string `db:"owner_id"`
DispensaId string `db:"dispensa_id"`
type HashApproval struct { File string `db:"file"`
Id string `db:"id"`
Hash string `db:"hash"`
} }
type HashRejection struct { type FileApproval struct {
Id string `db:"id"` Id string `db:"id"`
Hash string `db:"hash"` CreatedAt string `db:"created_at"`
}
// Relations
type Owner struct {
OwnerId string `db:"owner_id"` OwnerId string `db:"owner_id"`
OwnedId string `db:"owned_id"` UploadId string `db:"upload_id"`
Status string `db:"status"`
} }
type Author struct { type Download struct {
UserId string `db:"user_id"`
DispensaId string `db:"dispensa_id"` DispensaId string `db:"dispensa_id"`
Timestamp string `db:"timestamp"`
} }
type CreationTime struct { type Tag struct {
EntityId string `db:"entity_id"` DispensaId string `db:"dispensa_id"`
CreatedAt string `db:"created_at"` Tag string `db:"tag"`
} }

@ -22,10 +22,12 @@ type sqliteDB struct {
} }
func (db *sqliteDB) Migrate(migrationFolder string) error { 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 { if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS migrations(timestamp TEXT, filename TEXT)`); err != nil {
return err return err
} }
log.Printf(`Loading applied migrations`)
appliedMigrations := []migration{} appliedMigrations := []migration{}
if err := db.Select(&appliedMigrations, `SELECT * FROM migrations`); err != nil { if err := db.Select(&appliedMigrations, `SELECT * FROM migrations`); err != nil {
return err return err
@ -86,13 +88,14 @@ func (db *sqliteDB) Migrate(migrationFolder string) error {
} }
func (db *sqliteDB) CreateDispensa(template Dispensa) (string, error) { func (db *sqliteDB) CreateDispensa(template Dispensa) (string, error) {
template.Id = "dispensa/" + util.GenerateRandomString(15) template.Id = "dispensa/" + util.GenerateRandomString(8)
template.CreatedAt = time.Now().Format(time.RFC3339)
if _, err := db.NamedExec(` if _, err := db.NamedExec(`
INSERT INTO INSERT INTO
dispense(id, title, description) dispense(id, created_at, owner_id, title, description)
VALUES VALUES
(:id, :title, :description) (:id, :created_at, :owner_id, :title, :description)
`, &template); err != nil { `, &template); err != nil {
return "", err return "", err
} }
@ -102,7 +105,6 @@ func (db *sqliteDB) CreateDispensa(template Dispensa) (string, error) {
func (db *sqliteDB) GetDispensa(id string) (Dispensa, error) { func (db *sqliteDB) GetDispensa(id string) (Dispensa, error) {
var dispensa Dispensa var dispensa Dispensa
if err := db.Get(&dispensa, `SELECT * FROM dispense WHERE id = ?`, id); err != nil { if err := db.Get(&dispensa, `SELECT * FROM dispense WHERE id = ?`, id); err != nil {
return Dispensa{}, err return Dispensa{}, err
} }
@ -112,7 +114,6 @@ func (db *sqliteDB) GetDispensa(id string) (Dispensa, error) {
func (db *sqliteDB) AllDispense() ([]Dispensa, error) { func (db *sqliteDB) AllDispense() ([]Dispensa, error) {
var dispense []Dispensa var dispense []Dispensa
if err := db.Select(&dispense, `SELECT * FROM dispense`); err != nil { if err := db.Select(&dispense, `SELECT * FROM dispense`); err != nil {
return nil, err return nil, err
} }
@ -122,10 +123,14 @@ func (db *sqliteDB) AllDispense() ([]Dispensa, error) {
func (db *sqliteDB) UpdateDispensa(d Dispensa) error { func (db *sqliteDB) UpdateDispensa(d Dispensa) error {
if _, err := db.NamedExec(` if _, err := db.NamedExec(`
UPDATE dispense UPDATE
SET title = :title, dispense
SET
owner_id = :owner_id
title = :title,
description = :description description = :description
WHERE id = :id WHERE
id = :id
`, &d); err != nil { `, &d); err != nil {
return err return err
} }
@ -137,8 +142,32 @@ func (db *sqliteDB) DeleteDispensa(id string) error {
panic("TODO: Not implemented") 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) { func NewSqlite3Database(filename string) (*DB, error) {
sqlDB, err := sqlx.Open("sqlite3", filename) sqlDB, err := sqlx.Open("sqlite3", filename+"?_fk=1")
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -153,9 +182,6 @@ func NewSqlite3Database(filename string) (*DB, error) {
return &DB{ return &DB{
DBMigrate: db, DBMigrate: db,
DBDispense: db, DBDispense: db,
// DBUpload: db, DBUploads: db,
// DBApprovedHashes: db,
// DBRejectedHashes: db,
// DBOther: db,
}, nil }, nil
} }

@ -5,6 +5,7 @@ import (
"html/template" "html/template"
"io" "io"
"git.phc.dm.unipi.it/phc/website/appunti"
"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/lista_utenti" "git.phc.dm.unipi.it/phc/website/lista_utenti"
@ -16,22 +17,51 @@ import (
) )
type Service interface { type Service interface {
//
// Pages
//
HandleStaticPage(w io.Writer, view string, ctx Context) error HandleStaticPage(w io.Writer, view string, ctx Context) error
HandleUtenti() ([]*model.User, error)
HandleListaUtenti() ([]*model.ListUser, error) // Storia
HandleStoriaPage(w io.Writer, ctx Context) error HandleStoriaPage(w io.Writer, ctx Context) error
HandleQueryAppunti(w io.Writer, query string, ctx Context) error
// Appunti
HandleAppuntiPage(w io.Writer, query string, ctx Context) error
HandleAppuntiCondivisiPage(w io.Writer, ctx Context) error HandleAppuntiCondivisiPage(w io.Writer, ctx Context) error
HandleDispensaPage(w io.Writer, id string, ctx Context) error
// News
HandleNewsPage(w io.Writer, ctx Context) error HandleNewsPage(w io.Writer, ctx Context) error
// Guide
HandleGuidePage(w io.Writer, ctx Context) error HandleGuidePage(w io.Writer, ctx Context) error
HandleLogin(username, password string) (*model.Session, error)
HandleUser(token string) *model.User // User
HandleRequiredUser(ctx Context) (*model.User, error)
HandleProfilePage(w io.Writer, ctx Context) error HandleProfilePage(w io.Writer, ctx Context) error
// Article Pages
HandleNewsArticlePage(w io.Writer, articleID string, ctx Context) error HandleNewsArticlePage(w io.Writer, articleID string, ctx Context) error
HandleGuideArticlePage(w io.Writer, articleID string, ctx Context) error HandleGuideArticlePage(w io.Writer, articleID string, ctx Context) error
// RSS
HandleNewsFeedPage(w io.Writer) error HandleNewsFeedPage(w io.Writer) error
HandleGuideFeedPage(w io.Writer) error HandleGuideFeedPage(w io.Writer) error
//
// API
//
// User
HandleLogin(username, password string) (*model.Session, error)
HandleUser(token string) *model.User
HandleRequiredUser(ctx Context) (*model.User, error)
// User Listing
HandleUtenti() ([]*model.User, error)
HandleListaUtenti() ([]*model.ListUser, error)
// Appunti
HandleCreateDispensa(template model.Dispensa, ctx Context) (*model.Dispensa, error)
} }
// //
@ -48,6 +78,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
Renderer *templates.TemplateRenderer Renderer *templates.TemplateRenderer
NewsArticlesRegistry *articles.Registry NewsArticlesRegistry *articles.Registry
GuideArticlesRegistry *articles.Registry GuideArticlesRegistry *articles.Registry
@ -55,6 +86,8 @@ type DefaultHandler struct {
Storia storia.StoriaService Storia storia.StoriaService
} }
var _ Service = &DefaultHandler{}
func (h *DefaultHandler) HandleStaticPage(w io.Writer, view string, ctx Context) error { func (h *DefaultHandler) HandleStaticPage(w io.Writer, view string, ctx Context) error {
return h.Renderer.Render(w, view, util.Map{ return h.Renderer.Render(w, view, util.Map{
"User": ctx.getUser(), "User": ctx.getUser(),
@ -91,7 +124,7 @@ func (h *DefaultHandler) HandleStoriaPage(w io.Writer, ctx Context) error {
}) })
} }
func (h *DefaultHandler) HandleQueryAppunti(w io.Writer, query string, ctx Context) error { func (h *DefaultHandler) HandleAppuntiPage(w io.Writer, query string, ctx Context) error {
return h.Renderer.Render(w, "appunti.html", util.Map{ return h.Renderer.Render(w, "appunti.html", util.Map{
"User": ctx.getUser(), "User": ctx.getUser(),
"Query": query, "Query": query,
@ -104,6 +137,18 @@ 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)
if err != nil {
return err
}
return h.Renderer.Render(w, "dispensa.html", util.Map{
"User": ctx.getUser(),
"Dispensa": dispensa,
})
}
func (h *DefaultHandler) HandleNewsPage(w io.Writer, ctx Context) error { func (h *DefaultHandler) HandleNewsPage(w io.Writer, ctx Context) error {
articles, err := h.NewsArticlesRegistry.GetArticles() articles, err := h.NewsArticlesRegistry.GetArticles()
if err != nil { if err != nil {
@ -221,3 +266,28 @@ func (h *DefaultHandler) HandleGuideFeedPage(w io.Writer) error {
return guideFeed.WriteRss(w) return guideFeed.WriteRss(w)
} }
//
// API
//
func (h *DefaultHandler) HandleCreateDispensa(template model.Dispensa, ctx Context) (*model.Dispensa, error) {
user := ctx.getUser()
if user == nil {
return nil, ErrNoUser
}
template.OwnerId = user.Username
id, err := h.AppuntiService.CreateDispensa(template)
if err != nil {
return nil, err
}
template.Id = id
if len(template.Tags) > 0 {
h.AppuntiService.SetDispensaTags(id, template.Tags)
}
return &template, nil
}

@ -1,44 +1,9 @@
package model package model
import "time"
// Tables
type Dispensa struct { type Dispensa struct {
// Id is a unique identifier of this object Id string `json:"id"`
Id string OwnerId string `json:"ownerId"`
// CreatedAt is the time of creation of this object Title string `json:"title"`
CreatedAt time.Time Description string `json:"description"`
// Title of this dispensa Tags []string `json:"tags"`
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
} }

@ -115,7 +115,7 @@ func routes(h handler.Service, r fiber.Router) {
query := c.Query("q", "") query := c.Query("q", "")
c.Type("html") c.Type("html")
return h.HandleQueryAppunti(c, query, CreateContext(c)) return h.HandleAppuntiPage(c, query, CreateContext(c))
}) })
r.Get("/appunti/condivisi", func(c *fiber.Ctx) error { r.Get("/appunti/condivisi", func(c *fiber.Ctx) error {
@ -222,4 +222,18 @@ func routesApi(h handler.Service, r fiber.Router) {
return c.JSON(user) return c.JSON(user)
}) })
r.Post("/appunti", 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))
if err != nil {
return err
}
return c.JSON(dispensa)
})
} }

@ -0,0 +1,11 @@
package util
import "log"
func Must[T any](value T, err error) T {
if err != nil {
log.Fatal(err)
}
return value
}

@ -2,11 +2,11 @@ package util
import ( import (
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/hex"
"log" "log"
) )
// GenerateRandomString generates a random base64 string from a random array of "n" bytes (to prevent padding let n be a multiple of 3) // GenerateRandomString generates a random hex string from a random array of "n" bytes
func GenerateRandomString(n int) string { func GenerateRandomString(n int) string {
b := make([]byte, n) b := make([]byte, n)
@ -15,5 +15,5 @@ func GenerateRandomString(n int) string {
log.Fatal(err) log.Fatal(err)
} }
return base64.URLEncoding.EncodeToString(b) return hex.EncodeToString(b)
} }

Loading…
Cancel
Save