Beginning to work on the functions used in api.go (the handlers)

Mostly just function signatures at the moment

Signed-off-by: Ethan Wellenreiter <ewellenreiter@gmail.com>
This commit is contained in:
Ethan Wellenreiter 2025-04-30 21:02:51 -04:00
parent d3909a8783
commit a2837b6d82
4 changed files with 255 additions and 0 deletions

91
backend/cmd/api/auth.go Normal file
View File

@ -0,0 +1,91 @@
package main
import (
"encoding/base64"
"fmt"
"net/http"
"strings"
)
func (app *application) loginHandler(w http.ResponseWriter, r *http.Request) {
// should give them a cookie in the response
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
app.unauthorizedBasicErrorResponse(w, r, fmt.Errorf("authorization header is missing"))
return
}
ctx := r.Context()
// parse it -> get the base64
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Basic" {
app.unauthorizedBasicErrorResponse(w, r, fmt.Errorf("authorization header is malformed"))
return
}
// decode it
decoded, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
app.unauthorizedBasicErrorResponse(w, r, err)
return
}
// check the credentials
creds := strings.SplitN(string(decoded), ":", 2)
if len(creds) != 2 {
app.unauthorizedBasicErrorResponse(w, r, fmt.Errorf("invalid credentials"))
return
}
username, pass := creds[0], creds[1]
valid, user, err := app.auth.Users.SigninUser(ctx, username, pass)
if !valid || err != nil {
app.unauthorizedBasicErrorResponse(w, r, fmt.Errorf("invalid credentials"))
return
}
token, err := app.auth.Sessions.AddSession(ctx, user.ID)
if err != nil {
app.unauthorizedBasicErrorResponse(w, r, fmt.Errorf("failed to add session"))
return
}
w.Header().Add("Vary", "Cookie")
w.Header().Add("Cache-Control", `no-cache="Set-Cookie"`)
http.SetCookie(w, &http.Cookie{
Name: "Session_token",
Value: token,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
// Set an expiry and path
})
// also need to set the csrf or cors or refresh stuff here. Probably just the refresh token stuff (actually, with sessions, there will be no refresh token. Just updating the expiry)
// cache and ignore if the cache fails?
if !app.config.redisCfg.enabled {
if err := app.cacheStorage.Users.Set(ctx, user); err != nil {
app.internalServerError(w, r, fmt.Errorf("Failed to add user to cache"))
return
}
}
// since it could be a different storage system, idk if it makes sense to do any caching or to just deal with the overhead of logging in. It doesn't happen that often anyways so caching shouldn't make much of a difference. Maybe load it into cache after
// consider using the cached user stuff instead. that user will have the hashed password which is fine to move around. And then it can just do the compare stuff using whatever auth. Maybe instead, the checkpass can take in a user
}
func (app *application) logoutHandler(w http.ResponseWriter, r *http.Request) {
// should give them a cookie in the response
}
func (app *application) registerUserHandler(w http.ResponseWriter, r *http.Request) {
}
func (app *application) refreshTokenHandler(w http.ResponseWriter, r *http.Request) {
}

44
backend/cmd/api/images.go Normal file
View File

@ -0,0 +1,44 @@
package main
import (
"context"
"net/http"
"git.ewellenr.ca/receipt_indexer/backend/internal/storage"
)
func (app *application) getImage(ctx context.Context, imageID int64) (*storage.Image, error) {
if !app.config.redisCfg.enabled {
return app.store.Images.GetByID(ctx, imageID)
}
image, err := app.cacheStorage.ReceiptImage.Get(ctx, imageID)
if err != nil {
return nil, err
}
if image == nil {
image, err = app.store.Images.GetByID(ctx, imageID)
if err != nil {
return nil, err
}
if err := app.cacheStorage.ReceiptImage.Set(ctx, image); err != nil {
return nil, err
}
}
return image, nil
}
func (app *application) addImageHandler(w http.ResponseWriter, r *http.Request) {
// create a new image, add it to the receipt. this should be a function because it should be one whole transaction
// it should be do the database transaction, attempt the upload, and then abort/commit the transaction depending on the results of the upload. While uploading directly to the s3/minio would be good, it doesn't provide the transactionality that is required
}
func (app *application) deleteImageHandler(w http.ResponseWriter, r *http.Request) {
// delete image and remove from cache
// this also needs to be transactional first the database transaction stuff, then attempt the delete, and then abort/commit the transaction depending on the results of the upload.
}

View File

@ -0,0 +1,70 @@
package main
import (
"context"
"net/http"
"git.ewellenr.ca/receipt_indexer/backend/internal/storage"
)
type receiptKey string
const receiptCtx receiptKey = "receipt"
func (app *application) getReceiptsHandler(w http.ResponseWriter, r *http.Request) {
// get the page and size from context
// default them to something
}
func (app *application) getReceiptHandler(w http.ResponseWriter, r *http.Request) {
}
func (app *application) createReceiptHandler(w http.ResponseWriter, r *http.Request) {
// handle receipt creation logic here
}
func (app *application) updateReceiptHandler(w http.ResponseWriter, r *http.Request) {
// handle receipt update logic here
// Not too sure what to do here. need to break it down into what can actually be updated via the api
}
func (app *application) deleteReceiptHandler(w http.ResponseWriter, r *http.Request) {
// delete the receipt
// should be as simple as getting the receipt and then calling the delete function.
// Should also delete from cache if cache is enabled
// the delete function should be like a transaction. It should do the transaction for the receipt and images in the db.
// Then it should try and delete all the images in the S3/minio bucket. Abort/commit otherwise
}
func (app *application) getReceipt(ctx context.Context, receiptID int64) (*storage.Receipt, error) {
if !app.config.redisCfg.enabled {
return app.store.Receipts.GetByID(ctx, receiptID)
}
receipt, err := app.cacheStorage.Receipts.Get(ctx, receiptID)
if err != nil {
return nil, err
}
if receipt == nil {
receipt, err = app.store.Receipts.GetByID(ctx, receiptID)
if err != nil {
return nil, err
}
if err := app.cacheStorage.Receipts.Set(ctx, receipt); err != nil {
return nil, err
}
}
return receipt, nil
}
func getReceiptFromContext(r *http.Request) *storage.Receipt {
receipt, _ := r.Context().Value(receiptCtx).(*storage.Receipt)
return receipt
}

50
backend/cmd/api/users.go Normal file
View File

@ -0,0 +1,50 @@
package main
import (
"context"
"net/http"
auth_storage "git.ewellenr.ca/receipt_indexer/backend/internal/storage/auth"
)
type userKey string
const userCtx userKey = "user"
func (app *application) getUserHandler(w http.ResponseWriter, r *http.Request) {
}
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) {
if !app.config.redisCfg.enabled {
return app.auth.Users.GetByID(ctx, userID)
}
user, err := app.cacheStorage.Users.Get(ctx, userID)
if err != nil {
return nil, err
}
if user == nil {
user, err = app.auth.Users.GetByID(ctx, userID)
if err != nil {
return nil, err
}
if err := app.cacheStorage.Users.Set(ctx, user); err != nil {
return nil, err
}
}
return user, nil
}
func getUserFromContext(r *http.Request) *auth_storage.User {
user, _ := r.Context().Value(userCtx).(*auth_storage.User)
return user
}