package main import ( "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/httputil/serverevents" "github.com/go-chi/chi/v5" ) type Server struct { auth *auth.AuthSessionService roomServerEventStreams map[string]serverevents.Handler Database db.Database ApiRoute chi.Router } func NewServer() *Server { database := db.NewInMemoryStore() server := &Server{ Database: database, auth: auth.NewAuthSessionService( &simpleAuthenticator{ make(map[string]string), database, }, ), roomServerEventStreams: make(map[string]serverevents.Handler), ApiRoute: chi.NewRouter(), } roomIDs, _ := database.GetRooms() for _, roomID := range roomIDs { server.roomServerEventStreams[roomID] = serverevents.New(&serverevents.Config{}) } server.setupRoutes() return server } func (server *Server) setupRoutes() { api := server.ApiRoute database := server.Database auth := server.auth // FIXME: in realtà tutte le routes che interagiscono con il db dovrebbero essere in transazione con // database.BeginTransaction() // defer database.EndTransaction() // 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 } err := auth.Login(w, credentials.Username, credentials.Password) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } httputil.WriteJSON(w, "ok") }) api.With(auth.LoggedMiddleware()). Post("/logout", func(w http.ResponseWriter, r *http.Request) { auth.Logout(w) httputil.WriteJSON(w, "ok") }) api.Get("/user", func(w http.ResponseWriter, r *http.Request) { userID, err := auth.RequestUser(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(auth.LoggedMiddleware()). Post("/seat/occupy", func(w http.ResponseWriter, r *http.Request) { database.BeginTransaction() defer database.EndTransaction() userID, err := auth.RequestUser(r) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } user, err := database.GetUser(userID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } 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.StatusInternalServerError) return } // a seat can be occupied only if empty; simple users can occupy a seat only if they have occupied no other seat; admins and moderator (for now) can occupy even more than one seat (as occupied by an anonymous?) to fix the free seat count of the room if len(seat.OccupiedBy) == 0 { userSeats, err := database.GetUserSeats(userID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if !user.Permissions.HasAny(db.PermissionAdmin, db.PermissionModerator) && len(userSeats) > 0 { http.Error(w, `you can occupy only one seat at a time`, http.StatusUnprocessableEntity) return } if err := database.OccupySeat(seatID[0], userID); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } server.roomServerEventStreams[seat.RoomID].Broadcast("refresh") httputil.WriteJSON(w, "ok") } else { http.Error(w, `seat already occupied`, http.StatusUnprocessableEntity) } }) api.With(auth.LoggedMiddleware()). Post("/seat/leave", func(w http.ResponseWriter, r *http.Request) { userID, err := auth.RequestUser(r) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } user, err := database.GetUser(userID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } 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.StatusInternalServerError) return } // Check permissions, if the place is occupied then only its owner, a moderator or an admin can clear it if len(seat.OccupiedBy) > 0 { if user.ID == seat.OccupiedBy[0] || user.Permissions.HasAny(db.PermissionAdmin, db.PermissionModerator) { if err := database.FreeSeat(seatID[0]); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } server.roomServerEventStreams[seat.RoomID].Broadcast("refresh") httputil.WriteJSON(w, "ok") } else { http.Error(w, `seat can be freed only by its owner, a moderator or an admin`, http.StatusUnauthorized) } } else { http.Error(w, `seat already empty`, http.StatusUnprocessableEntity) } }) } // func (server *Server) ConnectedToRoom(roomID string, client chan string)