Implementing api handlers

Added some groups handlers and how we obtain the user for the handler

Signed-off-by: Ethan Wellenreiter <ewellenreiter@gmail.com>
This commit is contained in:
Ethan Wellenreiter 2025-05-10 01:32:49 -04:00
parent 5e6d061330
commit 330d850790
5 changed files with 176 additions and 9 deletions

View File

@ -57,6 +57,9 @@ func (app *application) mount() http.Handler {
r.Use(middleware.CleanPath)
r.Use(middleware.Recoverer)
/// CSRF and SESSION TOKEN STUFF:
// Store session token in https or whatever only so js can't access it, and the store csrf locally using react or whatever, remove the cookie, and then include it in the http header under 'X-CSRF-Token' or as a hidden input in an html form
r.Use(middleware.Throttle(100)) // temporary or removable. throttles the whole thing to 1000 concurrent requests
// r.Use(cors.Handler(cors.Options{
// AllowedOrigins: []string{env.GetString("CORS_ALLOWED_ORIGIN", "http://localhost:5174")},
@ -96,7 +99,7 @@ func (app *application) mount() http.Handler {
r.Route("/user", func(r chi.Router) {
r.Route("/{userID}", func(r chi.Router) {
r.Use(app.AuthSessionMiddleware, app.CSRFCheckMiddleware, app.CheckUserMatchingMiddleware)
r.Use(app.AuthSessionMiddleware, app.CSRFCheckMiddleware, app.CheckUserMatchingMiddleware) // consider the actual use of the user matching middleware and whether an admin should be used instead
r.Get("/", app.getUserHandler)
@ -104,16 +107,20 @@ func (app *application) mount() http.Handler {
r.Get("/", app.getUsersGroupsHandler)
r.Route("/{groupID}", func(r chi.Router) {
r.Get("/", app.getUsersGroupHandler)
r.Delete("/", app.removeUserGroupHandler) // maybe this should expect authentication headers to reverify the password when deleting a group you own.
r.Put("/moderator", app.addGroupModeratorHandler)
r.Put("/owner", app.setGroupOwnerHandler)
r.Get("/owner", app.getGroupOwnerHandler)
r.Post("/moderator", app.addGroupModeratorHandler)
r.Delete("/moderator/{secondaryuserID}", app.removeModeratorPriviligesHandler)
r.Get("/users", app.getGroupUsersHandler)
r.Delete("/users/{secondaryuserID}", app.removeUserFromGroupHandler)
r.Post("/users", app.addGroupUserHandler) // needs to create a new user/do the whole invite thing if they don't already exist
r.Delete("/users/{secondaryuserID}", app.removeUserFromGroupHandler) // needs to check if the user is an owner/moderator
r.Put("/owner", app.setGroupOwnerHandler)
})
})
@ -130,6 +137,7 @@ func (app *application) mount() http.Handler {
r.Get("/", app.getReceiptImageHandler)
r.Put("/", app.changeReceiptImageHandler)
r.Delete("/", app.deleteReceiptImageHandler)
r.Post("/confirm", app.confirmImageCreationHandler)
})
})
})
@ -140,7 +148,7 @@ func (app *application) mount() http.Handler {
})
r.Use(app.CSRFCheckMiddleware)
// r.Use(app.CSRFCheckMiddleware)
r.Group(func(r chi.Router) {
r.Use(app.AuthSessionMiddleware)

View File

@ -89,3 +89,5 @@ func (app *application) registerUserHandler(w http.ResponseWriter, r *http.Reque
func (app *application) refreshTokenHandler(w http.ResponseWriter, r *http.Request) {
}
// func (app *application)

106
backend/cmd/api/groups.go Normal file
View File

@ -0,0 +1,106 @@
package main
import (
"context"
"net/http"
"strconv"
"git.ewellenr.ca/receipt_indexer/backend/internal/storage"
"github.com/go-chi/chi/v5"
)
func (app *application) getUserGroups(ctx context.Context, userID int64) (*storage.UserGroups, error) {
if !app.config.redisCfg.enabled {
return app.store.Groups.GetUserGroups(ctx, userID)
}
usergroups, err := app.cacheStorage.UserGroups.Get(ctx, userID)
if err != nil {
return nil, err
}
if usergroups == nil {
usergroups, err = app.store.Groups.GetUserGroups(ctx, userID)
if err != nil {
return nil, err
}
if err := app.cacheStorage.UserGroups.Set(ctx, usergroups); err != nil {
return nil, err
}
}
return usergroups, nil
}
func (app *application) getGroup(ctx context.Context, groupID int64) (*storage.Group, error) {
if !app.config.redisCfg.enabled {
return app.store.Groups.GetByID(ctx, groupID)
}
group, err := app.cacheStorage.Groups.Get(ctx, groupID)
if err != nil {
return nil, err
}
if group == nil {
group, err = app.store.Groups.GetByID(ctx, groupID)
if err != nil {
return nil, err
}
if err := app.cacheStorage.Groups.Set(ctx, group); err != nil {
return nil, err
}
}
return group, nil
}
func (app *application) getUsersGroupsHandler(w http.ResponseWriter, r *http.Request) {
user, err := app.getUserFromURL(w, r)
if err != nil { // assume that the writing was already handled
return
}
usergroups, err := app.getUserGroups(r.Context(), user.ID)
if err != nil {
switch err {
case storage.ErrNotFound:
app.notFoundResponse(w, r, err)
return
default:
app.internalServerError(w, r, err)
return
}
}
if err := app.jsonResponse(w, http.StatusOK, usergroups); err != nil {
app.internalServerError(w, r, err)
}
}
func (app *application) getUsersGroupHandler(w http.ResponseWriter, r *http.Request) {
groupID, err := strconv.ParseInt(chi.URLParam(r, "groupID"), 10, 64)
if err != nil {
app.badRequestResponse(w, r, err)
return
}
group, err := app.getGroup(r.Context(), groupID)
if err != nil {
switch err {
case storage.ErrNotFound:
app.notFoundResponse(w, r, err)
return
default:
app.internalServerError(w, r, err)
return
}
}
if err := app.jsonResponse(w, http.StatusOK, group); err != nil {
app.internalServerError(w, r, err)
}
}

View File

@ -5,6 +5,8 @@ import (
"net/http"
)
// copied from https://github.com/sikozonpc/GopherSocial/blob/main/cmd/api/json.go
func writeJSON(w http.ResponseWriter, status int, data any) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
@ -18,3 +20,21 @@ func writeJSONError(w http.ResponseWriter, status int, message string) error {
return writeJSON(w, status, &envelope{Error: message})
}
func readJSON(w http.ResponseWriter, r *http.Request, data any) error {
maxBytes := 1_048_578 // 1mb
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
return decoder.Decode(data)
}
func (app *application) jsonResponse(w http.ResponseWriter, status int, data any) error {
type envelope struct {
Data any `json:"data"`
}
return writeJSON(w, status, &envelope{Data: data})
}

View File

@ -3,16 +3,47 @@ package main
import (
"context"
"net/http"
"strconv"
auth_storage "git.ewellenr.ca/receipt_indexer/backend/internal/storage/auth"
"git.ewellenr.ca/receipt_indexer/backend/internal/storage"
"github.com/go-chi/chi/v5"
)
type userKey string
const userCtx userKey = "user"
func (app *application) getUserFromURL(w http.ResponseWriter, r *http.Request) (*storage.User, error) {
userID, err := strconv.ParseInt(chi.URLParam(r, "userID"), 10, 64)
if err != nil {
app.badRequestResponse(w, r, err)
return nil, err
}
user, err := app.getUser(r.Context(), userID)
if err != nil {
switch err {
case storage.ErrNotFound:
app.notFoundResponse(w, r, err)
return nil, err
default:
app.internalServerError(w, r, err)
return nil, err
}
}
return user, nil
}
func (app *application) getUserHandler(w http.ResponseWriter, r *http.Request) {
user, err := app.getUserFromURL(w, r)
if err != nil { // assume that the writing was already handled
return
}
if err := app.jsonResponse(w, http.StatusOK, user); err != nil {
app.internalServerError(w, r, err)
}
}
func (app *application) getUsersHandler(w http.ResponseWriter, r *http.Request) {
@ -20,7 +51,7 @@ func (app *application) getUsersHandler(w http.ResponseWriter, r *http.Request)
func (app *application) deleteUserHandler(w http.ResponseWriter, r *http.Request) {
}
func (app *application) getUser(ctx context.Context, userID int64) (*auth_storage.User, error) {
func (app *application) getUser(ctx context.Context, userID int64) (*storage.User, error) {
if !app.config.redisCfg.enabled {
return app.auth.Users.GetByID(ctx, userID)
}
@ -44,7 +75,7 @@ func (app *application) getUser(ctx context.Context, userID int64) (*auth_storag
return user, nil
}
func getUserFromContext(r *http.Request) *auth_storage.User {
user, _ := r.Context().Value(userCtx).(*auth_storage.User)
func getUserFromContext(r *http.Request) *storage.User {
user, _ := r.Context().Value(userCtx).(*storage.User)
return user
}