| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- package http
- import (
- "encoding/json"
- "errors"
- "fmt"
- "log/slog"
- "net/http"
- "os"
- "github.com/google/uuid"
- "github.com/mk6i/retro-aim-server/config"
- "github.com/mk6i/retro-aim-server/state"
- )
- type userWithPassword struct {
- state.User
- Password string `json:"password,omitempty"`
- }
- type userSession struct {
- ScreenName string `json:"screen_name"`
- }
- type onlineUsers struct {
- Count int `json:"count"`
- Sessions []userSession `json:"sessions"`
- }
- type UserManager interface {
- AllUsers() ([]state.User, error)
- InsertUser(u state.User) error
- SetUserPassword(u state.User) error
- }
- type SessionRetriever interface {
- AllSessions() []*state.Session
- }
- func StartManagementAPI(userManager UserManager, sessionRetriever SessionRetriever, logger *slog.Logger) {
- mux := http.NewServeMux()
- newUser := func() state.User {
- return state.User{AuthKey: uuid.New().String()}
- }
- mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
- userHandler(w, r, userManager, newUser, logger)
- })
- mux.HandleFunc("/user/password", func(w http.ResponseWriter, r *http.Request) {
- userPasswordHandler(w, r, userManager, newUser, logger)
- })
- mux.HandleFunc("/session", func(w http.ResponseWriter, r *http.Request) {
- sessionHandler(w, r, sessionRetriever)
- })
- //todo make port configurable
- addr := config.Address("", 8080)
- logger.Info("starting management API server", "addr", addr)
- if err := http.ListenAndServe(addr, mux); err != nil {
- logger.Error("unable to bind management API address address", "err", err.Error())
- os.Exit(1)
- }
- }
- func userHandler(
- w http.ResponseWriter,
- r *http.Request,
- userManager UserManager,
- newUser func() state.User,
- logger *slog.Logger,
- ) {
- switch r.Method {
- case http.MethodGet:
- getUserHandler(w, r, userManager, logger)
- case http.MethodPost:
- postUserHandler(w, r, userManager, newUser, logger)
- default:
- http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
- }
- }
- func userPasswordHandler(
- w http.ResponseWriter,
- r *http.Request,
- userManager UserManager,
- userFactory func() state.User,
- logger *slog.Logger,
- ) {
- switch r.Method {
- case http.MethodPut:
- putUserPasswordHandler(w, r, userManager, userFactory, logger)
- default:
- http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
- }
- }
- // putUserPasswordHandler handles the PUT /user/password endpoint.
- func putUserPasswordHandler(
- w http.ResponseWriter,
- r *http.Request,
- userManager UserManager,
- newUser func() state.User,
- logger *slog.Logger,
- ) {
- user := userWithPassword{
- User: newUser(),
- }
- if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
- http.Error(w, "malformed input", http.StatusBadRequest)
- return
- }
- if err := user.HashPassword(user.Password); err != nil {
- logger.Error("error hashing user password in PUT /user/password", "err", err.Error())
- http.Error(w, "internal server error", http.StatusInternalServerError)
- return
- }
- if err := userManager.SetUserPassword(user.User); err != nil {
- switch {
- case errors.Is(err, state.ErrNoUser):
- http.Error(w, "user does not exist", http.StatusNotFound)
- return
- case err != nil:
- logger.Error("error updating user password PUT /user/password", "err", err.Error())
- http.Error(w, "internal server error", http.StatusInternalServerError)
- return
- }
- }
- w.WriteHeader(http.StatusNoContent)
- }
- // sessionHandler handles GET /session
- func sessionHandler(w http.ResponseWriter, r *http.Request, sessionRetriever SessionRetriever) {
- w.Header().Set("Content-Type", "application/json")
- if r.Method != http.MethodGet {
- http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
- return
- }
- allUsers := sessionRetriever.AllSessions()
- ou := onlineUsers{
- Count: len(allUsers),
- Sessions: make([]userSession, 0),
- }
- for _, s := range allUsers {
- ou.Sessions = append(ou.Sessions, userSession{
- ScreenName: s.ScreenName(),
- })
- }
- if err := json.NewEncoder(w).Encode(ou); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- }
- // getUserHandler handles the GET /user endpoint.
- func getUserHandler(w http.ResponseWriter, _ *http.Request, userManager UserManager, logger *slog.Logger) {
- w.Header().Set("Content-Type", "application/json")
- users, err := userManager.AllUsers()
- if err != nil {
- logger.Error("error in GET /user", "err", err.Error())
- http.Error(w, "internal server error", http.StatusInternalServerError)
- return
- }
- if err := json.NewEncoder(w).Encode(users); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- }
- // postUserHandler handles the POST /user endpoint.
- func postUserHandler(
- w http.ResponseWriter,
- r *http.Request,
- userManager UserManager,
- newUser func() state.User,
- logger *slog.Logger,
- ) {
- user := userWithPassword{
- User: newUser(),
- }
- if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
- http.Error(w, "malformed input", http.StatusBadRequest)
- return
- }
- if err := user.HashPassword(user.Password); err != nil {
- logger.Error("error hashing user password in POST /user", "err", err.Error())
- http.Error(w, "internal server error", http.StatusInternalServerError)
- return
- }
- err := userManager.InsertUser(user.User)
- switch {
- case errors.Is(err, state.ErrDupUser):
- http.Error(w, "user already exists", http.StatusConflict)
- return
- case err != nil:
- logger.Error("error inserting user POST /user", "err", err.Error())
- http.Error(w, "internal server error", http.StatusInternalServerError)
- return
- }
- w.WriteHeader(http.StatusCreated)
- fmt.Fprintln(w, "User account created successfully.")
- }
|