Raggiunto primo stato presentabile

This commit is contained in:
2022-03-05 04:45:12 +01:00
parent b0732c9c4f
commit bbd6b9eb7e
19 changed files with 1326 additions and 468 deletions
+4 -4
View File
@@ -49,7 +49,7 @@ type AuthService struct {
UserFromSessionToken func(session string) (string, error)
// AuthenticationFailed handles failed authentications
AuthenticationFailed http.Handler
AuthenticationFailed func(error) http.Handler
// OtherError handles other errors
OtherError func(error) http.Handler
@@ -57,7 +57,7 @@ type AuthService struct {
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)
auth.AuthenticationFailed(err).ServeHTTP(w, r)
return
}
@@ -98,7 +98,7 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
return
}
auth.AuthenticationFailed.ServeHTTP(w, r)
auth.AuthenticationFailed(err).ServeHTTP(w, r)
return
}
if err != nil {
@@ -139,7 +139,7 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
}
if !hasAll {
auth.AuthenticationFailed.ServeHTTP(w, r)
auth.AuthenticationFailed(err).ServeHTTP(w, r)
return
}
}
+108 -40
View File
@@ -1,27 +1,33 @@
package db
import "fmt"
import (
"errors"
"fmt"
)
var ErrAlreadyExists = errors.New(`object already exists in database`)
var ErrDoesntExist = errors.New(`object doesn't exist in database`)
// Entities
type User struct {
ID string
Username string
Permissions []string
ID string `json:"id"`
Permissions []string `json:"permissions"`
}
type Room struct {
ID string
SeatIDs []string
ID string `json:"id"`
SeatIDs []string `json:"seatIds"`
// gridRows, gridCols int
}
type Seat struct {
ID string
ID string `json:"id"`
RoomID string `json:"roomId"`
// OccupiedBy is an empty list or a singleton of a userID
OccupiedBy []string
OccupiedBy []string `json:"occupiedBy"`
// x, y, w, h int
}
@@ -29,16 +35,27 @@ type Seat struct {
// Database Interfaces
type Store interface {
CreateUser(user *User) error
CreateRoom(room *Room) error
CreateSeat(seat *Seat) error
DeleteUser(user *User) error
DeleteRoom(room *Room) error
DeleteSeat(seat *Seat) error
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)
GetRooms() ([]string, error)
GetRoomOccupiedSeats(roomID string) ([]string, error)
GetRoomFreeSeats(roomID string) ([]string, error)
GetUserSeat(userID string) ([]string, error)
OccupySeat(userID, roomID, seatID string) error
FreeSeat(userID, roomID, seatID string) error
OccupySeat(seatID, userID string) error
FreeSeat(seatID string) error
}
// TODO: Create an SQLite implementation
@@ -57,47 +74,88 @@ func NewInMemoryStore() Store {
}
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",
},
ID: "aula-stud",
SeatIDs: []string{},
}
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{},
for i := 1; i <= 11; i++ {
seatID := fmt.Sprintf(`aula-stud/posto-%d`, i)
db.rooms["aula-stud"].SeatIDs = append(db.rooms["aula-stud"].SeatIDs, seatID)
db.seats[seatID] = &Seat{
ID: seatID,
RoomID: "aula-stud",
OccupiedBy: []string{},
}
}
db.seats["aula-stud/posto-7"].OccupiedBy = []string{"aziis98"}
db.seats["aula-stud/posto-8"].OccupiedBy = []string{"jack"}
db.users["aziis98"] = &User{
ID: "aziis98",
Username: "aziis98",
Permissions: []string{"admin"},
}
db.users["bachoseven"] = &User{
ID: "bachoseven",
Username: "bachoseven",
Permissions: []string{"admin"},
}
return db
}
func (db *memDB) CreateUser(user *User) error {
if _, present := db.users[user.ID]; present {
return ErrAlreadyExists
}
db.users[user.ID] = user
return nil
}
func (db *memDB) CreateRoom(room *Room) error {
if _, present := db.rooms[room.ID]; present {
return ErrAlreadyExists
}
db.rooms[room.ID] = room
return nil
}
func (db *memDB) CreateSeat(seat *Seat) error {
if _, present := db.seats[seat.ID]; present {
return ErrAlreadyExists
}
db.seats[seat.ID] = seat
return nil
}
func (db *memDB) DeleteUser(user *User) error {
if _, present := db.users[user.ID]; !present {
return ErrDoesntExist
}
delete(db.users, user.ID)
return nil
}
func (db *memDB) DeleteRoom(room *Room) error {
if _, present := db.rooms[room.ID]; !present {
return ErrDoesntExist
}
delete(db.rooms, room.ID)
return nil
}
func (db *memDB) DeleteSeat(seat *Seat) error {
if _, present := db.seats[seat.ID]; !present {
return ErrDoesntExist
}
delete(db.seats, seat.ID)
return nil
}
func (db *memDB) GetUser(userID string) (*User, error) {
user, present := db.users[userID]
if !present {
@@ -125,7 +183,17 @@ func (db *memDB) GetSeat(seatID string) (*Seat, error) {
return seat, nil
}
func (db *memDB) GetOccupiedSeats(roomID string) ([]string, error) {
func (db *memDB) GetRooms() ([]string, error) {
roomIDs := []string{}
for roomID := range db.rooms {
roomIDs = append(roomIDs, roomID)
}
return roomIDs, nil
}
func (db *memDB) GetRoomOccupiedSeats(roomID string) ([]string, error) {
room, err := db.GetRoom(roomID)
if err != nil {
return nil, err
@@ -142,7 +210,7 @@ func (db *memDB) GetOccupiedSeats(roomID string) ([]string, error) {
return seats, nil
}
func (db *memDB) GetFreeSeats(roomID string) ([]string, error) {
func (db *memDB) GetRoomFreeSeats(roomID string) ([]string, error) {
room, err := db.GetRoom(roomID)
if err != nil {
return nil, err
@@ -169,12 +237,12 @@ func (db *memDB) GetUserSeat(userID string) ([]string, error) {
return []string{}, nil
}
func (db *memDB) OccupySeat(userID string, roomID string, seatID string) error {
func (db *memDB) OccupySeat(seatID, userID string) error {
db.seats[seatID].OccupiedBy = []string{userID}
return nil
}
func (db *memDB) FreeSeat(userID string, roomID string, seatID string) error {
func (db *memDB) FreeSeat(seatID string) error {
db.seats[seatID].OccupiedBy = []string{}
return nil
}
+5 -2
View File
@@ -7,9 +7,12 @@ import (
type H map[string]interface{}
func WriteJSON(w http.ResponseWriter, data interface{}) error {
func WriteJSON(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
return json.NewEncoder(w).Encode(data)
err := json.NewEncoder(w).Encode(data)
if err != nil {
panic(err)
}
}
func ReadJSON(r *http.Request, data interface{}) error {
+78
View File
@@ -0,0 +1,78 @@
package serverevents
import (
"fmt"
"log"
"net/http"
)
type Config struct {
Connected func(chan string)
Disconnected func(chan string)
}
type Handler interface {
http.Handler
Broadcast(message string)
}
type handler struct {
clients map[chan string]bool
Connected func(chan string)
Disconnected func(chan string)
}
func New(config *Config) Handler {
return &handler{
make(map[chan string]bool),
config.Connected,
config.Disconnected,
}
}
func (sse *handler) Broadcast(message string) {
for client := range sse.clients {
client <- message
}
}
func (sse *handler) ServeHTTP(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)
sse.clients[client] = true
log.Printf(`New connection`)
if sse.Connected != nil {
sse.Connected(client)
}
defer func() {
log.Printf(`Connection closed`)
close(client)
delete(sse.clients, client)
if sse.Disconnected != nil {
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
}
}
}
+34
View File
@@ -0,0 +1,34 @@
package serverevents_test
import (
"log"
"net/http"
"testing"
"time"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/httputil/serverevents"
)
func TestSSE(t *testing.T) {
sse := serverevents.New(&serverevents.Config{
Connected: func(client chan string) {
go func() {
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 {
log.Printf(`Broadcasting message`)
sse.Broadcast("Messaggio per tutti")
time.Sleep(1 * time.Second)
}
}()
http.Handle("/sse", sse)
http.ListenAndServe(":8000", nil)
}
-102
View File
@@ -1,102 +0,0 @@
package httputil
import (
"fmt"
"log"
"net/http"
)
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
// }
// }
// })
// }
-32
View File
@@ -1,32 +0,0 @@
package httputil_test
import (
"log"
"net/http"
"testing"
"time"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/httputil"
)
func TestSSE(t *testing.T) {
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 {
log.Printf(`Broadcasting message`)
sse.Broadcast("Messaggio per tutti")
time.Sleep(1 * time.Second)
}
}()
http.Handle("/sse", sse)
http.ListenAndServe(":8000", nil)
}
+163 -56
View File
@@ -7,71 +7,93 @@ import (
"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/httputil/serverevents"
"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
roomServerEventStreams map[string]serverevents.Handler
Database db.Store
ApiRoute chi.Router
}
func NewServer() *Server {
sessions := make(map[string]*db.User)
database := db.NewInMemoryStore()
server := &Server{
sessions: make(map[string]*db.User),
Database: db.NewInMemoryStore(),
Database: database,
authService: &auth.AuthService{
CheckUserPassword: func(userID, password string) error {
if password != "phc" {
return fmt.Errorf(`invalid password`)
}
// FIXME: al momento quando la password è giusta creiamo tutti gli account necessari
database.CreateUser(&db.User{
ID: userID,
Permissions: []string{},
})
return nil
},
UserPermissions: func(userID string) ([]string, error) {
user, err := database.GetUser(userID)
if err != nil {
return nil, err
}
return user.Permissions, nil
},
SessionTokenFromUser: func(userID string) (string, error) {
user, err := database.GetUser(userID)
if err != nil {
return "", err
}
token := util.RandomHash(10)
sessions[token] = user
repr.Println("Sessions: ", sessions)
return token, nil
},
UserFromSessionToken: func(session string) (string, error) {
user, present := sessions[session]
if !present {
return "", auth.ErrNoUserForSession
}
return user.ID, nil
},
AuthenticationFailed: func(err error) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), 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)
})
},
},
roomServerEventStreams: make(map[string]serverevents.Handler),
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)
})
},
roomIDs, _ := database.GetRooms()
for _, roomID := range roomIDs {
server.roomServerEventStreams[roomID] = serverevents.New(&serverevents.Config{
Connected: func(client chan string) {
server.ConnectedToRoom(roomID, client)
},
})
}
server.setupRoutes()
@@ -81,7 +103,7 @@ func NewServer() *Server {
func (server *Server) setupRoutes() {
api := server.ApiRoute
db := server.Database
database := server.Database
// Authenticated Routes
@@ -104,31 +126,116 @@ func (server *Server) setupRoutes() {
server.authService.Logout(w)
})
api.Get("/user", func(w http.ResponseWriter, r *http.Request) {
userID, err := server.authService.UserFromSession(r)
if err != nil {
httputil.WriteJSON(w, nil)
return
}
user, err := database.GetUser(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
}
httputil.WriteJSON(w, user)
})
api.Get("/room/seats", func(w http.ResponseWriter, r *http.Request) {
roomID, ok := r.URL.Query()["id"]
if !ok {
http.Error(w, `missing room id`, http.StatusBadRequest)
}
room, err := database.GetRoom(roomID[0])
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
seats := []*db.Seat{}
for _, seatID := range room.SeatIDs {
seat, err := database.GetSeat(seatID)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
seats = append(seats, seat)
}
httputil.WriteJSON(w, seats)
})
api.HandleFunc("/room_events", func(w http.ResponseWriter, r *http.Request) {
roomID, ok := r.URL.Query()["id"]
if !ok {
http.Error(w, `missing room id`, http.StatusBadRequest)
return
}
server.roomServerEventStreams[roomID[0]].ServeHTTP(w, r)
})
api.With(server.authService.LoggedMiddleware()).
Get("/current-seat", func(w http.ResponseWriter, r *http.Request) {
Post("/seat/occupy", func(w http.ResponseWriter, r *http.Request) {
userID, err := server.authService.UserFromSession(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
occupiedSeatID, err := db.GetUserSeat(userID)
seatID, ok := r.URL.Query()["id"]
if !ok {
http.Error(w, `missing seat id`, http.StatusBadRequest)
return
}
seat, err := database.GetSeat(seatID[0])
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
repr.Println(userID, seatID)
if err := database.OccupySeat(seatID[0], userID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if len(occupiedSeatID) == 0 {
httputil.WriteJSON(w, nil)
server.roomServerEventStreams[seat.RoomID].Broadcast("refresh")
httputil.WriteJSON(w, "ok")
})
api.With(server.authService.LoggedMiddleware()).
Post("/seat/leave", func(w http.ResponseWriter, r *http.Request) {
seatID, ok := r.URL.Query()["id"]
if !ok {
http.Error(w, `missing seat id`, http.StatusBadRequest)
return
}
seat, err := db.GetSeat(occupiedSeatID[0])
seat, err := database.GetSeat(seatID[0])
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if err := database.FreeSeat(seatID[0]); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteJSON(w, seat)
server.roomServerEventStreams[seat.RoomID].Broadcast("refresh")
httputil.WriteJSON(w, "ok")
})
}
func (server *Server) ConnectedToRoom(roomID string, client chan string) {
// go func() {
// for {
// client <- "hi from server!"
// time.Sleep(1 * time.Second)
// }
// }()
}