AulaStud
-
-
+

+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/client/src/style.scss b/client/src/style.scss
index 06b2a0e..c5abf61 100644
--- a/client/src/style.scss
+++ b/client/src/style.scss
@@ -55,6 +55,11 @@ header {
}
}
+.panel {
+ border: 1px solid #ddd;
+ background: #fff;
+}
+
main {
display: flex;
flex-direction: column;
@@ -74,47 +79,168 @@ main {
}
.room-main {
- display: flex;
- flex-direction: row;
- gap: 1rem;
+ display: grid;
- @media screen and (max-width: 1000px) {
- flex-direction: column;
- }
+ grid-template-columns: auto 1fr;
+ grid-template-rows: auto auto;
+ grid-template-areas:
+ 'diagram bookings'
+ 'timeline timeline';
+
+ max-width: 800px;
.room-diagram {
- display: flex;
- flex-direction: column;
- border: 1px solid #ddd;
+ grid-area: diagram;
+ }
+ .room-bookings {
+ grid-area: bookings;
+ }
+ .room-timeline {
+ grid-area: timeline;
+ }
- background: #fff;
+ gap: 1rem;
+ .room-diagram {
padding: 1rem;
+
+ // --posto-libero-bg: #ddf;
+ // --posto-libero-bg-1: #88f;
+ // --posto-libero-bg-2: #66d;
+ --posto-libero-bg: #8d8;
+ --posto-occupato-bg: #d88;
+
+ display: grid;
+ grid-template-columns: repeat(15, 1fr);
+ grid-template-rows: repeat(16, 1fr);
+
+ gap: 0.25rem;
+
+ aspect-ratio: 15 / 16;
+
+ width: calc(15 * 2rem);
+ max-width: 500px;
+
+ .posto {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+
+ &.libero {
+ background: var(--posto-libero-bg);
+ }
+ &.occupato {
+ background: var(--posto-occupato-bg);
+ }
+
+ border: 2px solid #0004;
+ cursor: pointer;
+
+ &:hover {
+ border-color: #0006;
+ border-width: 4px;
+ }
+
+ line-height: 1;
+
+ &::before {
+ font-size: 12px;
+ content: 'Posto';
+ }
+ &::after {
+ font-size: 12px;
+ content: attr(data-index);
+ }
+ }
}
.room-bookings {
display: flex;
flex-direction: column;
- border: 1px solid #ddd;
-
- background: #fff;
min-width: 15rem;
+ width: 100%;
- padding: 0.5rem;
+ padding: 0.75rem 0;
.seat-list {
display: flex;
flex-direction: column;
- gap: 0.5rem;
-
.seat {
+ padding: 0.25rem 1rem;
+
display: grid;
grid-template-columns: 1fr auto;
+
+ height: 2.5rem;
+
+ &:hover {
+ background: #0001;
+ }
+
+ .name {
+ display: flex;
+ align-items: center;
+ }
+
+ &.selected {
+ background: #0002;
+
+ .name {
+ font-style: italic;
+ }
+ }
}
}
}
+
+ .room-timeline {
+ padding: 1rem;
+
+ display: flex;
+ flex-direction: row;
+ gap: 0.5rem;
+
+ align-items: center;
+
+ overflow-x: auto;
+
+ button {
+ min-width: 8rem;
+ height: 3rem;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: 1000px) {
+ header {
+ height: unset;
+
+ display: flex;
+ flex-direction: column;
+
+ .nav-cell {
+ position: relative;
+ padding: 0 1rem;
+ }
+ }
+
+ main {
+ .room-main {
+ display: flex;
+ flex-direction: column;
+
+ .room-diagram {
+ width: 200px;
+ }
+
+ .room-timeline {
+ flex-direction: column;
+ }
+ }
}
}
diff --git a/client/vite.config.js b/client/vite.config.js
new file mode 100644
index 0000000..dae6c68
--- /dev/null
+++ b/client/vite.config.js
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ server: {
+ proxy: {
+ '/api': {
+ target: 'http://localhost:4000/api',
+ changeOrigin: true,
+ rewrite: path => path.replace(/^\/api/, ''),
+ },
+ },
+ },
+})
diff --git a/server/auth/auth.go b/server/auth/auth.go
index a013522..2b3a46f 100644
--- a/server/auth/auth.go
+++ b/server/auth/auth.go
@@ -1,21 +1,29 @@
package auth
import (
- "fmt"
+ "errors"
"net/http"
"time"
+
+ "git.phc.dm.unipi.it/aziis98/posti-dm/server/httputil"
)
+var (
+ ErrNoUserForSession = errors.New(`no user for session token`)
+)
var SessionCookieName = "session" // TODO: Make configurable
type AuthMiddlewareConfig struct {
- IsLogged bool
+ // RequireLogged rejects not logged users if true
+ RequireLogged bool
+
+ // WithPermissions is the list of permissions the user should have to pass the middleware
WithPermissions []string
}
type Authenticator interface {
- // Login adds a session cookie to the user
- Login(w http.ResponseWriter, userID string)
+ // Login checks user credentials and adds a session cookie to the user if successfull
+ Login(w http.ResponseWriter, r *http.Request, userID, password string)
// Logout clears the user session cookies (by setting the session cookie timeout to 0)
Logout(w http.ResponseWriter)
@@ -25,71 +33,106 @@ type Authenticator interface {
LoggedMiddleware() func(http.Handler) http.Handler
// UserFromSession returns the userID for this cookie session token
- UserFromSession(r http.Request) (string, error)
+ UserFromSession(r *http.Request) (string, error)
}
type AuthService struct {
// CheckUserPassword
- CheckUserPassword func(userID string, password string) bool
+ CheckUserPassword func(userID string, password string) error
// GetUserPermissions gets the list of permissions for this user
- UserPermissions func(userID string) []string
+ UserPermissions func(userID string) ([]string, error)
// 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)
+ SessionTokenFromUser func(userID string) (string, error)
+ // UserFromSessionToken returns the corresponing user for this session token or "auth.ErrNoUserForSession"
+ UserFromSessionToken func(session string) (string, error)
// AuthenticationFailed handles failed authentications
AuthenticationFailed http.Handler
+
+ // OtherError handles other errors
+ OtherError func(error) http.Handler
}
-func (auth *AuthService) Login(w http.ResponseWriter, userID string) {
+func (auth *AuthService) Login(w http.ResponseWriter, r *http.Request, userID, password string) {
+ if err := auth.CheckUserPassword(userID, password); err != nil {
+ auth.AuthenticationFailed.ServeHTTP(w, r)
+ return
+ }
+
+ token, err := auth.SessionTokenFromUser(userID)
+ if err != nil {
+ auth.OtherError(err).ServeHTTP(w, r)
+ return
+ }
+
http.SetCookie(w, &http.Cookie{
Name: SessionCookieName,
- Value: auth.SessionTokenFromUser(userID),
- Expires: time.Now().Add(10 * time.Minute), // TODO: Make configurable
+ Path: "/", // TODO: Make configurable
+ Value: token,
+ Expires: time.Now().Add(7 * 24 * time.Hour), // TODO: Make configurable
})
+
+ httputil.WriteJSON(w, "ok")
}
func (auth *AuthService) Logout(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: SessionCookieName,
+ Path: "/",
Value: "",
Expires: time.Now(),
})
+
+ httputil.WriteJSON(w, "ok")
}
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)
+ if err == http.ErrNoCookie {
+ if !config.RequireLogged { // Login not required
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ auth.AuthenticationFailed.ServeHTTP(w, r)
return
}
if err != nil {
- auth.AuthenticationFailed.ServeHTTP(w, r)
+ auth.OtherError(err).ServeHTTP(w, r)
return
}
- userID, exists := auth.UserFromSessionToken(cookie.Value)
- if !exists {
+ userID, err := auth.UserFromSessionToken(cookie.Value)
+ if err == ErrNoUserForSession {
auth.Logout(w)
- w.WriteHeader(http.StatusBadRequest)
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+ if err != nil {
+ auth.OtherError(err).ServeHTTP(w, r)
return
}
- if config.IsLogged {
- userPerms := map[string]bool{}
- for _, perm := range auth.UserPermissions(userID) {
- userPerms[perm] = true
+ if config.RequireLogged {
+ userPerms, err := auth.UserPermissions(userID)
+ if err != nil {
+ auth.OtherError(err).ServeHTTP(w, r)
+ return
+ }
+
+ userPermsMap := map[string]bool{}
+ for _, perm := range userPerms {
+ userPermsMap[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 {
+ if _, present := userPermsMap[perm]; !present {
hasAll = false
break
}
@@ -101,6 +144,14 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
}
}
+ // Refresh session cookie expiration
+ http.SetCookie(w, &http.Cookie{
+ Name: SessionCookieName,
+ Path: "/",
+ Value: cookie.Value,
+ Expires: time.Now().Add(7 * 24 * time.Hour),
+ })
+
next.ServeHTTP(w, r)
})
}
@@ -108,20 +159,20 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler {
return auth.Middleware(&AuthMiddlewareConfig{
- IsLogged: true,
+ RequireLogged: true,
WithPermissions: []string{},
})
}
-func (auth *AuthService) UserFromSession(r http.Request) (string, error) {
+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`)
+ userID, err := auth.UserFromSessionToken(cookie.Value)
+ if err == ErrNoUserForSession {
+ return "", err
}
return userID, nil
diff --git a/server/db/database.go b/server/db/database.go
index 2cde51e..92a5c06 100644
--- a/server/db/database.go
+++ b/server/db/database.go
@@ -1,50 +1,180 @@
package db
+import "fmt"
+
// Entities
-type Utente struct {
+type User struct {
ID string
Username string
Permissions []string
}
-type Stanza struct {
- ID string
- Posti []string
+type Room struct {
+ ID string
+ SeatIDs []string
+
+ // gridRows, gridCols int
}
-// Actions
+type Seat struct {
+ ID string
+
+ // OccupiedBy is an empty list or a singleton of a userID
+ OccupiedBy []string
-type AzioneBase struct {
- Tipo string
+ // x, y, w, h int
}
-type AzioneStanza struct {
- AzioneBase
- UserID string
- StanzaID string
+// Database Interfaces
+
+type Store interface {
+ GetUser(userID string) (*User, error)
+ GetRoom(roomID string) (*Room, error)
+ GetSeat(seatID string) (*Seat, error)
+
+ GetOccupiedSeats(roomID string) ([]string, error)
+ GetFreeSeats(roomID string) ([]string, error)
+ GetUserSeat(userID string) ([]string, error)
+
+ OccupySeat(userID, roomID, seatID string) error
+ FreeSeat(userID, roomID, seatID string) error
}
-type AzioneOccupaPosto struct {
- AzioneStanza
- PostoID string
+// TODO: Create an SQLite implementation
+
+type memDB struct {
+ users map[string]*User
+ rooms map[string]*Room
+ seats map[string]*Seat
}
-type AzioneLiberaPosto struct {
- AzioneStanza
- PostoID string
+func NewInMemoryStore() Store {
+ db := &memDB{
+ make(map[string]*User),
+ make(map[string]*Room),
+ make(map[string]*Seat),
+ }
+
+ db.rooms["aula-stud"] = &Room{
+ ID: "aula-stud",
+ SeatIDs: []string{
+ "aula-stud/posto-1",
+ "aula-stud/posto-2",
+ "aula-stud/posto-3",
+ "aula-stud/posto-4",
+ },
+ }
+
+ db.seats["aula-stud/posto-1"] = &Seat{
+ ID: "aula-stud/posto-1",
+ OccupiedBy: []string{},
+ }
+ db.seats["aula-stud/posto-2"] = &Seat{
+ ID: "aula-stud/posto-2",
+ OccupiedBy: []string{},
+ }
+ db.seats["aula-stud/posto-3"] = &Seat{
+ ID: "aula-stud/posto-3",
+ OccupiedBy: []string{},
+ }
+ db.seats["aula-stud/posto-4"] = &Seat{
+ ID: "aula-stud/posto-4",
+ OccupiedBy: []string{},
+ }
+
+ db.users["aziis98"] = &User{
+ ID: "aziis98",
+ Username: "aziis98",
+ Permissions: []string{"admin"},
+ }
+
+ db.users["bachoseven"] = &User{
+ ID: "bachoseven",
+ Username: "bachoseven",
+ Permissions: []string{"admin"},
+ }
+
+ return db
}
-// Database Interfaces
+func (db *memDB) GetUser(userID string) (*User, error) {
+ user, present := db.users[userID]
+ if !present {
+ return nil, fmt.Errorf(`no such user "%s"`, userID)
+ }
-type Store interface {
- // Entities
+ return user, nil
+}
- GetUtente(userID string) *Utente
- GetStanza(stanzaID string) *Stanza
+func (db *memDB) GetRoom(roomID string) (*Room, error) {
+ room, present := db.rooms[roomID]
+ if !present {
+ return nil, fmt.Errorf(`no such room "%s"`, roomID)
+ }
- // Available Seats
+ return room, nil
+}
+
+func (db *memDB) GetSeat(seatID string) (*Seat, error) {
+ seat, present := db.seats[seatID]
+ if !present {
+ return nil, fmt.Errorf(`no such seat "%s"`, seatID)
+ }
+
+ return seat, nil
+}
+
+func (db *memDB) GetOccupiedSeats(roomID string) ([]string, error) {
+ room, err := db.GetRoom(roomID)
+ if err != nil {
+ return nil, err
+ }
+
+ seats := []string{}
+ for _, seatID := range room.SeatIDs {
+ seat := db.seats[seatID]
+ if len(seat.OccupiedBy) == 1 {
+ seats = append(seats, seatID)
+ }
+ }
+
+ return seats, nil
+}
+
+func (db *memDB) GetFreeSeats(roomID string) ([]string, error) {
+ room, err := db.GetRoom(roomID)
+ if err != nil {
+ return nil, err
+ }
+
+ seats := []string{}
+ for _, seatID := range room.SeatIDs {
+ seat := db.seats[seatID]
+ if len(seat.OccupiedBy) == 0 {
+ seats = append(seats, seatID)
+ }
+ }
+
+ return seats, nil
+}
+
+func (db *memDB) GetUserSeat(userID string) ([]string, error) {
+ for _, seat := range db.seats {
+ if len(seat.OccupiedBy) > 0 && seat.OccupiedBy[0] == userID {
+ return []string{userID}, nil
+ }
+ }
+
+ return []string{}, nil
+}
+
+func (db *memDB) OccupySeat(userID string, roomID string, seatID string) error {
+ db.seats[seatID].OccupiedBy = []string{userID}
+ return nil
+}
- GetPostiOccupati(stanzaID string) []string
- GetPostiLiberi(stanzaID string) []string
+func (db *memDB) FreeSeat(userID string, roomID string, seatID string) error {
+ db.seats[seatID].OccupiedBy = []string{}
+ return nil
}
diff --git a/server/go.mod b/server/go.mod
index 2c5f4cb..cbd6daf 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -4,4 +4,7 @@ go 1.17
require github.com/go-chi/chi/v5 v5.0.7
-require github.com/mattn/go-sqlite3 v1.14.12 // indirect
+require (
+ github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae // indirect
+ github.com/mattn/go-sqlite3 v1.14.12 // indirect
+)
diff --git a/server/go.sum b/server/go.sum
index f65788f..d1f516e 100644
--- a/server/go.sum
+++ b/server/go.sum
@@ -1,3 +1,5 @@
+github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=
+github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
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=
diff --git a/server/httputil/response.go b/server/httputil/response.go
new file mode 100644
index 0000000..70376c1
--- /dev/null
+++ b/server/httputil/response.go
@@ -0,0 +1,17 @@
+package httputil
+
+import (
+ "encoding/json"
+ "net/http"
+)
+
+type H map[string]interface{}
+
+func WriteJSON(w http.ResponseWriter, data interface{}) error {
+ w.Header().Set("Content-Type", "application/json")
+ return json.NewEncoder(w).Encode(data)
+}
+
+func ReadJSON(r *http.Request, data interface{}) error {
+ return json.NewDecoder(r.Body).Decode(data)
+}
diff --git a/server/httputil/serversentevents.go b/server/httputil/serversentevents.go
index 537a727..5e50f1b 100644
--- a/server/httputil/serversentevents.go
+++ b/server/httputil/serversentevents.go
@@ -2,37 +2,101 @@ package httputil
import (
"fmt"
+ "log"
"net/http"
)
-func HandleSSE(handler func(broadcast, single chan<- string)) http.Handler {
- broadcast := make(chan string)
-
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "text/event-stream")
- w.Header().Set("Cache-Control", "no-cache")
- w.Header().Set("Connection", "keep-alive")
- w.Header().Set("Access-Control-Allow-Origin", "*")
-
- client := make(chan string)
- defer func() {
- close(client)
- }()
-
- go handler(broadcast, client)
-
- flusher, _ := w.(http.Flusher)
- for {
- select {
- case message := <-broadcast:
- fmt.Fprintf(w, "%s\n", message)
- flusher.Flush()
- case message := <-client:
- fmt.Fprintf(w, "%s\n", message)
- flusher.Flush()
- case <-r.Context().Done():
+type SSEHandler struct {
+ clients map[chan string]bool
+
+ Connected func(chan string)
+ Disconnected func(chan string)
+}
+
+func (sse *SSEHandler) init() {
+ if sse.clients == nil {
+ sse.clients = make(map[chan string]bool)
+ }
+}
+
+func (sse *SSEHandler) Broadcast(message string) {
+ sse.init()
+
+ for client := range sse.clients {
+ client <- message
+ }
+}
+
+func (sse *SSEHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ sse.init()
+
+ w.Header().Set("Content-Type", "text/event-stream")
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Connection", "keep-alive")
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+
+ client := make(chan string)
+ sse.clients[client] = true
+
+ log.Printf(`New connection`)
+
+ if sse.Connected != nil {
+ go sse.Connected(client)
+ }
+
+ defer func() {
+ log.Printf(`Connection closed`)
+ close(client)
+ delete(sse.clients, client)
+ if sse.Disconnected != nil {
+ go sse.Disconnected(client)
+ }
+ }()
+
+ flusher, _ := w.(http.Flusher)
+ for {
+ select {
+ case message, ok := <-client:
+ if !ok {
return
}
+
+ fmt.Fprintf(w, "data: %s\n\n", message)
+ flusher.Flush()
+ case <-r.Context().Done():
+ return
}
- })
+ }
}
+
+// func HandleSSE(handler func(broadcast, single chan<- string)) http.Handler {
+// broadcast := make(chan string)
+
+// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+// w.Header().Set("Content-Type", "text/event-stream")
+// w.Header().Set("Cache-Control", "no-cache")
+// w.Header().Set("Connection", "keep-alive")
+// w.Header().Set("Access-Control-Allow-Origin", "*")
+
+// client := make(chan string)
+// defer func() {
+// close(client)
+// }()
+
+// go handler(broadcast, client)
+
+// flusher, _ := w.(http.Flusher)
+// for {
+// select {
+// case message := <-broadcast:
+// fmt.Fprintf(w, "data: %s\n\n", message)
+// flusher.Flush()
+// case message := <-client:
+// fmt.Fprintf(w, "data: %s\n\n", message)
+// flusher.Flush()
+// case <-r.Context().Done():
+// return
+// }
+// }
+// })
+// }
diff --git a/server/httputil/sse_test.go b/server/httputil/sse_test.go
index d753b34..511cf28 100644
--- a/server/httputil/sse_test.go
+++ b/server/httputil/sse_test.go
@@ -1,6 +1,7 @@
package httputil_test
import (
+ "log"
"net/http"
"testing"
"time"
@@ -9,23 +10,23 @@ import (
)
func TestSSE(t *testing.T) {
- var broadcastChannel chan<- string
+ sse := &httputil.SSEHandler{
+ Connected: func(client chan string) {
+ log.Printf(`New client connected callback`)
+ client <- "Messaggio 1 per questo client"
+ time.Sleep(1 * time.Second)
+ client <- "Messaggio 2 per questo client"
+ },
+ }
go func() {
for {
- if broadcastChannel != nil {
- broadcastChannel <- "Messaggio per tutti"
- }
+ log.Printf(`Broadcasting message`)
+ sse.Broadcast("Messaggio per tutti")
time.Sleep(1 * time.Second)
}
}()
- http.Handle("/sse", httputil.HandleSSE(func(broadcast, single chan<- string) {
- broadcastChannel = broadcast
- time.Sleep(1 * time.Second)
- single <- "Messaggio 1 per questo client"
- single <- "Messaggio 2 per questo client"
- }))
-
+ http.Handle("/sse", sse)
http.ListenAndServe(":8000", nil)
}
diff --git a/server/main.go b/server/main.go
index 3b4a596..aec6ccb 100644
--- a/server/main.go
+++ b/server/main.go
@@ -8,7 +8,7 @@ import (
"github.com/go-chi/chi/v5/middleware"
)
-var HOST = ":3000"
+var HOST = ":4000"
func main() {
r := chi.NewRouter()
@@ -18,6 +18,23 @@ func main() {
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
+ server := NewServer()
+
+ // api := chi.NewRouter()
+ // api.Get("/example", func(w http.ResponseWriter, r *http.Request) {
+ // w.Header().Add("Content-Type", "application/json")
+ // http.SetCookie(w, &http.Cookie{
+ // Name: "test",
+ // Value: "Prova",
+ // Expires: time.Now().Add(120 * time.Second),
+ // })
+ // json.NewEncoder(w).Encode(map[string]interface{}{
+ // "foo": "bar",
+ // })
+ // })
+
+ r.Mount("/api", server.ApiRoute)
+
log.Printf(`Starting server on %s...`, HOST)
err := http.ListenAndServe(HOST, r)
if err != nil {
diff --git a/server/server.go b/server/server.go
new file mode 100644
index 0000000..a068190
--- /dev/null
+++ b/server/server.go
@@ -0,0 +1,134 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+
+ "git.phc.dm.unipi.it/aziis98/posti-dm/server/auth"
+ "git.phc.dm.unipi.it/aziis98/posti-dm/server/db"
+ "git.phc.dm.unipi.it/aziis98/posti-dm/server/httputil"
+ "git.phc.dm.unipi.it/aziis98/posti-dm/server/util"
+ "github.com/alecthomas/repr"
+ "github.com/go-chi/chi/v5"
+)
+
+type Server struct {
+ sessions map[string]*db.User
+ authService *auth.AuthService
+
+ Database db.Store
+ ApiRoute chi.Router
+}
+
+func NewServer() *Server {
+ server := &Server{
+ sessions: make(map[string]*db.User),
+ Database: db.NewInMemoryStore(),
+ ApiRoute: chi.NewRouter(),
+ }
+
+ server.authService = &auth.AuthService{
+ CheckUserPassword: func(userID, password string) error {
+ repr.Println("Sessions: ", server.sessions)
+
+ if password != "phc" {
+ return fmt.Errorf(`invalid password`)
+ }
+
+ return nil
+ },
+ UserPermissions: func(userID string) ([]string, error) {
+ user, err := server.Database.GetUser(userID)
+ if err != nil {
+ return nil, err
+ }
+
+ return user.Permissions, nil
+ },
+ SessionTokenFromUser: func(userID string) (string, error) {
+ user, err := server.Database.GetUser(userID)
+ if err != nil {
+ return "", err
+ }
+
+ token := util.RandomHash(10)
+ server.sessions[token] = user
+
+ return token, nil
+ },
+ UserFromSessionToken: func(session string) (string, error) {
+ user, present := server.sessions[session]
+ if !present {
+ return "", auth.ErrNoUserForSession
+ }
+
+ return user.ID, nil
+ },
+ AuthenticationFailed: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ http.Error(w, `not authenticated`, http.StatusUnauthorized)
+ }),
+ OtherError: func(err error) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ })
+ },
+ }
+
+ server.setupRoutes()
+
+ return server
+}
+
+func (server *Server) setupRoutes() {
+ api := server.ApiRoute
+ db := server.Database
+
+ // Authenticated Routes
+
+ api.Post("/login", func(w http.ResponseWriter, r *http.Request) {
+ var credentials struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ }
+
+ if err := httputil.ReadJSON(r, &credentials); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ server.authService.Login(w, r, credentials.Username, credentials.Password)
+ })
+
+ api.With(server.authService.LoggedMiddleware()).
+ Post("/logout", func(w http.ResponseWriter, r *http.Request) {
+ server.authService.Logout(w)
+ })
+
+ api.With(server.authService.LoggedMiddleware()).
+ Get("/current-seat", func(w http.ResponseWriter, r *http.Request) {
+ userID, err := server.authService.UserFromSession(r)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ occupiedSeatID, err := db.GetUserSeat(userID)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ if len(occupiedSeatID) == 0 {
+ httputil.WriteJSON(w, nil)
+ return
+ }
+
+ seat, err := db.GetSeat(occupiedSeatID[0])
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ httputil.WriteJSON(w, seat)
+ })
+}
diff --git a/server/util/hash.go b/server/util/hash.go
new file mode 100644
index 0000000..6b7fd58
--- /dev/null
+++ b/server/util/hash.go
@@ -0,0 +1,13 @@
+package util
+
+import (
+ "crypto/rand"
+ "encoding/base64"
+)
+
+func RandomHash(len int) string {
+ buff := make([]byte, len)
+ rand.Read(buff)
+ str := base64.StdEncoding.EncodeToString(buff)
+ return str[:len]
+}
Posto 1
-
+
Posto 2
-
+
Posto 3
-
+
Posto 4
-
+
+
Posto 5
-
+
Posto 6
-
+
Posto 7
-
+
Posto 8
-
+
Posto 9
-
+
Posto 10
-
+
+
+
+
+
+
+
+
+
+
+
+