← back to jearl4__PhotoGallery

Function bodies 1,000 total

All specs Real LLM only Function bodies
handlers.GalleryHandler.SetExpiration method · go · L723-L778 (56 LOC)
backend/internal/api/handlers/gallery.go
func (h *GalleryHandler) SetExpiration(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	// Get photographer ID from context (set by auth middleware)
	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// SECURITY: Verify ownership before allowing expiration change (IDOR prevention)
	existingGallery, err := h.galleryService.GetByID(ctx, galleryID)
	if err != nil {
		respondError(w, err)
		return
	}
	if existingGallery.PhotographerID != photographerID {
		logger.Warn("IDOR attempt: user tried to set expiration on gallery they don't own", map[string]interface{}{
			"userId":    photographerID,
			"galleryId": galleryID,
			"ownerId":   existingGallery.PhotographerID,
		})
		respondError(w, errors.NewForbidden("You do not have permission to modify this gallery"))
		return
	}

	var req SetExpirationRequest
	if err := json.NewDecoder
handlers.respondError function · go · L787-L802 (16 LOC)
backend/internal/api/handlers/gallery.go
func respondError(w http.ResponseWriter, err error) {
	if appErr, ok := err.(*errors.AppError); ok {
		// Sanitize the error message before sending to client
		sanitizedErr := security.SanitizeForClient(appErr.Code, appErr.Message)
		respondJSON(w, appErr.Code, map[string]string{
			"error": sanitizedErr.Message,
		})
	} else {
		logger.Error("Unexpected error", map[string]interface{}{"error": err.Error()})
		// Use sanitizer for generic errors too
		sanitizedErr := security.SanitizeForClient(http.StatusInternalServerError, err.Error())
		respondJSON(w, http.StatusInternalServerError, map[string]string{
			"error": sanitizedErr.Message,
		})
	}
}
handlers.getURLParam function · go · L804-L816 (13 LOC)
backend/internal/api/handlers/gallery.go
func getURLParam(r *http.Request, param string) string {
	// This is a placeholder - actual implementation depends on router
	// For API Gateway Lambda, you'd get this from the path parameters
	val := r.Context().Value(param)
	if val == nil {
		return ""
	}
	str, ok := val.(string)
	if !ok {
		return ""
	}
	return str
}
handlers.NewPhotoHandler function · go · L41-L48 (8 LOC)
backend/internal/api/handlers/photo.go
func NewPhotoHandler(photoService PhotoService, tierGate *middleware.TierGateMiddleware, photographerGetter PhotographerGetter, galleryGetter GalleryGetter) *PhotoHandler {
	return &PhotoHandler{
		photoService:       photoService,
		tierGate:           tierGate,
		photographerGetter: photographerGetter,
		galleryGetter:      galleryGetter,
	}
}
handlers.PhotoHandler.GetUploadURL method · go · L57-L150 (94 LOC)
backend/internal/api/handlers/photo.go
func (h *PhotoHandler) GetUploadURL(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	// Get photographer ID from context (set by auth middleware)
	photographerID, ok := ctx.Value("userID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	var req GetUploadURLRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, errors.NewBadRequest("Invalid request body"))
		return
	}

	if req.FileName == "" {
		respondError(w, errors.NewBadRequest("fileName is required"))
		return
	}
	if req.MimeType == "" {
		respondError(w, errors.NewBadRequest("mimeType is required"))
		return
	}

	// Check storage limit before generating upload URL
	if h.tierGate != nil && h.photographerGetter != nil {
		photographer, err := h.photographerGetter.GetByID(ctx, photographerID)
		if err != nil {
			logger.Error("Failed to get photographer for storage limit check", map[string]interface{}{
		
handlers.PhotoHandler.ListPhotos method · go · L153-L172 (20 LOC)
backend/internal/api/handlers/photo.go
func (h *PhotoHandler) ListPhotos(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	limit := 50 // default
	var lastKey map[string]interface{}

	photos, nextKey, err := h.photoService.ListByGallery(ctx, galleryID, limit, lastKey)
	if err != nil {
		respondError(w, err)
		return
	}

	response := map[string]interface{}{
		"photos":  photos,
		"lastKey": nextKey,
	}

	respondJSON(w, http.StatusOK, response)
}
handlers.PhotoHandler.DeletePhoto method · go · L175-L226 (52 LOC)
backend/internal/api/handlers/photo.go
func (h *PhotoHandler) DeletePhoto(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	photoID := getURLParam(r, "photoId")

	// Get photographer ID from context (set by auth middleware)
	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// SECURITY: Verify ownership before allowing delete (IDOR prevention)
	// galleryGetter is required for ownership verification - fail closed if not configured
	if h.galleryGetter == nil {
		logger.Error("CRITICAL: galleryGetter is nil - IDOR check cannot be performed", map[string]interface{}{
			"photoId": photoID,
			"userId":  photographerID,
		})
		respondError(w, errors.NewInternalServer("Service configuration error"))
		return
	}

	// First, get the photo to find its gallery
	photoObj, err := h.photoService.GetByID(ctx, photoID)
	if err != nil {
		respondError(w, err)
		return
	}

	// Then get the gallery to check ownership
	
If a scraper extracted this row, it came from Repobility (https://repobility.com)
handlers.PhotoHandler.GetFavorites method · go · L229-L242 (14 LOC)
backend/internal/api/handlers/photo.go
func (h *PhotoHandler) GetFavorites(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	favorites, err := h.photoService.ListFavoritesByGallery(ctx, galleryID)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"favorites": favorites,
	})
}
handlers.PhotoHandler.UpdatePhotoSet method · go · L252-L326 (75 LOC)
backend/internal/api/handlers/photo.go
func (h *PhotoHandler) UpdatePhotoSet(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "galleryId")
	photoID := getURLParam(r, "photoId")

	// Get photographer ID from context (set by auth middleware)
	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// SECURITY: Verify gallery ownership (IDOR prevention)
	if h.galleryGetter == nil {
		logger.Error("CRITICAL: galleryGetter is nil - IDOR check cannot be performed", map[string]interface{}{
			"photoId": photoID,
			"userId":  photographerID,
		})
		respondError(w, errors.NewInternalServer("Service configuration error"))
		return
	}

	gallery, err := h.galleryGetter.GetByID(ctx, galleryID)
	if err != nil {
		respondError(w, err)
		return
	}
	if gallery == nil || gallery.PhotographerID != photographerID {
		logger.Warn("IDOR attempt: user tried to update photo section for gallery they do
handlers.PhotoHandler.GetPhotosBySet method · go · L329-L366 (38 LOC)
backend/internal/api/handlers/photo.go
func (h *PhotoHandler) GetPhotosBySet(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	setID := getURLParam(r, "setId")

	// IDOR protection: verify user is authenticated
	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found in context"))
		return
	}

	photos, err := h.photoService.ListBySet(ctx, setID)
	if err != nil {
		respondError(w, err)
		return
	}

	// Verify ownership via the first photo's gallery
	if len(photos) > 0 {
		gallery, err := h.galleryGetter.GetByID(ctx, photos[0].GalleryID)
		if err != nil {
			respondError(w, err)
			return
		}
		if gallery == nil || gallery.PhotographerID != photographerID {
			logger.Warn("IDOR attempt: user tried to get photos for set they don't own", map[string]interface{}{
				"userId": photographerID,
				"setId":  setID,
			})
			respondError(w, errors.NewForbidden("You do not have permission to view this set"))
			return
		}
	}

	respon
handlers.NewPhotoSetHandler function · go · L30-L35 (6 LOC)
backend/internal/api/handlers/photo_set.go
func NewPhotoSetHandler(photoSetService PhotoSetServiceInterface, galleryGetter GalleryGetter) *PhotoSetHandler {
	return &PhotoSetHandler{
		photoSetService: photoSetService,
		galleryGetter:   galleryGetter,
	}
}
handlers.PhotoSetHandler.CreatePhotoSet method · go · L44-L96 (53 LOC)
backend/internal/api/handlers/photo_set.go
func (h *PhotoSetHandler) CreatePhotoSet(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "galleryId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// SECURITY: Verify gallery ownership (IDOR prevention)
	if h.galleryGetter == nil {
		logger.Error("CRITICAL: galleryGetter is nil - IDOR check cannot be performed", map[string]interface{}{
			"galleryId": galleryID,
			"userId":    photographerID,
		})
		respondError(w, errors.NewInternalServer("Service configuration error"))
		return
	}

	gallery, err := h.galleryGetter.GetByID(ctx, galleryID)
	if err != nil {
		respondError(w, err)
		return
	}
	if gallery == nil || gallery.PhotographerID != photographerID {
		logger.Warn("IDOR attempt: user tried to create photo set in gallery they don't own", map[string]interface{}{
			"userId":    photographerID,
			"galleryId": galleryID,
		}
handlers.PhotoSetHandler.ListPhotoSets method · go · L99-L136 (38 LOC)
backend/internal/api/handlers/photo_set.go
func (h *PhotoSetHandler) ListPhotoSets(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "galleryId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// SECURITY: Verify gallery ownership (IDOR prevention)
	if h.galleryGetter != nil {
		gallery, err := h.galleryGetter.GetByID(ctx, galleryID)
		if err != nil {
			respondError(w, err)
			return
		}
		if gallery == nil || gallery.PhotographerID != photographerID {
			logger.Warn("IDOR attempt: user tried to list photo sets from gallery they don't own", map[string]interface{}{
				"userId":    photographerID,
				"galleryId": galleryID,
			})
			respondError(w, errors.NewForbidden("You do not have permission to view this gallery"))
			return
		}
	}

	sets, err := h.photoSetService.ListByGallery(ctx, galleryID)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.S
handlers.PhotoSetHandler.UpdatePhotoSet method · go · L146-L185 (40 LOC)
backend/internal/api/handlers/photo_set.go
func (h *PhotoSetHandler) UpdatePhotoSet(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	setID := getURLParam(r, "setId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	var req UpdatePhotoSetRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, errors.NewBadRequest("Invalid request body"))
		return
	}

	// At least one field must be provided
	if req.Name == nil && req.Description == nil && req.DisplayOrder == nil {
		respondError(w, errors.NewBadRequest("At least one field (name, description, or displayOrder) must be provided"))
		return
	}

	name := ""
	if req.Name != nil {
		name = *req.Name
	}

	description := ""
	if req.Description != nil {
		description = *req.Description
	}

	set, err := h.photoSetService.Update(ctx, setID, photographerID, name, description, req.DisplayOrder)
	if err != nil {
		respondError(w, err)
	
handlers.PhotoSetHandler.DeletePhotoSet method · go · L188-L205 (18 LOC)
backend/internal/api/handlers/photo_set.go
func (h *PhotoSetHandler) DeletePhotoSet(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	setID := getURLParam(r, "setId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	err := h.photoSetService.Delete(ctx, setID, photographerID)
	if err != nil {
		respondError(w, err)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
handlers.PhotoSetHandler.ReorderPhotoSets method · go · L213-L243 (31 LOC)
backend/internal/api/handlers/photo_set.go
func (h *PhotoSetHandler) ReorderPhotoSets(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "galleryId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	var req ReorderSetsRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, errors.NewBadRequest("Invalid request body"))
		return
	}

	if len(req.SetIDs) == 0 {
		respondError(w, errors.NewBadRequest("setIds array is required"))
		return
	}

	err := h.photoSetService.ReorderSets(ctx, galleryID, photographerID, req.SetIDs)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Sets reordered successfully",
	})
}
handlers.NewPhotoVersionHandler function · go · L37-L43 (7 LOC)
backend/internal/api/handlers/photo_version.go
func NewPhotoVersionHandler(versionService PhotoVersionServiceInterface, photoGetter PhotoGetter, galleryGetter GalleryGetter) *PhotoVersionHandler {
	return &PhotoVersionHandler{
		versionService: versionService,
		photoGetter:    photoGetter,
		galleryGetter:  galleryGetter,
	}
}
handlers.PhotoVersionHandler.GetReplaceUploadURL method · go · L52-L90 (39 LOC)
backend/internal/api/handlers/photo_version.go
func (h *PhotoVersionHandler) GetReplaceUploadURL(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	photoID := getURLParam(r, "photoId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// SECURITY: Verify ownership through photo -> gallery -> photographer chain
	if err := h.verifyPhotoOwnership(ctx, photoID, photographerID); err != nil {
		respondError(w, err)
		return
	}

	var req GetReplaceUploadURLRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, errors.NewBadRequest("Invalid request body"))
		return
	}

	if req.FileName == "" {
		respondError(w, errors.NewBadRequest("fileName is required"))
		return
	}
	if req.MimeType == "" {
		respondError(w, errors.NewBadRequest("mimeType is required"))
		return
	}

	resp, err := h.versionService.GenerateReplaceUploadURL(ctx, photoID, photographerID, req.FileName, req.MimeType)
	if
handlers.PhotoVersionHandler.ReplacePhoto method · go · L100-L140 (41 LOC)
backend/internal/api/handlers/photo_version.go
func (h *PhotoVersionHandler) ReplacePhoto(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	photoID := getURLParam(r, "photoId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// SECURITY: Verify ownership through photo -> gallery -> photographer chain
	if err := h.verifyPhotoOwnership(ctx, photoID, photographerID); err != nil {
		respondError(w, err)
		return
	}

	var req ReplacePhotoRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, errors.NewBadRequest("Invalid request body"))
		return
	}

	if req.FileName == "" {
		respondError(w, errors.NewBadRequest("fileName is required"))
		return
	}

	photo, err := h.versionService.ReplacePhoto(ctx, photoID, photographerID, photoversion.ReplacePhotoInput{
		FileName:        req.FileName,
		ReplacementNote: req.ReplacementNote,
	})
	if err != nil {
		respondError(w, err)
		return
handlers.PhotoVersionHandler.GetVersionHistory method · go · L143-L169 (27 LOC)
backend/internal/api/handlers/photo_version.go
func (h *PhotoVersionHandler) GetVersionHistory(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	photoID := getURLParam(r, "photoId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// SECURITY: Verify ownership through photo -> gallery -> photographer chain
	if err := h.verifyPhotoOwnership(ctx, photoID, photographerID); err != nil {
		respondError(w, err)
		return
	}

	versions, err := h.versionService.GetVersionHistory(ctx, photoID, photographerID)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"versions": versions,
		"count":    len(versions),
	})
}
handlers.PhotoVersionHandler.verifyPhotoOwnership method · go · L172-L205 (34 LOC)
backend/internal/api/handlers/photo_version.go
func (h *PhotoVersionHandler) verifyPhotoOwnership(ctx context.Context, photoID, photographerID string) error {
	if h.photoGetter == nil || h.galleryGetter == nil {
		logger.Error("CRITICAL: photoGetter or galleryGetter is nil - IDOR check cannot be performed", map[string]interface{}{
			"photoId": photoID,
			"userId":  photographerID,
		})
		return errors.NewInternalServer("Service configuration error")
	}

	// Get the photo
	photo, err := h.photoGetter.GetByID(ctx, photoID)
	if err != nil {
		return err
	}
	if photo == nil {
		return errors.NewNotFound("Photo not found")
	}

	// Get the gallery to verify ownership
	gallery, err := h.galleryGetter.GetByID(ctx, photo.GalleryID)
	if err != nil {
		return err
	}
	if gallery == nil || gallery.PhotographerID != photographerID {
		logger.Warn("IDOR attempt: user tried to access photo in gallery they don't own", map[string]interface{}{
			"userId":    photographerID,
			"photoId":   photoID,
			"galleryId": photo.GalleryID,
		})
		return er
handlers.NewPortalHandler function · go · L20-L30 (11 LOC)
backend/internal/api/handlers/portal.go
func NewPortalHandler(
	domainService *customdomain.Service,
	galleryService *gallery.Service,
	photographerRepo PhotographerRepository,
) *PortalHandler {
	return &PortalHandler{
		domainService:    domainService,
		galleryService:   galleryService,
		photographerRepo: photographerRepo,
	}
}
handlers.PortalHandler.GetPortalInfo method · go · L54-L104 (51 LOC)
backend/internal/api/handlers/portal.go
func (h *PortalHandler) GetPortalInfo(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	// Get photographer ID from context (set by domain middleware)
	photographerID, ok := ctx.Value("photographerID").(string)
	if !ok {
		respondError(w, errors.NewNotFound("Photographer not found for this domain"))
		return
	}

	// Get photographer info
	p, err := h.photographerRepo.GetByID(ctx, photographerID)
	if err != nil {
		if err == photographer.ErrNotFound {
			respondError(w, errors.NewNotFound("Photographer not found"))
		} else {
			respondError(w, errors.NewInternalServer("Failed to load photographer"))
		}
		return
	}

	// Get photographer's active galleries
	galleries, _, err := h.galleryService.ListByPhotographer(ctx, photographerID, 100, nil)
	if err != nil {
		respondError(w, errors.NewInternalServer("Failed to load galleries"))
		return
	}

	// Filter to only active galleries and transform to public format
	publicGalleries := make([]GalleryInfo, 0)
	for _, g := range ga
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
handlers.PortalHandler.ListPortalGalleries method · go · L108-L142 (35 LOC)
backend/internal/api/handlers/portal.go
func (h *PortalHandler) ListPortalGalleries(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	// Get photographer ID from context (set by domain middleware)
	photographerID, ok := ctx.Value("photographerID").(string)
	if !ok {
		respondError(w, errors.NewNotFound("Photographer not found for this domain"))
		return
	}

	// Get photographer's active galleries
	galleries, _, err := h.galleryService.ListByPhotographer(ctx, photographerID, 100, nil)
	if err != nil {
		respondError(w, errors.NewInternalServer("Failed to load galleries"))
		return
	}

	// Filter to only active galleries and transform to public format
	publicGalleries := make([]GalleryInfo, 0)
	for _, g := range galleries {
		if g.Status == "active" {
			publicGalleries = append(publicGalleries, GalleryInfo{
				GalleryID:   g.GalleryID,
				Name:        g.Name,
				Description: g.Description,
				CustomURL:   g.CustomURL,
				PhotoCount:  g.PhotoCount,
			})
		}
	}

	respondJSON(w, http.StatusOK, map[string]inte
handlers.ProofingHandler.EnableProofing method · go · L39-L63 (25 LOC)
backend/internal/api/handlers/proofing.go
func (h *ProofingHandler) EnableProofing(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	err := h.proofingService.TransitionToProofing(ctx, proofing.TransitionToProofingRequest{
		GalleryID:      galleryID,
		PhotographerID: photographerID,
	})
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"message":   "Gallery transitioned to proofing phase",
		"galleryId": galleryID,
		"phase":     "proofing",
	})
}
handlers.ProofingHandler.DisableProofing method · go · L67-L88 (22 LOC)
backend/internal/api/handlers/proofing.go
func (h *ProofingHandler) DisableProofing(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	err := h.proofingService.TransitionToUploading(ctx, galleryID, photographerID)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"message":   "Gallery transitioned to uploading phase",
		"galleryId": galleryID,
		"phase":     "uploading",
	})
}
handlers.ProofingHandler.GetProofingSummary method · go · L91-L108 (18 LOC)
backend/internal/api/handlers/proofing.go
func (h *ProofingHandler) GetProofingSummary(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	summary, err := h.proofingService.GetProofingSummary(ctx, galleryID, photographerID)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, summary)
}
handlers.ProofingHandler.GetSelections method · go · L111-L131 (21 LOC)
backend/internal/api/handlers/proofing.go
func (h *ProofingHandler) GetSelections(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	selections, err := h.proofingService.GetSelectionsForGallery(ctx, galleryID, photographerID)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"selections": selections,
		"count":      len(selections),
	})
}
handlers.ProofingHandler.GetComments method · go · L134-L154 (21 LOC)
backend/internal/api/handlers/proofing.go
func (h *ProofingHandler) GetComments(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	comments, err := h.proofingService.ListCommentsForGallery(ctx, galleryID, photographerID)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"comments": comments,
		"count":    len(comments),
	})
}
handlers.ProofingHandler.GetEditRequests method · go · L157-L180 (24 LOC)
backend/internal/api/handlers/proofing.go
func (h *ProofingHandler) GetEditRequests(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "id")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// Get status filter from query params
	status := r.URL.Query().Get("status")

	requests, err := h.proofingService.ListEditRequestsForGallery(ctx, galleryID, photographerID, status)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"editRequests": requests,
		"count":        len(requests),
	})
}
handlers.ProofingHandler.UpdateEditRequestStatus method · go · L188-L217 (30 LOC)
backend/internal/api/handlers/proofing.go
func (h *ProofingHandler) UpdateEditRequestStatus(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	photoID := getURLParam(r, "photoId")
	requestID := getURLParam(r, "requestId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	var req UpdateEditRequestStatusRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, errors.NewBadRequest("Invalid request body"))
		return
	}

	if req.Status == "" {
		respondError(w, errors.NewBadRequest("Status is required"))
		return
	}

	updatedRequest, err := h.proofingService.UpdateEditRequestStatus(ctx, photoID, requestID, photographerID, req.Status)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, updatedRequest)
}
Repobility — same analyzer, your code, free for public repos · /scan/
handlers.NewSelectionHandler function · go · L34-L39 (6 LOC)
backend/internal/api/handlers/selection.go
func NewSelectionHandler(selectionService SelectionServiceInterface, galleryGetter GalleryGetter) *SelectionHandler {
	return &SelectionHandler{
		selectionService: selectionService,
		galleryGetter:    galleryGetter,
	}
}
handlers.SelectionHandler.GetSubmissions method · go · L42-L79 (38 LOC)
backend/internal/api/handlers/selection.go
func (h *SelectionHandler) GetSubmissions(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "galleryId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// SECURITY: Verify gallery ownership (IDOR prevention)
	if h.galleryGetter != nil {
		gallery, err := h.galleryGetter.GetByID(ctx, galleryID)
		if err != nil {
			respondError(w, err)
			return
		}
		if gallery == nil || gallery.PhotographerID != photographerID {
			logger.Warn("IDOR attempt: user tried to view submissions for gallery they don't own", map[string]interface{}{
				"userId":    photographerID,
				"galleryId": galleryID,
			})
			respondError(w, errors.NewForbidden("You do not have permission to view this gallery"))
			return
		}
	}

	submissions, err := h.selectionService.ListSubmissionsByGallery(ctx, galleryID, photographerID)
	if err != nil {
		respondError(w, err
handlers.SelectionHandler.GetLatestSubmission method · go · L82-L109 (28 LOC)
backend/internal/api/handlers/selection.go
func (h *SelectionHandler) GetLatestSubmission(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "galleryId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	submission, err := h.selectionService.GetSubmissionByGallery(ctx, galleryID, photographerID)
	if err != nil {
		respondError(w, err)
		return
	}

	if submission == nil {
		respondJSON(w, http.StatusOK, map[string]interface{}{
			"submission": nil,
			"message":    "No selections have been submitted yet",
		})
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"submission": submission,
	})
}
handlers.SelectionHandler.UnlockSubmission method · go · L117-L148 (32 LOC)
backend/internal/api/handlers/selection.go
func (h *SelectionHandler) UnlockSubmission(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "galleryId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	var req UnlockSubmissionRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, errors.NewBadRequest("Invalid request body"))
		return
	}

	if req.SubmissionID == "" {
		respondError(w, errors.NewBadRequest("submissionId is required"))
		return
	}

	submission, err := h.selectionService.UnlockSubmission(ctx, galleryID, req.SubmissionID, photographerID)
	if err != nil {
		respondError(w, err)
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"submission": submission,
		"message":    "Submission unlocked for client revisions",
	})
}
handlers.SelectionHandler.ExportSelections method · go · L152-L189 (38 LOC)
backend/internal/api/handlers/selection.go
func (h *SelectionHandler) ExportSelections(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	galleryID := getURLParam(r, "galleryId")

	photographerID, ok := ctx.Value("userID").(string)
	if !ok || photographerID == "" {
		respondError(w, errors.NewUnauthorized("User ID not found"))
		return
	}

	// Get format from query params (default to lightroom)
	format := r.URL.Query().Get("format")
	if format == "" {
		format = "lightroom"
	}

	// Validate format
	validFormats := map[string]bool{"lightroom": true, "json": true, "csv": true}
	if !validFormats[format] {
		respondError(w, errors.NewBadRequest("Invalid format. Valid options: lightroom, json, csv"))
		return
	}

	data, contentType, err := h.selectionService.ExportSelections(ctx, galleryID, photographerID, format)
	if err != nil {
		respondError(w, err)
		return
	}

	w.Header().Set("Content-Type", contentType)
	// Set Content-Disposition for file downloads (json and csv formats)
	// Lightroom format returns JSON for fron
handlers.getFileExtension function · go · L192-L203 (12 LOC)
backend/internal/api/handlers/selection.go
func getFileExtension(format string) string {
	switch format {
	case "lightroom":
		return "txt"
	case "json":
		return "json"
	case "csv":
		return "csv"
	default:
		return "txt"
	}
}
handlers.ClientSelectionHandler.SubmitSelections method · go · L224-L267 (44 LOC)
backend/internal/api/handlers/selection.go
func (h *ClientSelectionHandler) SubmitSelections(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	galleryID, ok := ctx.Value("galleryID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("Gallery ID not found in session"))
		return
	}

	sessionID, ok := ctx.Value("sessionID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("Session ID not found"))
		return
	}

	var req SubmitSelectionsRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, errors.NewBadRequest("Invalid request body"))
		return
	}

	// Validate photo IDs
	if len(req.PhotoIDs) == 0 {
		respondError(w, errors.NewBadRequest("At least one photo must be selected"))
		return
	}

	// Sanitize notes (limit length)
	notes := strings.TrimSpace(req.Notes)
	if len(notes) > 1000 {
		notes = notes[:1000]
	}

	submission, err := h.selectionService.SubmitSelections(ctx, galleryID, sessionID, req.PhotoIDs, notes)
	if err != nil {
		respondError(w, err)
		return
	}

	
handlers.ClientSelectionHandler.GetMySubmission method · go · L270-L305 (36 LOC)
backend/internal/api/handlers/selection.go
func (h *ClientSelectionHandler) GetMySubmission(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	galleryID, ok := ctx.Value("galleryID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("Gallery ID not found in session"))
		return
	}

	sessionID, ok := ctx.Value("sessionID").(string)
	if !ok {
		respondError(w, errors.NewUnauthorized("Session ID not found"))
		return
	}

	submission, err := h.selectionService.GetSubmissionForSession(ctx, galleryID, sessionID)
	if err != nil {
		respondError(w, err)
		return
	}

	if submission == nil {
		respondJSON(w, http.StatusOK, map[string]interface{}{
			"submission": nil,
			"submitted":  false,
			"message":    "You have not submitted your selections yet",
		})
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"submission": submission,
		"submitted":  true,
		"locked":     !submission.Unlocked,
	})
}
If a scraper extracted this row, it came from Repobility (https://repobility.com)
handlers.NewSubscriptionHandler function · go · L43-L49 (7 LOC)
backend/internal/api/handlers/subscription.go
func NewSubscriptionHandler(subscriptionService SubscriptionService, webhookHandler WebhookHandler, priceToTier map[string]subscription.Tier) *SubscriptionHandler {
	return &SubscriptionHandler{
		subscriptionService: subscriptionService,
		webhookHandler:      webhookHandler,
		priceToTier:         priceToTier,
	}
}
handlers.SubscriptionHandler.GetSubscription method · go · L53-L83 (31 LOC)
backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) GetSubscription(w http.ResponseWriter, r *http.Request) {
	userID, ok := r.Context().Value("userID").(string)
	if !ok || userID == "" {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	// Check if sync is requested (useful after checkout when webhooks haven't processed)
	if r.URL.Query().Get("sync") == "true" && h.priceToTier != nil {
		_, syncErr := h.subscriptionService.SyncSubscriptionFromStripe(r.Context(), userID, h.priceToTier)
		if syncErr != nil {
			logger.Warn("Failed to sync subscription from Stripe", map[string]interface{}{
				"userID": userID,
				"error":  syncErr.Error(),
			})
			// Continue to return cached data even if sync fails
		}
	}

	resp, err := h.subscriptionService.GetSubscriptionWithLimits(r.Context(), userID)
	if err != nil {
		logger.Error("Failed to get subscription", map[string]interface{}{
			"userID": userID,
			"error":  err.Error(),
		})
		respondWithError(w, http.StatusInternalServerError, "F
handlers.SubscriptionHandler.GetTiers method · go · L86-L91 (6 LOC)
backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) GetTiers(w http.ResponseWriter, r *http.Request) {
	tiers := h.subscriptionService.GetTiers()
	respondWithJSON(w, http.StatusOK, map[string]interface{}{
		"tiers": tiers,
	})
}
handlers.SubscriptionHandler.CreateCheckoutSession method · go · L94-L130 (37 LOC)
backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) CreateCheckoutSession(w http.ResponseWriter, r *http.Request) {
	userID, ok := r.Context().Value("userID").(string)
	if !ok || userID == "" {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	email, _ := r.Context().Value("email").(string)
	name, _ := r.Context().Value("name").(string)

	var req struct {
		PriceID string `json:"priceId"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	if req.PriceID == "" {
		respondWithError(w, http.StatusBadRequest, "Price ID is required")
		return
	}

	session, err := h.subscriptionService.CreateCheckoutSession(r.Context(), userID, email, name, req.PriceID)
	if err != nil {
		logger.Error("Failed to create checkout session", map[string]interface{}{
			"userID":  userID,
			"priceID": req.PriceID,
			"error":   err.Error(),
		})
		respondWithError(w, http.StatusInternalServerError, "Failed to cr
handlers.SubscriptionHandler.CreatePortalSession method · go · L133-L151 (19 LOC)
backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) CreatePortalSession(w http.ResponseWriter, r *http.Request) {
	userID, ok := r.Context().Value("userID").(string)
	if !ok || userID == "" {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	session, err := h.subscriptionService.CreatePortalSession(r.Context(), userID)
	if err != nil {
		logger.Error("Failed to create portal session", map[string]interface{}{
			"userID": userID,
			"error":  err.Error(),
		})
		respondWithError(w, http.StatusInternalServerError, "Failed to create portal session")
		return
	}

	respondWithJSON(w, http.StatusOK, session)
}
handlers.SubscriptionHandler.GetProrationPreview method · go · L154-L179 (26 LOC)
backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) GetProrationPreview(w http.ResponseWriter, r *http.Request) {
	userID, ok := r.Context().Value("userID").(string)
	if !ok || userID == "" {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	priceID := r.URL.Query().Get("priceId")
	if priceID == "" {
		respondWithError(w, http.StatusBadRequest, "Price ID is required")
		return
	}

	preview, err := h.subscriptionService.GetProrationPreview(r.Context(), userID, priceID)
	if err != nil {
		logger.Error("Failed to get proration preview", map[string]interface{}{
			"userID":  userID,
			"priceID": priceID,
			"error":   err.Error(),
		})
		respondWithError(w, http.StatusInternalServerError, "Failed to get proration preview")
		return
	}

	respondWithJSON(w, http.StatusOK, preview)
}
handlers.SubscriptionHandler.ChangeTier method · go · L182-L217 (36 LOC)
backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) ChangeTier(w http.ResponseWriter, r *http.Request) {
	userID, ok := r.Context().Value("userID").(string)
	if !ok || userID == "" {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	var req struct {
		PriceID string `json:"priceId"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	if req.PriceID == "" {
		respondWithError(w, http.StatusBadRequest, "Price ID is required")
		return
	}

	err := h.subscriptionService.ChangeTier(r.Context(), userID, req.PriceID)
	if err != nil {
		logger.Error("Failed to change tier", map[string]interface{}{
			"userID":  userID,
			"priceID": req.PriceID,
			"error":   err.Error(),
		})
		respondWithError(w, http.StatusInternalServerError, "Failed to change subscription tier")
		return
	}

	respondWithJSON(w, http.StatusOK, map[string]string{
		"message": "Subscription tier change initiated",
	})
}
handlers.SubscriptionHandler.CancelSubscription method · go · L220-L242 (23 LOC)
backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) CancelSubscription(w http.ResponseWriter, r *http.Request) {
	userID, ok := r.Context().Value("userID").(string)
	if !ok || userID == "" {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	sub, err := h.subscriptionService.CancelSubscription(r.Context(), userID)
	if err != nil {
		logger.Error("Failed to cancel subscription", map[string]interface{}{
			"userID": userID,
			"error":  err.Error(),
		})
		respondWithError(w, http.StatusInternalServerError, "Failed to cancel subscription")
		return
	}

	respondWithJSON(w, http.StatusOK, map[string]interface{}{
		"message":      "Subscription will be canceled at the end of the current billing period",
		"cancelAt":     sub.CurrentPeriodEnd,
		"subscription": sub,
	})
}
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
handlers.SubscriptionHandler.ReactivateSubscription method · go · L245-L266 (22 LOC)
backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) ReactivateSubscription(w http.ResponseWriter, r *http.Request) {
	userID, ok := r.Context().Value("userID").(string)
	if !ok || userID == "" {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	sub, err := h.subscriptionService.ReactivateSubscription(r.Context(), userID)
	if err != nil {
		logger.Error("Failed to reactivate subscription", map[string]interface{}{
			"userID": userID,
			"error":  err.Error(),
		})
		respondWithError(w, http.StatusInternalServerError, "Failed to reactivate subscription")
		return
	}

	respondWithJSON(w, http.StatusOK, map[string]interface{}{
		"message":      "Subscription has been reactivated",
		"subscription": sub,
	})
}
handlers.SubscriptionHandler.HandleStripeWebhook method · go · L271-L316 (46 LOC)
backend/internal/api/handlers/subscription.go
func (h *SubscriptionHandler) HandleStripeWebhook(w http.ResponseWriter, r *http.Request) {
	// Read the request body
	payload, err := io.ReadAll(r.Body)
	if err != nil {
		logger.Error("Failed to read webhook payload", map[string]interface{}{
			"error": err.Error(),
		})
		respondWithError(w, http.StatusBadRequest, "Failed to read request body")
		return
	}

	// Get the Stripe signature header
	signature := r.Header.Get("Stripe-Signature")
	if signature == "" {
		respondWithError(w, http.StatusBadRequest, "Missing Stripe-Signature header")
		return
	}

	// Handle the webhook event
	err = h.webhookHandler.HandleWebhook(r.Context(), payload, signature)
	if err != nil {
		logger.Error("Failed to handle webhook", map[string]interface{}{
			"error": err.Error(),
		})

		// SECURITY FIX: Return appropriate status codes based on error type
		// - 400 Bad Request: Signature verification failures (security issue, don't retry)
		// - 500 Internal Server Error: Processing errors (retry is appro
handlers.isSignatureVerificationError function · go · L321-L335 (15 LOC)
backend/internal/api/handlers/subscription.go
func isSignatureVerificationError(errStr string) bool {
	signatureErrorPatterns := []string{
		"signature verification failed",
		"signature mismatch",
		"invalid signature",
		"timestamp too old",
	}
	errStrLower := strings.ToLower(errStr)
	for _, pattern := range signatureErrorPatterns {
		if strings.Contains(errStrLower, pattern) {
			return true
		}
	}
	return false
}
‹ prevpage 3 / 20next ›