From 92cee16cc8bf78f744bfaa2657472f7829f90b99 Mon Sep 17 00:00:00 2001 From: Antonio De Lucreziis Date: Thu, 18 Aug 2022 17:08:05 +0200 Subject: [PATCH] Big refactor --- .gitignore | 1 + README.md | 2 +- .../news}/2021-12-22-notizia-1.md | 0 .../news}/2021-12-23-notizia-2.md | 0 .../news}/2021-12-24-notizia-3.md | 0 _content/public/css/main.css | 1 + {public => _content/public}/home.js | 0 .../public}/icons/icons8-electronics-100.png | Bin .../public}/images/logo-circuit-board.svg | 0 .../images/logo-circuit-board@100w.png | Bin .../public}/images/logo-circuit-board@1x.png | Bin .../public}/images/scritta-h.jpg | Bin .../public}/images/scritta-t-mini.jpg | Bin .../public}/images/scritta-t.jpg | Bin .../public}/images/scritta.jpg | Bin _content/public/js/profilo.min.js | 1 + _content/public/js/utenti.min.js | 1 + {public => _content/public}/style.css | 0 {public => _content/public}/theme-dark.css | 0 .../screenshots}/0_localhost_8000_home.png | Bin .../screenshots}/0_localhost_8000_login.png | Bin .../screenshots}/0_localhost_8000_utenti.png | Bin .../2021-12-17_localhost-8000_home.png | Bin .../screenshots}/2021-12-19_home.png | Bin .../screenshots}/2021-12-21_home.png | Bin {frontend => _frontend}/Makefile | 0 {frontend => _frontend}/package-lock.json | 0 {frontend => _frontend}/package.json | 0 {frontend => _frontend}/rollup.config.js | 0 {frontend => _frontend}/src/profilo.js | 0 {frontend => _frontend}/src/styles/main.scss | 0 .../src/styles/typography.scss | 0 {frontend => _frontend}/src/utenti.js | 0 {views => _views}/appunti.html | 0 {views => _views}/base.html | 0 {views => _views}/home.html | 0 {views => _views}/link.html | 0 {views => _views}/login.html | 0 {views => _views}/news-base.html | 0 {views => _views}/news.html | 0 {views => _views}/partials/navbar.html | 0 {views => _views}/profilo.html | 0 {views => _views}/storia.html | 0 {views => _views}/utenti.html | 0 cmd/phc-website-server/main.go | 41 ++++ config/config.go | 4 +- handler/context.go | 21 ++ handler/handler.go | 147 +++++++++++++ main.go | 208 ------------------ server/fiber.go | 172 +++++++++++++++ storia.go | 120 +++++----- templates/templates.go | 6 +- utenti.go | 23 +- util/generics.go | 1 - util/objects.go | 6 +- util/rand.go | 1 + 56 files changed, 469 insertions(+), 287 deletions(-) rename {news => _content/news}/2021-12-22-notizia-1.md (100%) rename {news => _content/news}/2021-12-23-notizia-2.md (100%) rename {news => _content/news}/2021-12-24-notizia-3.md (100%) create mode 100644 _content/public/css/main.css rename {public => _content/public}/home.js (100%) rename {public => _content/public}/icons/icons8-electronics-100.png (100%) rename {public => _content/public}/images/logo-circuit-board.svg (100%) rename {public => _content/public}/images/logo-circuit-board@100w.png (100%) rename {public => _content/public}/images/logo-circuit-board@1x.png (100%) rename {public => _content/public}/images/scritta-h.jpg (100%) rename {public => _content/public}/images/scritta-t-mini.jpg (100%) rename {public => _content/public}/images/scritta-t.jpg (100%) rename {public => _content/public}/images/scritta.jpg (100%) create mode 100644 _content/public/js/profilo.min.js create mode 100644 _content/public/js/utenti.min.js rename {public => _content/public}/style.css (100%) rename {public => _content/public}/theme-dark.css (100%) rename {_screenshots => _docs/screenshots}/0_localhost_8000_home.png (100%) rename {_screenshots => _docs/screenshots}/0_localhost_8000_login.png (100%) rename {_screenshots => _docs/screenshots}/0_localhost_8000_utenti.png (100%) rename {_screenshots => _docs/screenshots}/2021-12-17_localhost-8000_home.png (100%) rename {_screenshots => _docs/screenshots}/2021-12-19_home.png (100%) rename {_screenshots => _docs/screenshots}/2021-12-21_home.png (100%) rename {frontend => _frontend}/Makefile (100%) rename {frontend => _frontend}/package-lock.json (100%) rename {frontend => _frontend}/package.json (100%) rename {frontend => _frontend}/rollup.config.js (100%) rename {frontend => _frontend}/src/profilo.js (100%) rename {frontend => _frontend}/src/styles/main.scss (100%) rename {frontend => _frontend}/src/styles/typography.scss (100%) rename {frontend => _frontend}/src/utenti.js (100%) rename {views => _views}/appunti.html (100%) rename {views => _views}/base.html (100%) rename {views => _views}/home.html (100%) rename {views => _views}/link.html (100%) rename {views => _views}/login.html (100%) rename {views => _views}/news-base.html (100%) rename {views => _views}/news.html (100%) rename {views => _views}/partials/navbar.html (100%) rename {views => _views}/profilo.html (100%) rename {views => _views}/storia.html (100%) rename {views => _views}/utenti.html (100%) create mode 100644 cmd/phc-website-server/main.go create mode 100644 handler/context.go create mode 100644 handler/handler.go delete mode 100644 main.go create mode 100644 server/fiber.go delete mode 100644 util/generics.go diff --git a/.gitignore b/.gitignore index 50c6a60..79ef128 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ public/js/ # Executables phc-website-server +!phc-website-server/ diff --git a/README.md b/README.md index a452de6..867bcc6 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ $ fd -e js | entr make frontend - `MAIL` - Indirizzo di posta elettronica per contattare gli ammistratori del sito, + Indirizzo di posta elettronica per contattare gli amministratori del sito, che compare nel footer di ogni pagina. - `_URL` diff --git a/news/2021-12-22-notizia-1.md b/_content/news/2021-12-22-notizia-1.md similarity index 100% rename from news/2021-12-22-notizia-1.md rename to _content/news/2021-12-22-notizia-1.md diff --git a/news/2021-12-23-notizia-2.md b/_content/news/2021-12-23-notizia-2.md similarity index 100% rename from news/2021-12-23-notizia-2.md rename to _content/news/2021-12-23-notizia-2.md diff --git a/news/2021-12-24-notizia-3.md b/_content/news/2021-12-24-notizia-3.md similarity index 100% rename from news/2021-12-24-notizia-3.md rename to _content/news/2021-12-24-notizia-3.md diff --git a/_content/public/css/main.css b/_content/public/css/main.css new file mode 100644 index 0000000..20b63d0 --- /dev/null +++ b/_content/public/css/main.css @@ -0,0 +1 @@ +@import"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap";:root{--fg-300: #777;--fg-400: #666;--fg-500: #333;--fg-600: #222;--bg-000: #f0f0f0;--bg-100: #f0f0f0;--bg-500: #eaeaea;--bg-550: #ecedee;--bg-600: #e4e5e7;--bg-700: #d5d5d5;--bg-750: #c8c8c8;--bg-800: #c0c0c0;--bg-850: #b8b8b8;--accent-300: #5cc969;--accent-400: #4eaa59;--accent-500: #278542;--accent-600: #2e974c;--accent-700: #154d24;--accent-800: #002d0d;--ft-ss: "Inter", sans-serif;--ft-ss-wt-light: 300;--ft-ss-wt-normal: 400;--ft-ss-wt-medium: 500;--ft-ss-wt-bold: 700;--shadow-500: 0 0 16px 0 #00000018;--text-input-bg: var(--bg-000);--text-input-readonly-bg: var(--bg-600);--text-input-readonly-fg: var(--fg-300)}*{box-sizing:border-box}html,body{margin:0}html{height:100%;width:100%}body{background:var(--bg-500);color:var(--fg-500);font-family:var(--ft-ss);font-size:17px;font-weight:var(--ft-ss-wt-normal);width:100%;min-height:100%;position:relative}h1,h2,h3,h4{margin:0;margin-top:1rem;margin-bottom:.5rem;font-weight:var(--font-weight-light)}h1{font-size:2rem}h2{font-size:1.5rem}h3{font-size:1.35rem}h4{font-size:1.2rem;font-weight:var(--font-weight-bold)}p,ul,ol,li{margin:0;width:70ch;max-width:100%;line-height:1.8}p+p{padding-top:.5rem}ul,ol{padding:0 0 0 1.5rem}hr{width:50ch;height:1px;margin:0;border:none;background-color:var(--bg-darker-2)}pre{margin:.5rem 0;background:var(--bg-lighter);border:1px solid #cbcbcb;border-radius:2px;box-shadow:0 2px 4px 0 rgba(0,0,0,.2);font-size:90%;display:flex;overflow-x:auto}pre>code{display:block;margin:.25rem}p.center{text-align:center} diff --git a/public/home.js b/_content/public/home.js similarity index 100% rename from public/home.js rename to _content/public/home.js diff --git a/public/icons/icons8-electronics-100.png b/_content/public/icons/icons8-electronics-100.png similarity index 100% rename from public/icons/icons8-electronics-100.png rename to _content/public/icons/icons8-electronics-100.png diff --git a/public/images/logo-circuit-board.svg b/_content/public/images/logo-circuit-board.svg similarity index 100% rename from public/images/logo-circuit-board.svg rename to _content/public/images/logo-circuit-board.svg diff --git a/public/images/logo-circuit-board@100w.png b/_content/public/images/logo-circuit-board@100w.png similarity index 100% rename from public/images/logo-circuit-board@100w.png rename to _content/public/images/logo-circuit-board@100w.png diff --git a/public/images/logo-circuit-board@1x.png b/_content/public/images/logo-circuit-board@1x.png similarity index 100% rename from public/images/logo-circuit-board@1x.png rename to _content/public/images/logo-circuit-board@1x.png diff --git a/public/images/scritta-h.jpg b/_content/public/images/scritta-h.jpg similarity index 100% rename from public/images/scritta-h.jpg rename to _content/public/images/scritta-h.jpg diff --git a/public/images/scritta-t-mini.jpg b/_content/public/images/scritta-t-mini.jpg similarity index 100% rename from public/images/scritta-t-mini.jpg rename to _content/public/images/scritta-t-mini.jpg diff --git a/public/images/scritta-t.jpg b/_content/public/images/scritta-t.jpg similarity index 100% rename from public/images/scritta-t.jpg rename to _content/public/images/scritta-t.jpg diff --git a/public/images/scritta.jpg b/_content/public/images/scritta.jpg similarity index 100% rename from public/images/scritta.jpg rename to _content/public/images/scritta.jpg diff --git a/_content/public/js/profilo.min.js b/_content/public/js/profilo.min.js new file mode 100644 index 0000000..789357a --- /dev/null +++ b/_content/public/js/profilo.min.js @@ -0,0 +1 @@ +!function(a){"use strict";function o(a){return a&&"object"==typeof a&&"default"in a?a:{default:a}}var s=o(a);s.default.data("profilo",(()=>({init(){console.log("Profilo!")}}))),s.default.data("passwordForm",(()=>({password:"",passwordAgain:"",passwordSame:!0,onUpdate(){this.passwordSame=this.password===this.passwordAgain}})))}(Alpine); diff --git a/_content/public/js/utenti.min.js b/_content/public/js/utenti.min.js new file mode 100644 index 0000000..d5270cd --- /dev/null +++ b/_content/public/js/utenti.min.js @@ -0,0 +1 @@ +!function(e,s){"use strict";function t(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=t(e),r=t(s);const i={includeScore:!0,keys:["nome","cognome","tags",{name:"nomeCompleto",getFn:e=>`${e.nome} ${e.cognome}`}]},h={chronological:()=>0,name:(e,s)=>e.nomee.cognome({searchField:"",sortMode:"chronological",fetchedUsers:[],sortedUserBuffer:[],fuse:new r.default([],i),searchResultsBuffer:[],searchResults:[],async init(){const e=await fetch("/api/utenti");this.fetchedUsers=await e.json(),new IntersectionObserver((e=>{e.forEach((e=>{e.isIntersecting&&(console.log("Near the bottom of the page"),this.showMore())}))})).observe(this.$refs.spinner),this.updateSortMode(),this.updateSearch()},showMore(){const e=this.searchResults.length+15;this.searchResults=this.searchResultsBuffer.slice(0,e)},setResults(e){this.searchResultsBuffer=e.filter((e=>void 0===e.score||e.score<=.25)),this.searchResults=this.searchResultsBuffer.slice(0,15)},updateSortMode(){var e,s;this.sortedUserBuffer=(e=this.fetchedUsers,s=this.sortMode,[...e].sort(h[s])),this.fuse.setCollection(this.sortedUserBuffer),this.updateSearch()},updateSearch(){console.time("search"),0===this.searchField.trim().length?this.setResults(this.sortedUserBuffer.map((e=>({item:e})))):this.setResults(this.fuse.search(this.searchField)),console.timeEnd("search")}})))}(Alpine,Fuse); diff --git a/public/style.css b/_content/public/style.css similarity index 100% rename from public/style.css rename to _content/public/style.css diff --git a/public/theme-dark.css b/_content/public/theme-dark.css similarity index 100% rename from public/theme-dark.css rename to _content/public/theme-dark.css diff --git a/_screenshots/0_localhost_8000_home.png b/_docs/screenshots/0_localhost_8000_home.png similarity index 100% rename from _screenshots/0_localhost_8000_home.png rename to _docs/screenshots/0_localhost_8000_home.png diff --git a/_screenshots/0_localhost_8000_login.png b/_docs/screenshots/0_localhost_8000_login.png similarity index 100% rename from _screenshots/0_localhost_8000_login.png rename to _docs/screenshots/0_localhost_8000_login.png diff --git a/_screenshots/0_localhost_8000_utenti.png b/_docs/screenshots/0_localhost_8000_utenti.png similarity index 100% rename from _screenshots/0_localhost_8000_utenti.png rename to _docs/screenshots/0_localhost_8000_utenti.png diff --git a/_screenshots/2021-12-17_localhost-8000_home.png b/_docs/screenshots/2021-12-17_localhost-8000_home.png similarity index 100% rename from _screenshots/2021-12-17_localhost-8000_home.png rename to _docs/screenshots/2021-12-17_localhost-8000_home.png diff --git a/_screenshots/2021-12-19_home.png b/_docs/screenshots/2021-12-19_home.png similarity index 100% rename from _screenshots/2021-12-19_home.png rename to _docs/screenshots/2021-12-19_home.png diff --git a/_screenshots/2021-12-21_home.png b/_docs/screenshots/2021-12-21_home.png similarity index 100% rename from _screenshots/2021-12-21_home.png rename to _docs/screenshots/2021-12-21_home.png diff --git a/frontend/Makefile b/_frontend/Makefile similarity index 100% rename from frontend/Makefile rename to _frontend/Makefile diff --git a/frontend/package-lock.json b/_frontend/package-lock.json similarity index 100% rename from frontend/package-lock.json rename to _frontend/package-lock.json diff --git a/frontend/package.json b/_frontend/package.json similarity index 100% rename from frontend/package.json rename to _frontend/package.json diff --git a/frontend/rollup.config.js b/_frontend/rollup.config.js similarity index 100% rename from frontend/rollup.config.js rename to _frontend/rollup.config.js diff --git a/frontend/src/profilo.js b/_frontend/src/profilo.js similarity index 100% rename from frontend/src/profilo.js rename to _frontend/src/profilo.js diff --git a/frontend/src/styles/main.scss b/_frontend/src/styles/main.scss similarity index 100% rename from frontend/src/styles/main.scss rename to _frontend/src/styles/main.scss diff --git a/frontend/src/styles/typography.scss b/_frontend/src/styles/typography.scss similarity index 100% rename from frontend/src/styles/typography.scss rename to _frontend/src/styles/typography.scss diff --git a/frontend/src/utenti.js b/_frontend/src/utenti.js similarity index 100% rename from frontend/src/utenti.js rename to _frontend/src/utenti.js diff --git a/views/appunti.html b/_views/appunti.html similarity index 100% rename from views/appunti.html rename to _views/appunti.html diff --git a/views/base.html b/_views/base.html similarity index 100% rename from views/base.html rename to _views/base.html diff --git a/views/home.html b/_views/home.html similarity index 100% rename from views/home.html rename to _views/home.html diff --git a/views/link.html b/_views/link.html similarity index 100% rename from views/link.html rename to _views/link.html diff --git a/views/login.html b/_views/login.html similarity index 100% rename from views/login.html rename to _views/login.html diff --git a/views/news-base.html b/_views/news-base.html similarity index 100% rename from views/news-base.html rename to _views/news-base.html diff --git a/views/news.html b/_views/news.html similarity index 100% rename from views/news.html rename to _views/news.html diff --git a/views/partials/navbar.html b/_views/partials/navbar.html similarity index 100% rename from views/partials/navbar.html rename to _views/partials/navbar.html diff --git a/views/profilo.html b/_views/profilo.html similarity index 100% rename from views/profilo.html rename to _views/profilo.html diff --git a/views/storia.html b/_views/storia.html similarity index 100% rename from views/storia.html rename to _views/storia.html diff --git a/views/utenti.html b/_views/utenti.html similarity index 100% rename from views/utenti.html rename to _views/utenti.html diff --git a/cmd/phc-website-server/main.go b/cmd/phc-website-server/main.go new file mode 100644 index 0000000..e0ac999 --- /dev/null +++ b/cmd/phc-website-server/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "log" + + "git.phc.dm.unipi.it/phc/website" + "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/handler" + "git.phc.dm.unipi.it/phc/website/server" + "git.phc.dm.unipi.it/phc/website/templates" +) + +func main() { + config.Load() + + h := &handler.DefaultHandler{ + AuthService: auth.NewDefaultService(config.AuthServiceHost), + Renderer: templates.NewRenderer( + "./views/", + "./views/base.html", + "./views/partials/*.html", + ), + NewsArticlesRegistry: articles.NewRegistry("./news"), + ListaUtenti: &website.JsonFileListUtenti{ + Path: "./utenti-poisson-2022.local.json", + }, + Storia: &website.JsonFileStoria{ + Path: "./storia.json", + }, + } + + app := server.NewFiberServer(h) + + log.Printf("Starting server on host %q", config.Host) + err := app.Listen(config.Host) + if err != nil { + log.Fatal(err) + } +} diff --git a/config/config.go b/config/config.go index 462e6f8..d74ab95 100644 --- a/config/config.go +++ b/config/config.go @@ -54,8 +54,8 @@ func Load() { loadEnv(&AuthServiceHost, "AUTH_SERVICE_HOST", "http://localhost:3535") } -func Object() util.H { - return util.H{ +func Object() util.Map { + return util.Map{ "Mode": Mode, "Host": Host, diff --git a/handler/context.go b/handler/context.go new file mode 100644 index 0000000..bcb24ee --- /dev/null +++ b/handler/context.go @@ -0,0 +1,21 @@ +package handler + +type ContextKey[T any] string + +type Context map[string]any + +func GetContextValue[T any](ctx Context, key ContextKey[T]) T { + value, present := ctx[string(key)] + if !present { + var zero T + return zero + } + + typedValue, _ := value.(T) + + return typedValue +} + +func SetContextValue[T any](ctx Context, key ContextKey[T], value T) { + ctx[string(key)] = value +} diff --git a/handler/handler.go b/handler/handler.go new file mode 100644 index 0000000..2ff9253 --- /dev/null +++ b/handler/handler.go @@ -0,0 +1,147 @@ +package handler + +import ( + "fmt" + "html/template" + "io" + + "git.phc.dm.unipi.it/phc/website" + "git.phc.dm.unipi.it/phc/website/articles" + "git.phc.dm.unipi.it/phc/website/auth" + "git.phc.dm.unipi.it/phc/website/model" + "git.phc.dm.unipi.it/phc/website/templates" + "git.phc.dm.unipi.it/phc/website/util" +) + +type Service interface { + HandleStaticPage(w io.Writer, view string, ctx Context) error + HandleUtenti() ([]*model.User, error) + HandleStoriaPage(w io.Writer, ctx Context) error + HandleQueryAppunti(w io.Writer, query string, ctx Context) error + HandleNewsPage(w io.Writer, ctx Context) error + HandleLogin(username, password string) (*model.Session, error) + HandleUser(token string) *model.User + HandleRequiredUser(ctx Context) (*model.User, error) + HandleProfilePage(w io.Writer, ctx Context) error + HandleArticlePage(w io.Writer, articleID string, ctx Context) error +} + +// +// Typed context +// + +// UserKey is a typed type for *model.User used to extract a user form a [handler.Context] +const UserKey ContextKey[*model.User] = "user" + +func (ctx Context) getUser() *model.User { + return GetContextValue(ctx, UserKey) +} + +// 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 + Renderer *templates.TemplateRenderer + NewsArticlesRegistry *articles.Registry + ListaUtenti website.ListaUtentiService + Storia website.StoriaService +} + +func (h *DefaultHandler) HandleStaticPage(w io.Writer, view string, ctx Context) error { + return h.Renderer.Render(w, view, util.Map{ + "User": ctx.getUser(), + }) +} + +func (h *DefaultHandler) HandleUtenti() ([]*model.User, error) { + utenti, err := h.AuthService.GetUsers() + if err != nil { + return nil, err + } + + return utenti, nil +} + +func (h *DefaultHandler) HandleStoriaPage(w io.Writer, ctx Context) error { + storia, err := h.Storia.GetStoria() + if err != nil { + return err + } + + return h.Renderer.Render(w, "storia.html", util.Map{ + "User": ctx.getUser(), + "Storia": storia, + }) +} + +func (h *DefaultHandler) HandleQueryAppunti(w io.Writer, query string, ctx Context) error { + return h.Renderer.Render(w, "appunti.html", util.Map{ + "User": ctx.getUser(), + "Query": query, + }) +} + +func (h *DefaultHandler) HandleNewsPage(w io.Writer, ctx Context) error { + articles, err := h.NewsArticlesRegistry.GetArticles() + if err != nil { + return err + } + + return h.Renderer.Render(w, "news.html", util.Map{ + "User": ctx.getUser(), + "Articles": articles, + }) +} + +func (h *DefaultHandler) HandleLogin(username, password string) (*model.Session, error) { + session, err := h.AuthService.Login(username, password) + if err != nil { + return nil, err + } + + return session, nil +} + +func (h *DefaultHandler) HandleUser(token string) *model.User { + user, _ := auth.UserForSession(h.AuthService, token) + return user +} + +var ErrNoUser = fmt.Errorf(`user not logged in`) + +func (h *DefaultHandler) HandleRequiredUser(ctx Context) (*model.User, error) { + user := ctx.getUser() + if user == nil { + return nil, ErrNoUser + } + + return user, nil +} + +func (h *DefaultHandler) HandleProfilePage(w io.Writer, ctx Context) error { + user := ctx.getUser() + if user == nil { + return ErrNoUser + } + + return h.Renderer.Render(w, "profilo.html", util.Map{ + "User": user, + }) +} + +func (h *DefaultHandler) HandleArticlePage(w io.Writer, articleID string, ctx Context) error { + article, err := h.NewsArticlesRegistry.GetArticle(articleID) + if err != nil { + return err + } + + html, err := article.Render() + if err != nil { + return err + } + + return h.Renderer.Render(w, "news-base.html", util.Map{ + "User": ctx.getUser(), + "Article": article, + "ContentHTML": template.HTML(html), + }) +} diff --git a/main.go b/main.go deleted file mode 100644 index 87214f7..0000000 --- a/main.go +++ /dev/null @@ -1,208 +0,0 @@ -package main - -import ( - "fmt" - "html/template" - "log" - "time" - - "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/model" - "git.phc.dm.unipi.it/phc/website/templates" - "git.phc.dm.unipi.it/phc/website/util" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" - "github.com/gofiber/fiber/v2/middleware/recover" - "github.com/gofiber/redirect/v2" -) - -func UserMiddleware(as auth.Service) fiber.Handler { - return func(c *fiber.Ctx) error { - token := c.Cookies("session-token") - user, _ := auth.UserForSession(as, token) - c.Locals("user", user) - return c.Next() - } -} - -func main() { - config.Load() - - app := fiber.New() - - app.Use(logger.New()) - app.Use(recover.New()) - - // Remove trailing slash from URLs - app.Use(redirect.New(redirect.Config{ - Rules: map[string]string{ - "/*/": "/$1", - }, - })) - - // Serve content statically from "./public", mounted on the "/public/" route - app.Static("/public/", "./public") - - authService := auth.NewDefaultService(config.AuthServiceHost) - app.Use(UserMiddleware(authService)) - - // Templates & Renderer - renderer := templates.NewRenderer( - "./views/", - "./views/base.html", - "./views/partials/*.html", - ) - - newsArticlesRegistry := articles.NewRegistry("./news") - - // Routes - - actuallyStaticRoutes := map[string]string{ - "/": "home.html", - "/link": "link.html", - "/login": "login.html", - "/utenti": "utenti.html", - } - - for route, view := range actuallyStaticRoutes { - localView := view - app.Get(route, func(c *fiber.Ctx) error { - c.Type("html") - return renderer.Render(c, localView, util.H{ - "User": c.Locals("user"), - }) - }) - } - - app.Get("/api/utenti", func(c *fiber.Ctx) error { - utenti, err := authService.GetUsers() - if err != nil { - return err - } - - return c.JSON(utenti) - }) - - app.Get("/api/profilo", func(c *fiber.Ctx) error { - user := c.Locals("user") - if user == nil { - return fmt.Errorf(`user not logged in`) - } - - return c.JSON(user) - }) - - app.Get("/storia", func(c *fiber.Ctx) error { - storia, err := GetStoria() - if err != nil { - return err - } - - c.Type("html") - return renderer.Render(c, "storia.html", util.H{ - "User": c.Locals("user"), - "Storia": storia, - }) - }) - - app.Get("/appunti", func(c *fiber.Ctx) error { - searchQuery := c.Query("q", "") - - c.Type("html") - return renderer.Render(c, "appunti.html", util.H{ - "User": c.Locals("user"), - "Query": searchQuery, - }) - }) - - app.Get("/news", func(c *fiber.Ctx) error { - articles, err := newsArticlesRegistry.GetArticles() - if err != nil { - return err - } - - c.Type("html") - return renderer.Render(c, "news.html", util.H{ - "User": c.Locals("user"), - "Articles": articles, - }) - }) - - app.Post("/login", func(c *fiber.Ctx) error { - var loginForm struct { - Provider string `form:"provider"` - Username string `form:"username"` - Password string `form:"password"` - } - - if err := c.BodyParser(&loginForm); err != nil { - return err - } - - session, err := authService.Login(loginForm.Username, loginForm.Password) - if err != nil { - return err - } - - inThreeDays := time.Now().Add(3 * 24 * time.Hour) - c.Cookie(&fiber.Cookie{ - Name: "session-token", - Path: "/", - Value: session.Token, - Expires: inThreeDays, - }) - - return c.Redirect("/profilo") - }) - - app.Get("/profilo", func(c *fiber.Ctx) error { - user, ok := c.Locals("user").(*model.User) - if !ok || user == nil { - return fmt.Errorf(`user not logged in`) - } - - c.Type("html") - return renderer.Render(c, "profilo.html", util.H{ - "User": c.Locals("user"), - }) - }) - - app.Get("/logout", func(c *fiber.Ctx) error { - c.Cookie(&fiber.Cookie{ - Name: "session-token", - Path: "/", - Value: "", - Expires: time.Now(), - }) - return c.Redirect("/") - }) - - app.Get("/news/:article", func(c *fiber.Ctx) error { - articleID := c.Params("article") - - article, err := newsArticlesRegistry.GetArticle(articleID) - if err != nil { - return err - } - - html, err := article.Render() - if err != nil { - return err - } - - c.Type("html") - return renderer.Render(c, "news-base.html", util.H{ - "User": c.Locals("user"), - "Article": article, - "ContentHTML": template.HTML(html), - }) - }) - - log.Printf("Starting server on host %q", config.Host) - err := app.Listen(config.Host) - if err != nil { - log.Fatal(err) - } -} diff --git a/server/fiber.go b/server/fiber.go new file mode 100644 index 0000000..042966b --- /dev/null +++ b/server/fiber.go @@ -0,0 +1,172 @@ +package server + +import ( + "time" + + "git.phc.dm.unipi.it/phc/website/handler" + "git.phc.dm.unipi.it/phc/website/model" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/redirect/v2" +) + +func UserMiddleware(h handler.Service) fiber.Handler { + return func(c *fiber.Ctx) error { + token := c.Cookies("session-token") + user := h.HandleUser(token) + c.Locals("user", user) + return c.Next() + } +} + +func CreateContext(ctx *fiber.Ctx) handler.Context { + // the "_" here is required because Go cannot cast of type interface{} to *model.User, in other words of type *model.User and of type interface{} are different type. In this case the "_" returns a boolean that tells whether the cast succeeded or not, if it is false the user variable gets assigned its default zero value that is of type *model.User + user, _ := ctx.Locals("user").(*model.User) + + context := handler.Context{} + handler.SetContextValue(context, handler.UserKey, user) + + return context +} + +func NewFiberServer(h handler.Service) *fiber.App { + app := fiber.New() + routes(h, app) + return app +} + +func routes(h handler.Service, r fiber.Router) { + // + // Initial setup + // + + r.Use(logger.New()) + r.Use(recover.New()) + + // Remove trailing slash from URLs + r.Use(redirect.New(redirect.Config{ + Rules: map[string]string{ + "/*/": "/$1", + }, + })) + + // Serve content statically from "./public", mounted on the "/public/" route + r.Static("/public/", "./public") + + // Process all request and add user to the request context if there is a session cookie + r.Use(UserMiddleware(h)) + + // + // Pages + // + + r.Get("/", func(c *fiber.Ctx) error { + c.Type("html") + return h.HandleStaticPage(c, "home.html", CreateContext(c)) + }) + + r.Get("/link", func(c *fiber.Ctx) error { + c.Type("html") + return h.HandleStaticPage(c, "link.html", CreateContext(c)) + }) + + r.Get("/login", func(c *fiber.Ctx) error { + c.Type("html") + return h.HandleStaticPage(c, "login.html", CreateContext(c)) + }) + + r.Get("/utenti", func(c *fiber.Ctx) error { + c.Type("html") + return h.HandleStaticPage(c, "utenti.html", CreateContext(c)) + }) + + r.Get("/storia", func(c *fiber.Ctx) error { + c.Type("html") + return h.HandleStoriaPage(c, CreateContext(c)) + }) + + r.Get("/appunti", func(c *fiber.Ctx) error { + query := c.Query("q", "") + + c.Type("html") + return h.HandleQueryAppunti(c, query, CreateContext(c)) + }) + + r.Get("/news", func(c *fiber.Ctx) error { + c.Type("html") + return h.HandleNewsPage(c, CreateContext(c)) + }) + + r.Post("/login", func(c *fiber.Ctx) error { + var loginForm struct { + Provider string `form:"provider"` + Username string `form:"username"` + Password string `form:"password"` + } + + if err := c.BodyParser(&loginForm); err != nil { + return err + } + + session, err := h.HandleLogin(loginForm.Username, loginForm.Password) + if err != nil { + return err + } + + inThreeDays := time.Now().Add(3 * 24 * time.Hour) + c.Cookie(&fiber.Cookie{ + Name: "session-token", + Path: "/", + Value: session.Token, + Expires: inThreeDays, + }) + + return c.Redirect("/profilo") + }) + + r.Get("/profilo", func(c *fiber.Ctx) error { + c.Type("html") + return h.HandleProfilePage(c, CreateContext(c)) + }) + + r.Get("/logout", func(c *fiber.Ctx) error { + c.Cookie(&fiber.Cookie{ + Name: "session-token", + Path: "/", + Value: "", + Expires: time.Now(), + }) + + return c.Redirect("/") + }) + + r.Get("/news/:article", func(c *fiber.Ctx) error { + articleID := c.Params("article") + + c.Type("html") + return h.HandleArticlePage(c, articleID, CreateContext(c)) + }) + + routesApi(h, r.Group("/api")) +} + +func routesApi(h handler.Service, r fiber.Router) { + r.Get("/api/utenti", func(c *fiber.Ctx) error { + utenti, err := h.HandleUtenti() + if err != nil { + return err + } + + return c.JSON(utenti) + }) + + r.Get("/api/profilo", func(c *fiber.Ctx) error { + user, err := h.HandleRequiredUser(CreateContext(c)) + if err != nil { + return err + } + + return c.JSON(user) + }) +} diff --git a/storia.go b/storia.go index 55226fe..da1d7d4 100644 --- a/storia.go +++ b/storia.go @@ -1,9 +1,9 @@ -package main +package website import ( "encoding/json" - "io/ioutil" "math" + "os" "sort" "strconv" "strings" @@ -11,23 +11,8 @@ import ( "git.phc.dm.unipi.it/phc/website/util" ) -type byEventDateDescending []*GenericEvent - -func (m byEventDateDescending) Len() int { - return len(m) -} -func (m byEventDateDescending) Swap(i, j int) { - m[i], m[j] = m[j], m[i] -} -func (m byEventDateDescending) Less(i, j int) bool { - return m[i].Date > m[j].Date -} - -type Macchinista struct { - Uid string `json:"uid"` - FullName string `json:"fullName"` - EntryDate string `json:"entryDate"` - ExitDate string `json:"exitDate"` +type StoriaService interface { + GetStoria() ([]*GenericEvent, error) } type GenericEvent struct { @@ -47,33 +32,78 @@ type GenericEvent struct { Size int `json:"size"` } -type HistoryDB struct { +type Macchinista struct { + Uid string `json:"uid"` + FullName string `json:"fullName"` + EntryDate string `json:"entryDate"` + ExitDate string `json:"exitDate"` +} + +type storiaDB struct { Macchinisti []*Macchinista `json:"macchinisti"` GenericEvent []*GenericEvent `json:"eventi"` } -func GetRawHistory() (*HistoryDB, error) { - var rawHistory HistoryDB +type JsonFileStoria struct { + Path string +} + +type byEventDateDescending []*GenericEvent - historyDB, err := ioutil.ReadFile("./storia.json") +func (m byEventDateDescending) Len() int { + return len(m) +} +func (m byEventDateDescending) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} +func (m byEventDateDescending) Less(i, j int) bool { + return m[i].Date > m[j].Date +} + +func (db *JsonFileStoria) GetStoria() ([]*GenericEvent, error) { + history, err := db.GetRawStoria() if err != nil { return nil, err } - if err := json.Unmarshal(historyDB, &rawHistory); err != nil { - return nil, err + events := history.GenericEvent[:] + for _, m := range history.Macchinisti { + if m.ExitDate != "" { + events = append(events, &GenericEvent{ + Type: "exit-macchinista", + Uid: m.Uid, + FullName: m.FullName, + Date: m.ExitDate, + }) + } + if m.EntryDate != "" { + events = append(events, &GenericEvent{ + Type: "entry-macchinista", + Uid: m.Uid, + FullName: m.FullName, + Date: m.EntryDate, + }) + } } - return &rawHistory, nil + sort.Sort(byEventDateDescending(events)) + + return withSpacers(events), nil } -func getDateYear(date string) int { - year, err := strconv.Atoi(strings.Split(date, "/")[0]) +func (db *JsonFileStoria) GetRawStoria() (*storiaDB, error) { + var rawHistory storiaDB + + historyDB, err := os.ReadFile(db.Path) if err != nil { - panic(err) // Tanto nel caso si nota in fase di sviluppo visto che "storia.json" è statico + return nil, err } - return year + if err := json.Unmarshal(historyDB, &rawHistory); err != nil { + return nil, err + } + + return &rawHistory, nil } func withSpacers(events []*GenericEvent) []*GenericEvent { @@ -97,33 +127,11 @@ func withSpacers(events []*GenericEvent) []*GenericEvent { return newEvents } -func GetStoria() ([]*GenericEvent, error) { - history, err := GetRawHistory() +func getDateYear(date string) int { + year, err := strconv.Atoi(strings.Split(date, "/")[0]) if err != nil { - return nil, err - } - - events := history.GenericEvent[:] - for _, m := range history.Macchinisti { - if m.ExitDate != "" { - events = append(events, &GenericEvent{ - Type: "exit-macchinista", - Uid: m.Uid, - FullName: m.FullName, - Date: m.ExitDate, - }) - } - if m.EntryDate != "" { - events = append(events, &GenericEvent{ - Type: "entry-macchinista", - Uid: m.Uid, - FullName: m.FullName, - Date: m.EntryDate, - }) - } + panic(err) // Tanto nel caso si nota in fase di sviluppo visto che "storia.json" è statico } - sort.Sort(byEventDateDescending(events)) - - return withSpacers(events), nil + return year } diff --git a/templates/templates.go b/templates/templates.go index 751e557..b74a217 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -91,16 +91,16 @@ func (r *TemplateRenderer) Load(name string) *CachedTemplate { } // Render the template, also injects "Page" and "Config" values in the template -func (r *TemplateRenderer) Render(w io.Writer, name string, data util.H) error { +func (r *TemplateRenderer) Render(w io.Writer, name string, data util.Map) error { cachedTemplate := r.Load(name) if config.Mode == "development" { cachedTemplate.Reload() } - newData := util.H{} + newData := util.Map{} newData.Apply(data) - newData["Page"] = util.H{ + newData["Page"] = util.Map{ // Used to inject a page specific class on "Name": strings.TrimSuffix(path.Base(name), ".html"), } diff --git a/utenti.go b/utenti.go index 11692f7..e08c684 100644 --- a/utenti.go +++ b/utenti.go @@ -1,8 +1,8 @@ -package main +package website import ( "encoding/json" - "io/ioutil" + "os" ) type UserInfo struct { @@ -14,21 +14,18 @@ type UserInfo struct { IsMacchinista bool `json:"macchinista,omitempty"` } -func GetUtenti() ([]UserInfo, error) { - history, err := GetRawHistory() - if err != nil { - return nil, err - } +type ListaUtentiService interface { + GetUtenti() ([]UserInfo, error) +} - // Creo un set per velocizzare l'utilizzo dopo - macchinisti := map[string]bool{} - for _, m := range history.Macchinisti { - macchinisti[m.Uid] = true - } +type JsonFileListUtenti struct { + Path string +} +func (j *JsonFileListUtenti) GetUtenti() ([]UserInfo, error) { var users []UserInfo - usersJsonData, err := ioutil.ReadFile("./utenti-poisson-2022.local.json") + usersJsonData, err := os.ReadFile(j.Path) if err != nil { return nil, err } diff --git a/util/generics.go b/util/generics.go deleted file mode 100644 index c7d8682..0000000 --- a/util/generics.go +++ /dev/null @@ -1 +0,0 @@ -package util diff --git a/util/objects.go b/util/objects.go index becf244..67c364a 100644 --- a/util/objects.go +++ b/util/objects.go @@ -1,10 +1,10 @@ package util -// H is a shortcut for creating a json-like object -type H map[string]interface{} +// Map is a shortcut for creating a json-like object +type Map map[string]interface{} // Apply is like Object.apply from JS and merges the given objects on top of target -func (target H) Apply(sources ...H) H { +func (target Map) Apply(sources ...Map) Map { for _, source := range sources { for k, v := range source { target[k] = v diff --git a/util/rand.go b/util/rand.go index aa82370..f2a8fbe 100644 --- a/util/rand.go +++ b/util/rand.go @@ -6,6 +6,7 @@ import ( "log" ) +// GenerateRandomString generates a random base64 string from a random array of "n" bytes (to prevent padding let n be a multiple of 3) func GenerateRandomString(n int) string { b := make([]byte, n)