Function bodies 1,000 total
handlers.SubscriptionHandler.CheckFeature method · go · L338-L365 (28 LOC)backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) CheckFeature(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value("userID").(string)
if !ok || userID == "" {
respondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
feature := r.URL.Query().Get("feature")
if feature == "" {
respondWithError(w, http.StatusBadRequest, "Feature parameter is required")
return
}
hasFeature, err := h.subscriptionService.HasFeature(r.Context(), userID, feature)
if err != nil {
logger.Error("Failed to check feature", map[string]interface{}{
"userID": userID,
"feature": feature,
"error": err.Error(),
})
respondWithError(w, http.StatusInternalServerError, "Failed to check feature access")
return
}
respondWithJSON(w, http.StatusOK, map[string]bool{
"hasAccess": hasFeature,
})
}handlers.NewWatermarkHandler function · go · L37-L44 (8 LOC)backend/internal/api/handlers/watermark.go
func NewWatermarkHandler(storageSvc WatermarkStorageService, photographerSvc PhotographerUpdater, subChecker SubscriptionFeatureChecker, originalBucket string) *WatermarkHandler {
return &WatermarkHandler{
storageSvc: storageSvc,
photographerSvc: photographerSvc,
subChecker: subChecker,
originalBucket: originalBucket,
}
}handlers.WatermarkHandler.requireFullWatermarkFeature method · go · L53-L90 (38 LOC)backend/internal/api/handlers/watermark.go
func (h *WatermarkHandler) requireFullWatermarkFeature(ctx context.Context, w http.ResponseWriter, userID string) bool {
if h.subChecker == nil {
logger.Error("subscriptionChecker is nil, denying feature access", map[string]interface{}{
"photographerId": userID,
"feature": "full_watermark",
})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]interface{}{
"error": "service_unavailable",
"message": "Unable to verify feature access. Please try again later.",
})
return false
}
hasFeature, err := h.subChecker.HasFeature(ctx, userID, "full_watermark")
if err != nil {
logger.Error("Failed to check full_watermark feature", map[string]interface{}{
"photographerId": userID,
"error": err.Error(),
})
respondError(w, errors.NewInternalServer("Failed to check feature access"))
return false
}
if !hasFeature {
w.Header().Set("Content-Type", "applicatiohandlers.WatermarkHandler.GetStatus method · go · L94-L138 (45 LOC)backend/internal/api/handlers/watermark.go
func (h *WatermarkHandler) GetStatus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID, ok := ctx.Value("userID").(string)
if !ok || userID == "" {
respondError(w, errors.NewUnauthorized("User ID not found"))
return
}
if !h.requireFullWatermarkFeature(ctx, w, userID) {
return
}
p, err := h.photographerSvc.GetByID(ctx, userID)
if err != nil {
logger.Error("Failed to get photographer for watermark status", map[string]interface{}{
"photographerId": userID,
"error": err.Error(),
})
respondError(w, errors.NewInternalServer("Failed to get watermark status"))
return
}
key := ""
viewURL := ""
if p != nil && p.WatermarkImageKey != "" {
key = p.WatermarkImageKey
url, err := h.storageSvc.GenerateDownloadURL(ctx, key, h.originalBucket, "watermark.png")
if err != nil {
logger.Error("Failed to generate watermark view URL", map[string]interface{}{
"photographerId": userID,
"key": key,
"error": err.handlers.WatermarkHandler.GetUploadURL method · go · L143-L176 (34 LOC)backend/internal/api/handlers/watermark.go
func (h *WatermarkHandler) GetUploadURL(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID, ok := ctx.Value("userID").(string)
if !ok || userID == "" {
respondError(w, errors.NewUnauthorized("User ID not found"))
return
}
if !h.requireFullWatermarkFeature(ctx, w, userID) {
return
}
key := watermarkKey(userID)
resp, err := h.storageSvc.GenerateUploadURL(ctx, storage.UploadURLRequest{
GalleryID: "watermarks",
PhotoID: userID,
FileName: "watermark.png",
MimeType: "image/png",
})
if err != nil {
logger.Error("Failed to generate watermark upload URL", map[string]interface{}{
"photographerId": userID,
"error": err.Error(),
})
respondError(w, errors.NewInternalServer("Failed to generate upload URL"))
return
}
respondJSON(w, http.StatusOK, map[string]string{
"uploadUrl": resp.URL,
"key": key,
})
}handlers.WatermarkHandler.ConfirmUpload method · go · L180-L211 (32 LOC)backend/internal/api/handlers/watermark.go
func (h *WatermarkHandler) ConfirmUpload(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID, ok := ctx.Value("userID").(string)
if !ok || userID == "" {
respondError(w, errors.NewUnauthorized("User ID not found"))
return
}
if !h.requireFullWatermarkFeature(ctx, w, userID) {
return
}
key := watermarkKey(userID)
if err := h.photographerSvc.UpdateWatermarkImageKey(ctx, userID, key); err != nil {
logger.Error("Failed to confirm watermark upload", map[string]interface{}{
"photographerId": userID,
"error": err.Error(),
})
respondError(w, errors.NewInternalServer("Failed to confirm watermark upload"))
return
}
logger.Info("Watermark image confirmed", map[string]interface{}{
"photographerId": userID,
"key": key,
})
respondJSON(w, http.StatusOK, map[string]string{
"key": key,
})
}handlers.WatermarkHandler.Delete method · go · L215-L268 (54 LOC)backend/internal/api/handlers/watermark.go
func (h *WatermarkHandler) Delete(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID, ok := ctx.Value("userID").(string)
if !ok || userID == "" {
respondError(w, errors.NewUnauthorized("User ID not found"))
return
}
if !h.requireFullWatermarkFeature(ctx, w, userID) {
return
}
// Check that photographer has a watermark image
p, err := h.photographerSvc.GetByID(ctx, userID)
if err != nil {
logger.Error("Failed to get photographer for watermark deletion", map[string]interface{}{
"photographerId": userID,
"error": err.Error(),
})
respondError(w, errors.NewInternalServer("Failed to get photographer"))
return
}
if p == nil || p.WatermarkImageKey == "" {
respondError(w, errors.NewNotFound("Watermark image"))
return
}
// Delete from S3
if err := h.storageSvc.DeleteObject(ctx, h.originalBucket, p.WatermarkImageKey); err != nil {
logger.Error("Failed to delete watermark image from S3", map[string]interface{}{
"photographerIRepobility · open methodology · https://repobility.com/research/
middleware.AuthMiddleware.VerifyPhotographerToken method · go · L28-L63 (36 LOC)backend/internal/api/middleware/auth.go
func (m *AuthMiddleware) VerifyPhotographerToken(ctx context.Context, req events.APIGatewayProxyRequest) (context.Context, error) {
// Get authorization header
authHeader := req.Headers["Authorization"]
if authHeader == "" {
authHeader = req.Headers["authorization"]
}
if authHeader == "" {
logger.Warn("Missing authorization header", nil)
return ctx, errors.NewUnauthorized("Missing authorization header")
}
// Extract token
token, err := cognitoAuth.ExtractToken(authHeader)
if err != nil {
return ctx, err
}
// Verify token
claims, err := m.authService.VerifyToken(ctx, token)
if err != nil {
return ctx, err
}
// Add user info to context
ctx = context.WithValue(ctx, "userID", claims.CognitoUsername)
ctx = context.WithValue(ctx, "email", claims.Email)
ctx = context.WithValue(ctx, "name", claims.Name)
// SECURITY FIX: Don't log PII (email) - only log anonymized identifiers
logger.Info("Photographer authenticated", map[string]interface{}{
"userId": claims.middleware.SessionMiddleware.VerifyClientSession method · go · L80-L132 (53 LOC)backend/internal/api/middleware/auth.go
func (m *SessionMiddleware) VerifyClientSession(ctx context.Context, req events.APIGatewayProxyRequest) (context.Context, error) {
var token string
// First, try to get token from httpOnly cookie (more secure)
cookieHeader := req.Headers["Cookie"]
if cookieHeader == "" {
cookieHeader = req.Headers["cookie"]
}
if cookieHeader != "" {
if cookieToken, ok := security.GetCookieValue(cookieHeader, security.SessionCookieName); ok && cookieToken != "" {
token = cookieToken
logger.Info("Using session token from cookie", nil)
}
}
// Fall back to Authorization header for backwards compatibility
if token == "" {
authHeader := req.Headers["Authorization"]
if authHeader == "" {
authHeader = req.Headers["authorization"]
}
if authHeader == "" {
logger.Warn("Missing session token", nil)
return ctx, errors.NewUnauthorized("Missing session token")
}
// Extract token (format: "Bearer <token>")
var err error
token, err = cognitoAuth.ExtractToken(authHeadermiddleware.ValidateOrigin function · go · L12-L32 (21 LOC)backend/internal/api/middleware/cors.go
func ValidateOrigin(requestOrigin string, allowedOrigins []string) string {
if requestOrigin == "" {
return ""
}
for _, allowed := range allowedOrigins {
// Exact match
if allowed == requestOrigin {
return requestOrigin
}
// Wildcard subdomain match (e.g., "*.example.com")
if strings.HasPrefix(allowed, "*.") {
suffix := allowed[1:] // Remove the "*" to get ".example.com"
if strings.HasSuffix(requestOrigin, suffix) {
return requestOrigin
}
}
}
return ""
}middleware.ParseAllowedOrigins function · go · L36-L50 (15 LOC)backend/internal/api/middleware/cors.go
func ParseAllowedOrigins(allowedOriginsStr string) []string {
if allowedOriginsStr == "" {
return []string{}
}
origins := strings.Split(allowedOriginsStr, ",")
result := make([]string, 0, len(origins))
for _, origin := range origins {
trimmed := strings.TrimSpace(origin)
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}middleware.AddCORSHeaders function · go · L55-L75 (21 LOC)backend/internal/api/middleware/cors.go
func AddCORSHeaders(response events.APIGatewayProxyResponse, requestOrigin string, allowedOrigins []string) events.APIGatewayProxyResponse {
if response.Headers == nil {
response.Headers = make(map[string]string)
}
// Validate the origin against the whitelist
validatedOrigin := ValidateOrigin(requestOrigin, allowedOrigins)
if validatedOrigin != "" {
response.Headers["Access-Control-Allow-Origin"] = validatedOrigin
response.Headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.Headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Requested-With"
response.Headers["Access-Control-Max-Age"] = "3600"
response.Headers["Access-Control-Allow-Credentials"] = "true"
// Vary header is important when the response depends on the Origin header
response.Headers["Vary"] = "Origin"
}
// If origin is not valid, don't set CORS headers - browser will block the request
return response
}middleware.AddCORSHeadersLegacy function · go · L80-L91 (12 LOC)backend/internal/api/middleware/cors.go
func AddCORSHeadersLegacy(response events.APIGatewayProxyResponse, allowedOrigins string) events.APIGatewayProxyResponse {
if response.Headers == nil {
response.Headers = make(map[string]string)
}
response.Headers["Access-Control-Allow-Origin"] = allowedOrigins
response.Headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.Headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Requested-With"
response.Headers["Access-Control-Max-Age"] = "3600"
return response
}middleware.HandlePreflight function · go · L95-L115 (21 LOC)backend/internal/api/middleware/cors.go
func HandlePreflight(requestOrigin string, allowedOrigins []string) events.APIGatewayProxyResponse {
validatedOrigin := ValidateOrigin(requestOrigin, allowedOrigins)
headers := map[string]string{
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
"Access-Control-Max-Age": "3600",
"Vary": "Origin",
}
if validatedOrigin != "" {
headers["Access-Control-Allow-Origin"] = validatedOrigin
headers["Access-Control-Allow-Credentials"] = "true"
}
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: headers,
Body: "",
}
}middleware.HandlePreflightLegacy function · go · L119-L130 (12 LOC)backend/internal/api/middleware/cors.go
func HandlePreflightLegacy(allowedOrigins string) events.APIGatewayProxyResponse {
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
"Access-Control-Allow-Origin": allowedOrigins,
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
"Access-Control-Max-Age": "3600",
},
Body: "",
}
}Repobility · code-quality intelligence platform · https://repobility.com
middleware.NewDomainMiddleware function · go · L21-L26 (6 LOC)backend/internal/api/middleware/domain.go
func NewDomainMiddleware(domainService *customdomain.Service, baseDomain string) *DomainMiddleware {
return &DomainMiddleware{
domainService: domainService,
baseDomain: baseDomain,
}
}middleware.DomainMiddleware.ResolvePhotographer method · go · L31-L79 (49 LOC)backend/internal/api/middleware/domain.go
func (m *DomainMiddleware) ResolvePhotographer(ctx context.Context, req events.APIGatewayProxyRequest) (context.Context, error) {
// Get host from headers (try multiple header names for compatibility)
host := m.getHost(req)
if host == "" {
logger.Debug("No host header found", nil)
return ctx, nil
}
// Clean up the host
host = strings.ToLower(strings.TrimSpace(host))
host = strings.Split(host, ":")[0] // Remove port if present
logger.Debug("Resolving photographer from host", map[string]interface{}{
"host": host,
"baseDomain": m.baseDomain,
})
// Check if this is the main domain (not a custom domain)
if m.isMainDomain(host) {
logger.Debug("Main domain detected, skipping photographer resolution", nil)
return ctx, nil
}
// Try to resolve photographer from the host
p, err := m.domainService.ResolvePhotographerByHost(ctx, host)
if err != nil {
if err == photographer.ErrNotFound {
logger.Debug("No photographer found for host", map[string]interface{}{"middleware.DomainMiddleware.getHost method · go · L82-L100 (19 LOC)backend/internal/api/middleware/domain.go
func (m *DomainMiddleware) getHost(req events.APIGatewayProxyRequest) string {
// Check X-Forwarded-Host first (set by CloudFront/Lambda@Edge)
if host := req.Headers["X-Forwarded-Host"]; host != "" {
return host
}
if host := req.Headers["x-forwarded-host"]; host != "" {
return host
}
// Fall back to Host header
if host := req.Headers["Host"]; host != "" {
return host
}
if host := req.Headers["host"]; host != "" {
return host
}
return ""
}middleware.DomainMiddleware.isMainDomain method · go · L103-L130 (28 LOC)backend/internal/api/middleware/domain.go
func (m *DomainMiddleware) isMainDomain(host string) bool {
// Exact match
if host == m.baseDomain {
return true
}
// www subdomain
if host == "www."+m.baseDomain {
return true
}
// Localhost for development
if strings.HasPrefix(host, "localhost") {
return true
}
// API Gateway domains
if strings.Contains(host, ".execute-api.") {
return true
}
// CloudFront domains
if strings.HasSuffix(host, ".cloudfront.net") {
return true
}
return false
}api.NewMiddlewareFactory function · go · L23-L28 (6 LOC)backend/internal/api/middleware_factory.go
func NewMiddlewareFactory(allowedOrigins []string, jwtValidator JWTValidator) *MiddlewareFactory {
return &MiddlewareFactory{
allowedOrigins: allowedOrigins,
jwtValidator: jwtValidator,
}
}api.MiddlewareFactory.CORS method · go · L31-L82 (52 LOC)backend/internal/api/middleware_factory.go
func (f *MiddlewareFactory) CORS() Middleware {
return func(next Handler) Handler {
return func(req *Request) (*Response, error) {
origin := req.Headers["origin"]
if origin == "" {
origin = req.Headers["Origin"]
}
// Check if origin is allowed
allowed := false
for _, o := range f.allowedOrigins {
if o == "*" || o == origin {
allowed = true
break
}
}
// Handle preflight
if req.Method == http.MethodOptions {
headers := map[string]string{
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400",
}
if allowed {
headers["Access-Control-Allow-Origin"] = origin
headers["Access-Control-Allow-Credentials"] = "true"
}
return &Response{
StatusCode: http.StatusNoContent,
Headers: headers,
}, nil
}
// Process request and add CORS headers to response
resp, err := napi.MiddlewareFactory.Auth method · go · L85-L122 (38 LOC)backend/internal/api/middleware_factory.go
func (f *MiddlewareFactory) Auth() Middleware {
return func(next Handler) Handler {
return func(req *Request) (*Response, error) {
authHeader := req.Headers["authorization"]
if authHeader == "" {
authHeader = req.Headers["Authorization"]
}
if authHeader == "" {
return Unauthorized("Authorization header required"), nil
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
return Unauthorized("Invalid authorization header format"), nil
}
token := parts[1]
claims, err := f.jwtValidator.ValidateToken(token)
if err != nil {
return Unauthorized("Invalid or expired token"), nil
}
// Add claims to request context
if req.PathParams == nil {
req.PathParams = make(map[string]string)
}
if userID, ok := claims["sub"].(string); ok {
req.PathParams["__userId"] = userID
}
if email, ok := claims["email"].(string); ok {
req.PathParams["__email"] = email
}
api.MiddlewareFactory.Logging method · go · L125-L149 (25 LOC)backend/internal/api/middleware_factory.go
func (f *MiddlewareFactory) Logging() Middleware {
return func(next Handler) Handler {
return func(req *Request) (*Response, error) {
start := time.Now()
resp, err := next(req)
duration := time.Since(start)
status := 0
if resp != nil {
status = resp.StatusCode
}
log.Printf("[%s] %s %s - %d (%v)",
req.Method,
req.Path,
getClientIP(req),
status,
duration,
)
return resp, err
}
}
}All rows above produced by Repobility · https://repobility.com
api.MiddlewareFactory.Recovery method · go · L152-L165 (14 LOC)backend/internal/api/middleware_factory.go
func (f *MiddlewareFactory) Recovery() Middleware {
return func(next Handler) Handler {
return func(req *Request) (resp *Response, err error) {
defer func() {
if r := recover(); r != nil {
log.Printf("[PANIC] %s %s: %v", req.Method, req.Path, r)
resp = InternalError("Internal server error")
err = nil
}
}()
return next(req)
}
}
}api.MiddlewareFactory.RateLimit method · go · L168-L177 (10 LOC)backend/internal/api/middleware_factory.go
func (f *MiddlewareFactory) RateLimit(requestsPerMinute int) Middleware {
// Note: In production, use a distributed rate limiter with Redis
return func(next Handler) Handler {
return func(req *Request) (*Response, error) {
// For Lambda, rate limiting should be done at API Gateway level
// This is a placeholder for local development
return next(req)
}
}
}api.getClientIP function · go · L180-L195 (16 LOC)backend/internal/api/middleware_factory.go
func getClientIP(req *Request) string {
// Check X-Forwarded-For first (common for proxied requests)
if xff := req.Headers["X-Forwarded-For"]; xff != "" {
parts := strings.Split(xff, ",")
if len(parts) > 0 {
return strings.TrimSpace(parts[0])
}
}
if xff := req.Headers["x-forwarded-for"]; xff != "" {
parts := strings.Split(xff, ",")
if len(parts) > 0 {
return strings.TrimSpace(parts[0])
}
}
return "unknown"
}api.Chain function · go · L198-L205 (8 LOC)backend/internal/api/middleware_factory.go
func Chain(middlewares ...Middleware) Middleware {
return func(final Handler) Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
final = middlewares[i](final)
}
return final
}
}api.ConditionalMiddleware function · go · L208-L217 (10 LOC)backend/internal/api/middleware_factory.go
func ConditionalMiddleware(condition func(*Request) bool, middleware Middleware) Middleware {
return func(next Handler) Handler {
return func(req *Request) (*Response, error) {
if condition(req) {
return middleware(next)(req)
}
return next(req)
}
}
}api.ExcludePaths function · go · L227-L238 (12 LOC)backend/internal/api/middleware_factory.go
func ExcludePaths(middleware Middleware, paths ...string) Middleware {
return func(next Handler) Handler {
return func(req *Request) (*Response, error) {
for _, p := range paths {
if req.Path == p {
return next(req)
}
}
return middleware(next)(req)
}
}
}middleware.TierGateMiddleware.RequireTier method · go · L43-L79 (37 LOC)backend/internal/api/middleware/tier_gate.go
func (m *TierGateMiddleware) RequireTier(minTier subscription.Tier) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value("userID").(string)
if !ok || userID == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
meets, err := m.subscriptionService.MeetsTierRequirement(r.Context(), userID, minTier)
if err != nil {
http.Error(w, "Failed to check subscription", http.StatusInternalServerError)
return
}
if !meets {
sub, _ := m.subscriptionService.GetSubscription(r.Context(), userID)
currentTier := subscription.TierFree
if sub != nil {
currentTier = sub.Tier
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusPaymentRequired)
json.NewEncoder(w).Encode(TierGateResponse{
Error: "upgrade_required",
Message: "This feature requiremiddleware.TierGateMiddleware.RequireFeature method · go · L82-L111 (30 LOC)backend/internal/api/middleware/tier_gate.go
func (m *TierGateMiddleware) RequireFeature(feature string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value("userID").(string)
if !ok || userID == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
hasFeature, err := m.subscriptionService.HasFeature(r.Context(), userID, feature)
if err != nil {
http.Error(w, "Failed to check feature access", http.StatusInternalServerError)
return
}
if !hasFeature {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusPaymentRequired)
json.NewEncoder(w).Encode(map[string]interface{}{
"error": "feature_not_available",
"message": "Your subscription does not include this feature",
"feature": feature,
})
return
}
next.ServeHTTP(w, r)
})
}
}Open data scored by Repobility · https://repobility.com
middleware.TierGateMiddleware.CheckStorageLimit method · go · L124-L150 (27 LOC)backend/internal/api/middleware/tier_gate.go
func (m *TierGateMiddleware) CheckStorageLimit(ctx context.Context, userID string, currentStorageUsed int64) (bool, *StorageLimitResponse) {
withinLimit, limits, err := m.subscriptionService.CheckStorageLimit(ctx, userID, currentStorageUsed)
if err != nil {
return false, &StorageLimitResponse{
Error: "check_failed",
Message: "Failed to check storage limit",
}
}
if !withinLimit && limits != nil {
sub, _ := m.subscriptionService.GetSubscription(ctx, userID)
currentTier := "free"
if sub != nil {
currentTier = string(sub.Tier)
}
return false, &StorageLimitResponse{
Error: "storage_limit_exceeded",
Message: "You have reached your storage limit. Please upgrade your plan.",
StorageUsed: currentStorageUsed,
StorageLimit: limits.StorageBytes,
CurrentTier: currentTier,
}
}
return true, nil
}middleware.TierGateMiddleware.CheckStorageLimitWithUpload method · go · L154-L180 (27 LOC)backend/internal/api/middleware/tier_gate.go
func (m *TierGateMiddleware) CheckStorageLimitWithUpload(ctx context.Context, userID string, currentStorageUsed int64, uploadSizeBytes int64) (bool, *StorageLimitResponse) {
withinLimit, limits, err := m.subscriptionService.CheckStorageLimitWithUpload(ctx, userID, currentStorageUsed, uploadSizeBytes)
if err != nil {
return false, &StorageLimitResponse{
Error: "check_failed",
Message: "Failed to check storage limit",
}
}
if !withinLimit && limits != nil {
sub, _ := m.subscriptionService.GetSubscription(ctx, userID)
currentTier := "free"
if sub != nil {
currentTier = string(sub.Tier)
}
return false, &StorageLimitResponse{
Error: "storage_limit_exceeded",
Message: "This upload would exceed your storage limit. Please upgrade your plan.",
StorageUsed: currentStorageUsed,
StorageLimit: limits.StorageBytes,
CurrentTier: currentTier,
}
}
return true, nil
}middleware.TierGateMiddleware.CheckGalleryLimit method · go · L193-L219 (27 LOC)backend/internal/api/middleware/tier_gate.go
func (m *TierGateMiddleware) CheckGalleryLimit(ctx context.Context, userID string, currentGalleryCount int) (bool, *GalleryLimitResponse) {
withinLimit, limits, err := m.subscriptionService.CheckGalleryLimit(ctx, userID, currentGalleryCount)
if err != nil {
return false, &GalleryLimitResponse{
Error: "check_failed",
Message: "Failed to check gallery limit",
}
}
if !withinLimit && limits != nil {
sub, _ := m.subscriptionService.GetSubscription(ctx, userID)
currentTier := "free"
if sub != nil {
currentTier = string(sub.Tier)
}
return false, &GalleryLimitResponse{
Error: "gallery_limit_exceeded",
Message: "You have reached your gallery limit. Please upgrade your plan.",
GalleryCount: currentGalleryCount,
GalleryLimit: limits.MaxGalleries,
CurrentTier: currentTier,
}
}
return true, nil
}middleware.TierGateMiddleware.CheckPhotosPerGalleryLimit method · go · L232-L258 (27 LOC)backend/internal/api/middleware/tier_gate.go
func (m *TierGateMiddleware) CheckPhotosPerGalleryLimit(ctx context.Context, userID string, currentPhotoCount int) (bool, *PhotosPerGalleryLimitResponse) {
withinLimit, limits, err := m.subscriptionService.CheckPhotosPerGalleryLimit(ctx, userID, currentPhotoCount)
if err != nil {
return false, &PhotosPerGalleryLimitResponse{
Error: "check_failed",
Message: "Failed to check photos per gallery limit",
}
}
if !withinLimit && limits != nil {
sub, _ := m.subscriptionService.GetSubscription(ctx, userID)
currentTier := "free"
if sub != nil {
currentTier = string(sub.Tier)
}
return false, &PhotosPerGalleryLimitResponse{
Error: "photo_limit_exceeded",
Message: "You have reached your photo limit for this gallery. Please upgrade your plan.",
PhotoCount: currentPhotoCount,
PhotoLimit: limits.MaxPhotosPerGallery,
CurrentTier: currentTier,
}
}
return true, nil
}api.NewRouter function · go · L52-L63 (12 LOC)backend/internal/api/router.go
func NewRouter() *Router {
return &Router{
routes: make([]Route, 0),
middlewares: make([]Middleware, 0),
notFound: func(req *Request) (*Response, error) {
return &Response{
StatusCode: http.StatusNotFound,
Body: map[string]string{"message": "Not found"},
}, nil
},
}
}api.Router.Handle method · go · L72-L79 (8 LOC)backend/internal/api/router.go
func (r *Router) Handle(method, pattern string, handler Handler) *Router {
r.routes = append(r.routes, Route{
Method: method,
Pattern: pattern,
Handler: handler,
})
return r
}api.Router.matchRoute method · go · L108-L127 (20 LOC)backend/internal/api/router.go
func (r *Router) matchRoute(pattern, path string) (bool, map[string]string) {
patternParts := strings.Split(strings.Trim(pattern, "/"), "/")
pathParts := strings.Split(strings.Trim(path, "/"), "/")
if len(patternParts) != len(pathParts) {
return false, nil
}
params := make(map[string]string)
for i, part := range patternParts {
if strings.HasPrefix(part, "{") && strings.HasSuffix(part, "}") {
paramName := part[1 : len(part)-1]
params[paramName] = pathParts[i]
} else if part != pathParts[i] {
return false, nil
}
}
return true, params
}api.Router.Route method · go · L130-L144 (15 LOC)backend/internal/api/router.go
func (r *Router) Route(req *Request) (*Response, error) {
for _, route := range r.routes {
if route.Method != req.Method {
continue
}
if match, params := r.matchRoute(route.Pattern, req.Path); match {
req.PathParams = params
handler := r.applyMiddleware(route.Handler)
return handler(req)
}
}
return r.notFound(req)
}Repobility · open methodology · https://repobility.com/research/
api.Router.applyMiddleware method · go · L147-L152 (6 LOC)backend/internal/api/router.go
func (r *Router) applyMiddleware(handler Handler) Handler {
for i := len(r.middlewares) - 1; i >= 0; i-- {
handler = r.middlewares[i](handler)
}
return handler
}api.Router.HandleLambda method · go · L155-L200 (46 LOC)backend/internal/api/router.go
func (r *Router) HandleLambda(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
req := &Request{
Method: event.HTTPMethod,
Path: event.Path,
PathParams: event.PathParameters,
QueryParams: event.QueryStringParameters,
Headers: event.Headers,
Body: event.Body,
Context: ctx,
}
resp, err := r.Route(req)
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusInternalServerError,
Body: `{"message": "Internal server error"}`,
Headers: map[string]string{
"Content-Type": "application/json",
},
}, nil
}
body, err := json.Marshal(resp.Body)
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusInternalServerError,
Body: `{"message": "Failed to serialize response"}`,
Headers: map[string]string{
"Content-Type": "application/json",
},
}, nil
}
headers := map[string]string{
"Content-Type": "applicatapi.JSON function · go · L203-L211 (9 LOC)backend/internal/api/router.go
func JSON(status int, body interface{}) *Response {
return &Response{
StatusCode: status,
Body: body,
Headers: map[string]string{
"Content-Type": "application/json",
},
}
}config.Load function · go · L59-L96 (38 LOC)backend/internal/config/config.go
func Load() *Config {
return &Config{
AWSRegion: getEnv("AWS_REGION", "us-east-1"),
DynamoDBTablePrefix: getEnv("DYNAMODB_TABLE_PREFIX", "photographer-gallery"),
S3BucketOriginal: getEnv("S3_BUCKET_ORIGINAL", ""),
S3BucketOptimized: getEnv("S3_BUCKET_OPTIMIZED", ""),
S3BucketThumbnail: getEnv("S3_BUCKET_THUMBNAIL", ""),
CloudFrontDomain: getEnv("CLOUDFRONT_DOMAIN", ""),
CloudFrontKeyPairID: getEnv("CLOUDFRONT_KEY_PAIR_ID", ""),
CloudFrontKeyPath: getEnv("CLOUDFRONT_KEY_PATH", ""),
CognitoUserPoolID: getEnv("COGNITO_USER_POOL_ID", ""),
CognitoClientID: getEnv("COGNITO_CLIENT_ID", ""),
CognitoRegion: getEnv("COGNITO_REGION", "us-east-1"),
APIStage: getEnv("API_STAGE", "dev"),
AllowedOrigins: getEnv("ALLOWED_ORIGINS", "*"),
SQSQueueURL: getEnv("SQS_QUEUE_URL", ""),
ZipSQSQueueURL: getEnv("ZIP_SQS_QUEUE_URL", ""),
SessionTTLHours: getEnvAsInt("SESSION_TTL_HOURS", 24),
CookieDomain: config.Config.GetPriceToTierMap method · go · L99-L122 (24 LOC)backend/internal/config/config.go
func (c *Config) GetPriceToTierMap() map[string]string {
m := make(map[string]string)
// Monthly prices (3-tier system: Starter, Pro, Studio)
if c.StripePriceStarterMonthly != "" {
m[c.StripePriceStarterMonthly] = "starter"
}
if c.StripePriceProMonthly != "" {
m[c.StripePriceProMonthly] = "pro"
}
if c.StripePriceStudioMonthly != "" {
m[c.StripePriceStudioMonthly] = "studio"
}
// Yearly prices
if c.StripePriceStarterYearly != "" {
m[c.StripePriceStarterYearly] = "starter"
}
if c.StripePriceProYearly != "" {
m[c.StripePriceProYearly] = "pro"
}
if c.StripePriceStudioYearly != "" {
m[c.StripePriceStudioYearly] = "studio"
}
return m
}config.getEnv function · go · L124-L130 (7 LOC)backend/internal/config/config.go
func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
return value
}config.getEnvAsInt function · go · L132-L141 (10 LOC)backend/internal/config/config.go
func getEnvAsInt(key string, defaultValue int) int {
valueStr := os.Getenv(key)
if valueStr == "" {
return defaultValue
}
// Simple conversion, in production use strconv.Atoi with error handling
value := defaultValue
_, _ = fmt.Sscanf(valueStr, "%d", &value)
return value
}config.NewProcessorConfigBuilder function · go · L26-L31 (6 LOC)backend/internal/config/processor_config.go
func NewProcessorConfigBuilder() *ProcessorConfigBuilder {
return &ProcessorConfigBuilder{
config: &ProcessorConfig{},
errors: []string{},
}
}Repobility · code-quality intelligence platform · https://repobility.com
config.ProcessorConfigBuilder.FromEnvironment method · go · L34-L42 (9 LOC)backend/internal/config/processor_config.go
func (b *ProcessorConfigBuilder) FromEnvironment() *ProcessorConfigBuilder {
b.config.AWSRegion = os.Getenv("AWS_REGION_NAME")
b.config.DynamoDBTablePrefix = os.Getenv("DYNAMODB_TABLE_PREFIX")
b.config.S3BucketOriginal = os.Getenv("S3_BUCKET_ORIGINAL")
b.config.S3BucketOptimized = os.Getenv("S3_BUCKET_OPTIMIZED")
b.config.S3BucketThumbnail = os.Getenv("S3_BUCKET_THUMBNAIL")
b.config.APIStage = os.Getenv("STAGE")
return b
}config.ProcessorConfigBuilder.WithS3Buckets method · go · L57-L62 (6 LOC)backend/internal/config/processor_config.go
func (b *ProcessorConfigBuilder) WithS3Buckets(original, optimized, thumbnail string) *ProcessorConfigBuilder {
b.config.S3BucketOriginal = original
b.config.S3BucketOptimized = optimized
b.config.S3BucketThumbnail = thumbnail
return b
}config.ProcessorConfigBuilder.Build method · go · L71-L79 (9 LOC)backend/internal/config/processor_config.go
func (b *ProcessorConfigBuilder) Build() (*ProcessorConfig, error) {
b.validate()
if len(b.errors) > 0 {
return nil, fmt.Errorf("configuration errors: %v", b.errors)
}
return b.config, nil
}