Compare commits
6 Commits
330d850790
...
d1a267eb44
| Author | SHA1 | Date | |
|---|---|---|---|
| d1a267eb44 | |||
| 47ff895b26 | |||
| a22bbbf5ab | |||
| 9b9305db29 | |||
| b687b43b35 | |||
| 18330e745f |
@ -14,33 +14,43 @@ import (
|
||||
"github.com/go-chi/httprate"
|
||||
|
||||
// "git.ewellenr.ca/receipt_indexer/backend/internal/auth"
|
||||
|
||||
"git.ewellenr.ca/receipt_indexer/backend/internal/env"
|
||||
"git.ewellenr.ca/receipt_indexer/backend/internal/logger"
|
||||
"git.ewellenr.ca/receipt_indexer/backend/internal/ratelimiter"
|
||||
"git.ewellenr.ca/receipt_indexer/backend/internal/storage"
|
||||
auth_storage "git.ewellenr.ca/receipt_indexer/backend/internal/storage/auth"
|
||||
"git.ewellenr.ca/receipt_indexer/backend/internal/storage/cache"
|
||||
)
|
||||
|
||||
type application struct {
|
||||
// set up the configs stuff here
|
||||
config config
|
||||
auth auth_storage.AuthStorage
|
||||
store storage.Storage
|
||||
logger logger.Logger
|
||||
cacheStorage cache.Storage
|
||||
rateLimiter ratelimiter.Limiter
|
||||
environment env.Environment
|
||||
store storage.Storage // for user storage. also stores authentication stuff
|
||||
logger logger.Logger // logging events/actions/issues
|
||||
cacheStorage cache.Storage // for caching the storage
|
||||
rateLimiter ratelimiter.Limiter // rate limiting users
|
||||
environment env.Environment // accessing environment variables for the setup
|
||||
}
|
||||
|
||||
type config struct {
|
||||
addr string
|
||||
storeCfg storeConfig
|
||||
envCfg envConfig
|
||||
apiUrl string
|
||||
frontendUrl string
|
||||
auth authConfig
|
||||
mail mailConfig
|
||||
cacheCfg redisConfig
|
||||
rateLimiter ratelimiter.Config
|
||||
redisCfg redisConfig
|
||||
|
||||
// holds the different stuff like rate limiter, store, authenticator
|
||||
}
|
||||
|
||||
type storeConfig struct {
|
||||
db dbConfig
|
||||
authStore authConfig
|
||||
}
|
||||
|
||||
type redisConfig struct {
|
||||
addr string
|
||||
pw string
|
||||
@ -108,36 +118,49 @@ func (app *application) mount() http.Handler {
|
||||
|
||||
r.Route("/{groupID}", func(r chi.Router) {
|
||||
|
||||
r.Use(app.addGroupToContextMiddleware)
|
||||
|
||||
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("/owner", app.setGroupOwnerHandler)
|
||||
r.Get("/owner", app.getGroupOwnerHandler)
|
||||
|
||||
r.Post("/moderator", app.addGroupModeratorHandler)
|
||||
r.Put("/moderator", app.addGroupModeratorHandler)
|
||||
r.Delete("/moderator/{secondaryuserID}", app.removeModeratorPriviligesHandler)
|
||||
|
||||
r.Get("/users", app.getGroupUsersHandler)
|
||||
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.Route("/users", func(r chi.Router) {
|
||||
|
||||
r.Use(app.CheckGroupRoleMiddleware) // need to verify that they are an owner/moderator
|
||||
|
||||
r.Get("/", app.getGroupUsersHandler)
|
||||
r.Post("/", app.addGroupUserHandler) // needs to create a new user/do the whole invite thing if they don't already exist
|
||||
r.Delete("/{secondaryuserID}", app.removeUserFromGroupHandler) // needs to check if the user is an owner/moderator
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
r.Route("/receipts", func(r chi.Router) {
|
||||
r.Get("/", app.getReceiptsHandler)
|
||||
r.Use(app.addQueryParamsToContextMiddleware)
|
||||
|
||||
r.Get("/", app.getReceiptsHandler) // batch get. should allow pagination and potentially a filter of some sort in the request. Does not return images or anything like that. Just the basic data (id, amount, date, location/store)
|
||||
|
||||
r.Route("/{receiptID}", func(r chi.Router) {
|
||||
|
||||
r.Use(app.receiptContextMiddleware)
|
||||
|
||||
r.Get("/", app.getReceiptHandler)
|
||||
r.Delete("/", app.deleteReceiptHandler)
|
||||
|
||||
r.Route("/images", func(r chi.Router) {
|
||||
r.Get("/", app.getReceiptImagesHandler)
|
||||
r.Put("/", app.addReceiptImageHandler)
|
||||
r.Post("/", app.addReceiptImageHandler)
|
||||
r.Route("/{imageID}", func(r chi.Router) {
|
||||
r.Get("/", app.getReceiptImageHandler)
|
||||
r.Put("/", app.changeReceiptImageHandler)
|
||||
r.Delete("/", app.deleteReceiptImageHandler)
|
||||
r.Post("/confirm", app.confirmImageCreationHandler)
|
||||
r.Put("/confirm", app.confirmImageCreationHandler)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -9,8 +9,12 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type groupKey string
|
||||
|
||||
const groupCtx groupKey = "group"
|
||||
|
||||
func (app *application) getUserGroups(ctx context.Context, userID int64) (*storage.UserGroups, error) {
|
||||
if !app.config.redisCfg.enabled {
|
||||
if !app.config.cacheCfg.enabled {
|
||||
return app.store.Groups.GetUserGroups(ctx, userID)
|
||||
}
|
||||
|
||||
@ -34,7 +38,7 @@ func (app *application) getUserGroups(ctx context.Context, userID int64) (*stora
|
||||
}
|
||||
|
||||
func (app *application) getGroup(ctx context.Context, groupID int64) (*storage.Group, error) {
|
||||
if !app.config.redisCfg.enabled {
|
||||
if !app.config.cacheCfg.enabled {
|
||||
return app.store.Groups.GetByID(ctx, groupID)
|
||||
|
||||
}
|
||||
@ -104,3 +108,24 @@ func (app *application) getUsersGroupHandler(w http.ResponseWriter, r *http.Requ
|
||||
app.internalServerError(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) setGroupOwnerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (app *application) getGroupOwnerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (app *application) addGroupModeratorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (app *application) removeModeratorPriviligesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (app *application) getGroupUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (app *application) addGroupUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (app *application) removeUserFromGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
@ -7,7 +7,8 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
auth_storage "git.ewellenr.ca/receipt_indexer/backend/internal/storage/auth"
|
||||
// auth_storage "git.ewellenr.ca/receipt_indexer/backend/internal/storage/auth"
|
||||
l_context "git.ewellenr.ca/receipt_indexer/backend/internal/context"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
@ -27,7 +28,7 @@ func (app *application) AuthSessionMiddleware(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
valid, userID, err := app.auth.Sessions.CheckSession(r.Context(), token) // should have a different function for this
|
||||
valid, userID, err := app.store.Sessions.CheckSession(r.Context(), token) // should have a different function for this
|
||||
if !valid {
|
||||
app.unauthorizedErrorResponse(w, r, fmt.Errorf("Invalid session token"))
|
||||
return
|
||||
@ -131,7 +132,7 @@ func (app *application) RateLimiterMiddleware(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func (app *application) receiptsContextMiddleware(next http.Handler) http.Handler {
|
||||
func (app *application) receiptContextMiddleware(next http.Handler) http.Handler {
|
||||
// add the receipt id to the context? or the receipt class to the context
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@ -163,7 +164,7 @@ func (app *application) receiptsContextMiddleware(next http.Handler) http.Handle
|
||||
}
|
||||
|
||||
func (app *application) checkRolePrecedence(ctx context.Context, user *auth_storage.User, roleName string) (bool, error) {
|
||||
role, err := app.auth.Roles.GetByName(ctx, roleName)
|
||||
role, err := app.store.Roles.GetByName(ctx, roleName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -195,3 +196,36 @@ func (app *application) checkReceiptOwnership(requiredRole string, next http.Han
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (app *application) addGroupToContextMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
urlGroupID, err := strconv.ParseInt(chi.URLParam(r, "groupID"), 10, 64)
|
||||
if err != nil {
|
||||
app.badRequestResponse(w, r, fmt.Errorf("Invalid url group ID - Not an integer"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
group, err := app.getGroup(ctx, urlGroupID)
|
||||
if err != nil {
|
||||
app.unauthorizedErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
ctx = context.WithValue(ctx, groupCtx, group)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func (app *application) addQueryParamsToContextMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// need to select the user from the token validation stuff
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, l_context.QueryParamsCtx, r.URL.Query())
|
||||
|
||||
// make sure to add user and role into the context here
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
@ -5,20 +5,42 @@ import (
|
||||
"net/http"
|
||||
|
||||
"git.ewellenr.ca/receipt_indexer/backend/internal/storage"
|
||||
"git.ewellenr.ca/receipt_indexer/backend/internal/storage/cache"
|
||||
)
|
||||
|
||||
type receiptKey string
|
||||
|
||||
const receiptCtx receiptKey = "receipt"
|
||||
|
||||
type receiptsQuery struct {
|
||||
Length int `json:"length"`
|
||||
List []*storage.Receipt `json:"list"`
|
||||
}
|
||||
|
||||
func (app *application) getReceiptsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// get the page and size from context
|
||||
// default them to something
|
||||
receipts, err := app.store.Receipts.GetForUser(r.Context(), getUserFromContext(r).ID)
|
||||
if err != nil {
|
||||
app.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.jsonResponse(w, http.StatusOK, &receiptsQuery{Length: len(receipts), List: receipts}); err != nil {
|
||||
app.internalServerError(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) getReceiptHandler(w http.ResponseWriter, r *http.Request) {
|
||||
receipt, err := app.store.Receipts.GetByID(r.Context())
|
||||
if err != nil {
|
||||
app.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.jsonResponse(w, http.StatusOK, &receiptsQuery{Length: len(receipts), List: receipts}); err != nil {
|
||||
app.internalServerError(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) createReceiptHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@ -41,7 +63,7 @@ func (app *application) deleteReceiptHandler(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
func (app *application) getReceipt(ctx context.Context, receiptID int64) (*storage.Receipt, error) {
|
||||
if !app.config.redisCfg.enabled {
|
||||
if !app.config.cacheCfg.enabled {
|
||||
return app.store.Receipts.GetByID(ctx, receiptID)
|
||||
}
|
||||
|
||||
@ -64,6 +86,36 @@ func (app *application) getReceipt(ctx context.Context, receiptID int64) (*stora
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
func (app *application) getReceipts(ctx context.Context, userID int64) ([]*storage.Receipt, error) {
|
||||
if !app.config.cacheCfg.enabled {
|
||||
return app.store.Receipts.GetForUser(ctx, userID)
|
||||
}
|
||||
|
||||
var queryID int64 = 0 //encode it here
|
||||
receiptIDs, err := app.cacheStorage.ReceiptList.Get(ctx, queryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if receiptIDs == nil {
|
||||
receipts, err := app.store.Receipts.GetForUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
receiptIDs = []int64{}
|
||||
for _, val := range receipts {
|
||||
receiptIDs = append(receiptIDs, val.ID)
|
||||
}
|
||||
|
||||
if err := app.cacheStorage.ReceiptList.Set(ctx, cache.ReceiptListKeyVal{ID: queryID, List: receiptIDs}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
func getReceiptFromContext(r *http.Request) *storage.Receipt {
|
||||
receipt, _ := r.Context().Value(receiptCtx).(*storage.Receipt)
|
||||
return receipt
|
||||
|
||||
@ -51,9 +51,12 @@ func (app *application) getUsersHandler(w http.ResponseWriter, r *http.Request)
|
||||
func (app *application) deleteUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (app *application) removeUserGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (app *application) getUser(ctx context.Context, userID int64) (*storage.User, error) {
|
||||
if !app.config.redisCfg.enabled {
|
||||
return app.auth.Users.GetByID(ctx, userID)
|
||||
if !app.config.cacheCfg.enabled {
|
||||
return app.store.Users.GetByID(ctx, userID)
|
||||
}
|
||||
|
||||
user, err := app.cacheStorage.Users.Get(ctx, userID)
|
||||
@ -62,7 +65,7 @@ func (app *application) getUser(ctx context.Context, userID int64) (*storage.Use
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
user, err = app.auth.Users.GetByID(ctx, userID)
|
||||
user, err = app.store.Users.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
21
backend/internal/context/context.go
Normal file
21
backend/internal/context/context.go
Normal file
@ -0,0 +1,21 @@
|
||||
package context
|
||||
|
||||
import "net/http"
|
||||
|
||||
type pageKey string
|
||||
|
||||
const PageCtx pageKey = "page"
|
||||
|
||||
type pagesizeKey string
|
||||
|
||||
const PagesizeCtx pagesizeKey = "pageSize"
|
||||
|
||||
type queryParamsKey string
|
||||
|
||||
const QueryParamsCtx queryParamsKey = "queryParams"
|
||||
|
||||
func getPageAndSizeFromContext(r *http.Request) (int, int) {
|
||||
pageNumber, _ := r.Context().Value(pageCtx).(int)
|
||||
pageSize, _ := r.Context().Value(pagesizeCtx).(int)
|
||||
return pageNumber, pageSize
|
||||
}
|
||||
@ -38,7 +38,7 @@ Table reciepts {
|
||||
groupid integer
|
||||
created_at timestamp
|
||||
updated_at timestamp
|
||||
data nvarchar
|
||||
data jsonb
|
||||
}
|
||||
|
||||
// Table imageOwnership {
|
||||
|
||||
18
backend/internal/storage/cache/cache.go
vendored
18
backend/internal/storage/cache/cache.go
vendored
@ -9,12 +9,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
UserExpTime = time.Minute
|
||||
RoleExpTime = time.Minute
|
||||
GroupExpTime = time.Minute
|
||||
UserGroupsExpTime = time.Minute
|
||||
ReceiptExpTime = time.Minute
|
||||
ReceiptImageExpTime = time.Minute
|
||||
UserExpTime = time.Minute
|
||||
RoleExpTime = time.Minute
|
||||
GroupExpTime = time.Minute
|
||||
UserGroupsExpTime = time.Minute
|
||||
ReceiptExpTime = time.Minute * 5
|
||||
ReceiptsQueryExpTime = time.Minute
|
||||
ReceiptImageExpTime = time.Minute
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
@ -43,6 +44,11 @@ type Storage struct {
|
||||
Set(ctx context.Context, receipt *storage.Receipt) error
|
||||
Delete(ctx context.Context, id int64)
|
||||
}
|
||||
ReceiptList interface {
|
||||
Get(ctx context.Context, id int64) ([]int64, error)
|
||||
Set(ctx context.Context, receiptidList ReceiptListKeyVal) error
|
||||
Delete(ctx context.Context, id int64)
|
||||
}
|
||||
ReceiptImage interface {
|
||||
Get(ctx context.Context, id int64) (*storage.Image, error)
|
||||
Set(ctx context.Context, image *storage.Image) error
|
||||
|
||||
59
backend/internal/storage/cache/redis-receipts.go
vendored
Normal file
59
backend/internal/storage/cache/redis-receipts.go
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type ReceiptListKeyVal struct {
|
||||
ID int64 `json:"id"`
|
||||
List []int64 `json:"list"`
|
||||
}
|
||||
|
||||
type ReceiptListStore struct {
|
||||
rdb *redis.Client
|
||||
}
|
||||
|
||||
func (s *ReceiptListStore) generateCacheKey(id int64) string {
|
||||
return fmt.Sprintf("receiptList-%d", id)
|
||||
}
|
||||
|
||||
func (s *ReceiptListStore) Get(ctx context.Context, id int64) ([]int64, error) {
|
||||
cacheKey := s.generateCacheKey(id)
|
||||
|
||||
data, err := s.rdb.Get(ctx, cacheKey).Result()
|
||||
if err == redis.Nil {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var receipts ReceiptListKeyVal
|
||||
if data != "" {
|
||||
err := json.Unmarshal([]byte(data), &receipts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return receipts.List, nil
|
||||
}
|
||||
|
||||
func (s *ReceiptListStore) Set(ctx context.Context, receiptidList ReceiptListKeyVal) error {
|
||||
cacheKey := s.generateCacheKey(receiptidList.ID)
|
||||
|
||||
json, err := json.Marshal(receiptidList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.rdb.Set(ctx, cacheKey, json, ReceiptsQueryExpTime).Err()
|
||||
}
|
||||
|
||||
func (s *ReceiptListStore) Delete(ctx context.Context, id int64) {
|
||||
cacheKey := s.generateCacheKey(id)
|
||||
s.rdb.Del(ctx, cacheKey)
|
||||
}
|
||||
@ -1,6 +1,15 @@
|
||||
package storage
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
l_context "git.ewellenr.ca/receipt_indexer/backend/internal/context"
|
||||
)
|
||||
|
||||
type Receipt struct {
|
||||
ID int64 `json:"id"`
|
||||
@ -19,3 +28,123 @@ type ReceiptData struct {
|
||||
Items map[string]float64 `json:"items"`
|
||||
// Currency string `json:"currency"`
|
||||
}
|
||||
|
||||
type ReceiptsQuery struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
SearchTerms []string `json:"search_terms"`
|
||||
MinTotal int `json:"min_amount"`
|
||||
MaxTotal int `json:"max_amount"`
|
||||
}
|
||||
|
||||
var (
|
||||
pageNumberKey = "page"
|
||||
pageSizeKey = "size"
|
||||
timeStartKey = "time_start"
|
||||
timeEndKey = "time_end"
|
||||
searchTermsKey = "search_terms"
|
||||
totalMinKey = "total_min"
|
||||
totalMaxKey = "total_max"
|
||||
)
|
||||
|
||||
const stringifiedQueryMaxLength = 524288000 // 500MiB
|
||||
|
||||
func newReceiptQuery() ReceiptsQuery {
|
||||
return ReceiptsQuery{
|
||||
UserID: -1,
|
||||
StartDate: time.Time{},
|
||||
EndDate: time.Time{},
|
||||
Page: 1,
|
||||
Size: 20,
|
||||
SearchTerms: nil,
|
||||
MinTotal: -1,
|
||||
MaxTotal: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func EncodeReceiptQuery(ctx context.Context, userID int64) (string, error) {
|
||||
|
||||
receiptQuery := newReceiptQuery()
|
||||
|
||||
receiptQuery.UserID = userID
|
||||
|
||||
var urlParams url.Values = nil
|
||||
if ctx.Value(l_context.QueryParamsCtx) != nil {
|
||||
urlParams = ctx.Value(l_context.QueryParamsCtx).(url.Values)
|
||||
}
|
||||
|
||||
if urlParams == nil {
|
||||
return "", ErrInvalidReceiptsQuery
|
||||
}
|
||||
|
||||
// Extract the search terms
|
||||
if urlParams.Has(pageNumberKey) {
|
||||
temp, err := strconv.Atoi(urlParams.Get(pageNumberKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
receiptQuery.Page = temp
|
||||
}
|
||||
|
||||
if urlParams.Has(pageSizeKey) {
|
||||
temp, err := strconv.Atoi(urlParams.Get(pageSizeKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
receiptQuery.Page = temp
|
||||
}
|
||||
|
||||
if urlParams.Has(timeStartKey) {
|
||||
temp, err := time.Parse(time.DateTime, urlParams.Get(timeStartKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
receiptQuery.StartDate = temp
|
||||
}
|
||||
|
||||
if urlParams.Has(timeEndKey) {
|
||||
temp, err := time.Parse(time.DateTime, urlParams.Get(timeEndKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
receiptQuery.EndDate = temp
|
||||
}
|
||||
|
||||
if urlParams.Has(totalMinKey) {
|
||||
temp, err := strconv.Atoi(urlParams.Get(totalMinKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
receiptQuery.MinTotal = temp
|
||||
}
|
||||
|
||||
if urlParams.Has(totalMaxKey) {
|
||||
temp, err := strconv.Atoi(urlParams.Get(totalMaxKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
receiptQuery.MaxTotal = temp
|
||||
}
|
||||
|
||||
if urlParams.Has(searchTermsKey) {
|
||||
temp := urlParams[searchTermsKey]
|
||||
receiptQuery.SearchTerms = temp
|
||||
}
|
||||
|
||||
// encoding the query into a string so that it can be used as a query term
|
||||
jsonEncoding, err := json.Marshal(receiptQuery)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out := base64.StdEncoding.EncodeToString(jsonEncoding)
|
||||
// convert userID to base64 encoding of the int (so bitwise of the int and then into base64)
|
||||
|
||||
if len(out) > stringifiedQueryMaxLength { // Makes the assumption that none of the other filters would cause the overflow of 500MiB
|
||||
return "", ErrSearchTermTooLong
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@ -8,10 +8,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("resource not found")
|
||||
ErrConflict = errors.New("resource already exists")
|
||||
ErrDuplicateEmail = errors.New("a user with that email already exists")
|
||||
ErrDuplicateUsername = errors.New("a user with that username already exists")
|
||||
ErrNotFound = errors.New("resource not found")
|
||||
ErrConflict = errors.New("resource already exists")
|
||||
ErrDuplicateEmail = errors.New("a user with that email already exists")
|
||||
ErrDuplicateUsername = errors.New("a user with that username already exists")
|
||||
ErrInvalidReceiptsQuery = errors.New("invalid receipts query")
|
||||
ErrSearchTermTooLong = errors.New("search term too long")
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
@ -62,6 +64,7 @@ type Storage struct {
|
||||
GetByID(ctx context.Context, id int64) (*Receipt, error)
|
||||
Create(ctx context.Context, receipt *Receipt) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
GetForUser(ctx context.Context, userid int64) ([]*Receipt, error)
|
||||
}
|
||||
Images interface {
|
||||
GetByID(context.Context, int64) (*Image, error)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user