Initial commit
commit
88d26d1e5c
@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
# Posti DM
|
||||||
|
|
||||||
|
Prototipo di applicazione web per prenotare posti in dipartimento.
|
||||||
|
|
||||||
|
- FrontEnd: Vite + VanillaJS
|
||||||
|
|
||||||
|
- BackEnd: Golang + go-chi + sqlite3 (?)
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Posti DM</title>
|
||||||
|
|
||||||
|
<script type="module" src="./src/index.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="nav-cell left"></div>
|
||||||
|
<div class="nav-cell center">
|
||||||
|
<div class="nav-item">Prenota Posti DM</div>
|
||||||
|
</div>
|
||||||
|
<div class="nav-cell right">
|
||||||
|
<div class="nav-item">Login</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="room-name">AulaStud</div>
|
||||||
|
<div class="room-main">
|
||||||
|
<div class="room-diagram">
|
||||||
|
<img src="./diargamma-posti.jpg" alt="diargamma-posti" />
|
||||||
|
</div>
|
||||||
|
<div class="room-bookings">
|
||||||
|
<div class="seat-list">
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 1</div>
|
||||||
|
<button disabled>Prenotato</button>
|
||||||
|
</div>
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 2</div>
|
||||||
|
<button disabled>Prenotato</button>
|
||||||
|
</div>
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 3</div>
|
||||||
|
<button>Prenota</button>
|
||||||
|
</div>
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 4</div>
|
||||||
|
<button>Prenota</button>
|
||||||
|
</div>
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 5</div>
|
||||||
|
<button disabled>Prenotato</button>
|
||||||
|
</div>
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 6</div>
|
||||||
|
<button disabled>Prenotato</button>
|
||||||
|
</div>
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 7</div>
|
||||||
|
<button>Prenota</button>
|
||||||
|
</div>
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 8</div>
|
||||||
|
<button>Prenota</button>
|
||||||
|
</div>
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 9</div>
|
||||||
|
<button disabled>Prenotato</button>
|
||||||
|
</div>
|
||||||
|
<div class="seat">
|
||||||
|
<div class="name">Posto 10</div>
|
||||||
|
<button>Prenota</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "client",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"sass": "^1.49.9",
|
||||||
|
"vite": "^2.8.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1 @@
|
|||||||
|
import './style.scss'
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
/* Box sizing rules */
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove default margin */
|
||||||
|
body,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
p,
|
||||||
|
figure,
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
|
||||||
|
ul[role='list'],
|
||||||
|
ol[role='list'] {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set core root defaults */
|
||||||
|
html:focus-within {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set core body defaults */
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
text-rendering: optimizeSpeed;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A elements that don't have a class get default styles */
|
||||||
|
a:not([class]) {
|
||||||
|
text-decoration-skip-ink: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make images easier to work with */
|
||||||
|
img,
|
||||||
|
picture {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inherit fonts for inputs and buttons */
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove all animations, transitions and smooth scroll for people that prefer not to see them */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html:focus-within {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
@use './reset.scss';
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f0f0f0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
height: 3rem;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
height: 3rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
padding-left: 1rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
padding-right: 1rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 2rem 1rem 2rem;
|
||||||
|
|
||||||
|
.room-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-diagram {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-bookings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
min-width: 15rem;
|
||||||
|
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
.seat-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.seat {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SessionCookieName = "session" // TODO: Make configurable
|
||||||
|
|
||||||
|
type AuthMiddlewareConfig struct {
|
||||||
|
IsLogged bool
|
||||||
|
WithPermissions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Authenticator interface {
|
||||||
|
// Login adds a session cookie to the user
|
||||||
|
Login(w http.ResponseWriter, userID string)
|
||||||
|
// Logout clears the user session cookies (by setting the session cookie timeout to 0)
|
||||||
|
Logout(w http.ResponseWriter)
|
||||||
|
|
||||||
|
// Middleware is a configurable middleware to authenticate http routes based on logged status and permissions
|
||||||
|
Middleware(*AuthMiddlewareConfig) func(http.Handler) http.Handler
|
||||||
|
// LoggedMiddleware accepts all logged users without checking for specific permissions
|
||||||
|
LoggedMiddleware() func(http.Handler) http.Handler
|
||||||
|
|
||||||
|
// UserFromSession returns the userID for this cookie session token
|
||||||
|
UserFromSession(r http.Request) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthService struct {
|
||||||
|
// CheckUserPassword
|
||||||
|
CheckUserPassword func(userID string, password string) bool
|
||||||
|
|
||||||
|
// GetUserPermissions gets the list of permissions for this user
|
||||||
|
UserPermissions func(userID string) []string
|
||||||
|
|
||||||
|
// SessionTokenFromUser returns a session token that represents this user
|
||||||
|
SessionTokenFromUser func(userID string) string
|
||||||
|
// UserFromSessionToken returns the corresponing user for this session token
|
||||||
|
UserFromSessionToken func(session string) (string, bool)
|
||||||
|
|
||||||
|
// AuthenticationFailed handles failed authentications
|
||||||
|
AuthenticationFailed http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *AuthService) Login(w http.ResponseWriter, userID string) {
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: SessionCookieName,
|
||||||
|
Value: auth.SessionTokenFromUser(userID),
|
||||||
|
Expires: time.Now().Add(10 * time.Minute), // TODO: Make configurable
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *AuthService) Logout(w http.ResponseWriter) {
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: SessionCookieName,
|
||||||
|
Value: "",
|
||||||
|
Expires: time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cookie, err := r.Cookie(SessionCookieName)
|
||||||
|
if err == http.ErrNoCookie && !config.IsLogged {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
auth.AuthenticationFailed.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, exists := auth.UserFromSessionToken(cookie.Value)
|
||||||
|
if !exists {
|
||||||
|
auth.Logout(w)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.IsLogged {
|
||||||
|
userPerms := map[string]bool{}
|
||||||
|
for _, perm := range auth.UserPermissions(userID) {
|
||||||
|
userPerms[perm] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the user has all the permissions to access the route
|
||||||
|
hasAll := true
|
||||||
|
for _, perm := range config.WithPermissions {
|
||||||
|
if _, present := userPerms[perm]; !present {
|
||||||
|
hasAll = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasAll {
|
||||||
|
auth.AuthenticationFailed.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler {
|
||||||
|
return auth.Middleware(&AuthMiddlewareConfig{
|
||||||
|
IsLogged: true,
|
||||||
|
WithPermissions: []string{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *AuthService) UserFromSession(r http.Request) (string, error) {
|
||||||
|
cookie, err := r.Cookie(SessionCookieName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, exists := auth.UserFromSessionToken(cookie.Value)
|
||||||
|
if !exists {
|
||||||
|
return "", fmt.Errorf(`no user for this session token`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userID, nil
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
// Entities
|
||||||
|
|
||||||
|
type Utente struct {
|
||||||
|
ID string
|
||||||
|
Username string
|
||||||
|
Permissions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stanza struct {
|
||||||
|
ID string
|
||||||
|
Posti []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
|
||||||
|
type AzioneBase struct {
|
||||||
|
Tipo string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzioneStanza struct {
|
||||||
|
AzioneBase
|
||||||
|
UserID string
|
||||||
|
StanzaID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzioneOccupaPosto struct {
|
||||||
|
AzioneStanza
|
||||||
|
PostoID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzioneLiberaPosto struct {
|
||||||
|
AzioneStanza
|
||||||
|
PostoID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database Interfaces
|
||||||
|
|
||||||
|
type Store interface {
|
||||||
|
// Entities
|
||||||
|
|
||||||
|
GetUtente(userID string) *Utente
|
||||||
|
GetStanza(stanzaID string) *Stanza
|
||||||
|
|
||||||
|
// Available Seats
|
||||||
|
|
||||||
|
GetPostiOccupati(stanzaID string) []string
|
||||||
|
GetPostiLiberi(stanzaID string) []string
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
module git.phc.dm.unipi.it/aziis98/posti-dm/server
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require github.com/go-chi/chi/v5 v5.0.7
|
||||||
|
|
||||||
|
require github.com/mattn/go-sqlite3 v1.14.12 // indirect
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
var HOST = ":3000"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
|
||||||
|
r.Use(middleware.RequestID)
|
||||||
|
r.Use(middleware.RealIP)
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
r.Use(middleware.Recoverer)
|
||||||
|
|
||||||
|
log.Printf(`Starting server on %s...`, HOST)
|
||||||
|
err := http.ListenAndServe(HOST, r)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue