package main
import (
"fmt"
"log"
"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"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/util"
"github.com/go-chi/chi/v5"
)
type Server struct {
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 {
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
err := database . CreateUser ( & db . User {
ID : userID ,
Permissions : make ( util . StringSet ) ,
} )
if err != nil {
log . Printf ( ` got "%v" while trying to log as @%s ` , err , userID )
return nil
}
return nil
} ,
UserPermissions : func ( userID string ) ( [ ] string , error ) {
user , err := database . GetUser ( userID )
if err != nil {
return nil , err
}
return user . Permissions . Elements ( ) , nil
} ,
SessionTokenFromUser : func ( userID string ) ( string , error ) {
user , err := database . GetUser ( userID )
if err != nil {
return "" , err
}
token := util . RandomHash ( 20 )
sessions [ token ] = user
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 ( ) ,
}
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 . authService
// 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 , r , 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)