Big refactor
@@ -17,3 +17,4 @@ public/js/
|
||||
|
||||
# Executables
|
||||
phc-website-server
|
||||
!phc-website-server/
|
||||
|
||||
@@ -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.
|
||||
|
||||
- `<SERVIZIO>_URL`
|
||||
|
||||
@@ -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}
|
||||
|
Before Width: | Height: | Size: 790 B After Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 765 B After Width: | Height: | Size: 765 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 4.6 MiB After Width: | Height: | Size: 4.6 MiB |
@@ -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);
|
||||
@@ -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.nome<s.nome?-1:1,surname:(e,s)=>e.cognome<s.cognome?-1:1};o.default.data("utenti",(()=>({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);
|
||||
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 620 KiB After Width: | Height: | Size: 620 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 <nil> of type interface{} to *model.User, in other words <nil> of type *model.User and <nil> 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 <nil> 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)
|
||||
})
|
||||
}
|
||||
@@ -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,58 +32,36 @@ 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
|
||||
|
||||
historyDB, err := ioutil.ReadFile("./storia.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(historyDB, &rawHistory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rawHistory, nil
|
||||
type JsonFileStoria struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func getDateYear(date string) int {
|
||||
year, err := strconv.Atoi(strings.Split(date, "/")[0])
|
||||
if err != nil {
|
||||
panic(err) // Tanto nel caso si nota in fase di sviluppo visto che "storia.json" è statico
|
||||
}
|
||||
type byEventDateDescending []*GenericEvent
|
||||
|
||||
return year
|
||||
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 withSpacers(events []*GenericEvent) []*GenericEvent {
|
||||
// crea un nuovo slice con capacità già almeno "len(events)"
|
||||
newEvents := make([]*GenericEvent, 0, len(events))
|
||||
|
||||
for i := 0; i < len(events)-1; i++ {
|
||||
newEvents = append(newEvents, events[i])
|
||||
|
||||
delta := util.Abs(getDateYear(events[i+1].Date) - getDateYear(events[i].Date))
|
||||
if delta > 1 {
|
||||
logDelta := int(math.Log(float64(delta)))
|
||||
newEvents = append(newEvents, &GenericEvent{
|
||||
Type: "spacer",
|
||||
Size: logDelta,
|
||||
})
|
||||
}
|
||||
}
|
||||
newEvents = append(newEvents, events[len(events)-1])
|
||||
|
||||
return newEvents
|
||||
}
|
||||
|
||||
func GetStoria() ([]*GenericEvent, error) {
|
||||
history, err := GetRawHistory()
|
||||
func (db *JsonFileStoria) GetStoria() ([]*GenericEvent, error) {
|
||||
history, err := db.GetRawStoria()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -127,3 +90,48 @@ func GetStoria() ([]*GenericEvent, error) {
|
||||
|
||||
return withSpacers(events), nil
|
||||
}
|
||||
|
||||
func (db *JsonFileStoria) GetRawStoria() (*storiaDB, error) {
|
||||
var rawHistory storiaDB
|
||||
|
||||
historyDB, err := os.ReadFile(db.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(historyDB, &rawHistory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rawHistory, nil
|
||||
}
|
||||
|
||||
func withSpacers(events []*GenericEvent) []*GenericEvent {
|
||||
// crea un nuovo slice con capacità già almeno "len(events)"
|
||||
newEvents := make([]*GenericEvent, 0, len(events))
|
||||
|
||||
for i := 0; i < len(events)-1; i++ {
|
||||
newEvents = append(newEvents, events[i])
|
||||
|
||||
delta := util.Abs(getDateYear(events[i+1].Date) - getDateYear(events[i].Date))
|
||||
if delta > 1 {
|
||||
logDelta := int(math.Log(float64(delta)))
|
||||
newEvents = append(newEvents, &GenericEvent{
|
||||
Type: "spacer",
|
||||
Size: logDelta,
|
||||
})
|
||||
}
|
||||
}
|
||||
newEvents = append(newEvents, events[len(events)-1])
|
||||
|
||||
return newEvents
|
||||
}
|
||||
|
||||
func getDateYear(date string) int {
|
||||
year, err := strconv.Atoi(strings.Split(date, "/")[0])
|
||||
if err != nil {
|
||||
panic(err) // Tanto nel caso si nota in fase di sviluppo visto che "storia.json" è statico
|
||||
}
|
||||
|
||||
return year
|
||||
}
|
||||
|
||||
@@ -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 <body>
|
||||
"Name": strings.TrimSuffix(path.Base(name), ".html"),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package util
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||