Function bodies 1,000 total
gallery.Service.SetExpiration method · go · L383-L394 (12 LOC)backend/internal/domain/gallery/service.go
func (s *Service) SetExpiration(ctx context.Context, galleryID string, expiresAt *time.Time) (*repository.Gallery, error) {
gallery, err := s.GetByID(ctx, galleryID)
if err != nil {
return nil, err
}
gallery.ExpiresAt = expiresAt
if err := s.galleryRepo.Update(ctx, gallery); err != nil {
return nil, errors.Wrap(err, 500, "Failed to update gallery expiration")
}
logger.Info("Gallery expiration updated", map[string]interface{}{"galleryId": gallery.GalleryID})
return gallery, nil
}gallery.Service.ProcessExpiredGalleries method · go · L397-L419 (23 LOC)backend/internal/domain/gallery/service.go
func (s *Service) ProcessExpiredGalleries(ctx context.Context, limit int) error {
galleries, err := s.galleryRepo.ListExpired(ctx, limit)
if err != nil {
return errors.Wrap(err, 500, "Failed to list expired galleries")
}
var errorCount int
for _, gallery := range galleries {
if err := s.Delete(ctx, gallery.GalleryID); err != nil {
logger.Error("Failed to delete expired gallery", map[string]interface{}{
"galleryId": gallery.GalleryID, "error": err.Error(),
})
errorCount++
continue
}
logger.Info("Deleted expired gallery", map[string]interface{}{"galleryId": gallery.GalleryID})
}
if errorCount > 0 {
return fmt.Errorf("completed with %d errors out of %d galleries", errorCount, len(galleries))
}
return nil
}gallery.Service.ProcessScheduledGalleries method · go · L423-L477 (55 LOC)backend/internal/domain/gallery/service.go
func (s *Service) ProcessScheduledGalleries(ctx context.Context, limit int) error {
galleries, err := s.galleryRepo.ListScheduled(ctx, limit)
if err != nil {
return errors.Wrap(err, 500, "Failed to list scheduled galleries")
}
var errorCount int
var skippedCount int
for _, gallery := range galleries {
// Use atomic conditional update to prevent race conditions.
// Only updates if the gallery is still in "scheduled" status.
updated, err := s.galleryRepo.UpdateStatusIfScheduled(ctx, gallery.GalleryID, gallery.PhotographerID)
if err != nil {
logger.Error("Failed to publish scheduled gallery", map[string]interface{}{
"galleryId": gallery.GalleryID, "error": err.Error(),
})
errorCount++
continue
}
if !updated {
// Gallery was modified by user or already published by another scheduler instance
logger.Info("Skipped gallery (already modified or published)", map[string]interface{}{
"galleryId": gallery.GalleryID,
})
skippedCount++
continugallery.Service.PublishScheduledGalleriesForPhotographer method · go · L481-L507 (27 LOC)backend/internal/domain/gallery/service.go
func (s *Service) PublishScheduledGalleriesForPhotographer(ctx context.Context, photographerID string) error {
galleries, err := s.galleryRepo.ListScheduledByPhotographer(ctx, photographerID)
if err != nil {
return errors.Wrap(err, 500, "Failed to list scheduled galleries for photographer")
}
var errorCount int
for _, gallery := range galleries {
gallery.Status = "active"
gallery.PublishAt = nil // Clear the scheduled time
if err := s.galleryRepo.Update(ctx, gallery); err != nil {
logger.Error("Failed to publish scheduled gallery on tier downgrade", map[string]interface{}{
"galleryId": gallery.GalleryID, "photographerId": photographerID, "error": err.Error(),
})
errorCount++
continue
}
logger.Info("Published scheduled gallery due to tier downgrade", map[string]interface{}{
"galleryId": gallery.GalleryID, "photographerId": photographerID,
})
}
if errorCount > 0 {
return fmt.Errorf("completed with %d errors out of %d galleries", errorCount, lengallery.BaseValidator.ValidateNext method · go · L30-L35 (6 LOC)backend/internal/domain/gallery/validators.go
func (v *BaseValidator) ValidateNext(ctx context.Context, req interface{}) error {
if v.next != nil {
return v.next.Validate(ctx, req)
}
return nil
}gallery.CustomURLValidator.Validate method · go · L48-L66 (19 LOC)backend/internal/domain/gallery/validators.go
func (v *CustomURLValidator) Validate(ctx context.Context, req interface{}) error {
createReq, ok := req.(CreateGalleryRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
if createReq.CustomURL != "" {
if len(createReq.CustomURL) < 3 || len(createReq.CustomURL) > 100 {
return errors.NewBadRequest("Custom URL must be between 3 and 100 characters")
}
matched, _ := regexp.MatchString("^[a-z0-9-]+$", createReq.CustomURL)
if !matched {
return errors.NewBadRequest("Custom URL can only contain lowercase letters, numbers, and hyphens")
}
}
return v.ValidateNext(ctx, req)
}gallery.PasswordValidator.Validate method · go · L80-L95 (16 LOC)backend/internal/domain/gallery/validators.go
func (v *PasswordValidator) Validate(ctx context.Context, req interface{}) error {
createReq, ok := req.(CreateGalleryRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
if createReq.Password == "" {
return errors.NewBadRequest("Password is required")
}
if len(createReq.Password) < v.MinLength {
return errors.NewBadRequest("Password must be at least " + string(rune(v.MinLength+'0')) + " characters")
}
return v.ValidateNext(ctx, req)
}Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
gallery.NameValidator.Validate method · go · L110-L125 (16 LOC)backend/internal/domain/gallery/validators.go
func (v *NameValidator) Validate(ctx context.Context, req interface{}) error {
createReq, ok := req.(CreateGalleryRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
if createReq.Name == "" {
return errors.NewBadRequest("Gallery name is required")
}
if len(createReq.Name) < v.MinLength || len(createReq.Name) > v.MaxLength {
return errors.NewBadRequest("Gallery name must be between " + string(rune(v.MinLength+'0')) + " and " + string(rune(v.MaxLength+'0')) + " characters")
}
return v.ValidateNext(ctx, req)
}gallery.ExpirationValidator.Validate method · go · L138-L149 (12 LOC)backend/internal/domain/gallery/validators.go
func (v *ExpirationValidator) Validate(ctx context.Context, req interface{}) error {
createReq, ok := req.(CreateGalleryRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
if createReq.ExpiresAt != nil && createReq.ExpiresAt.Before(time.Now()) {
return errors.NewBadRequest("Expiration date must be in the future")
}
return v.ValidateNext(ctx, req)
}gallery.WatermarkValidator.Validate method · go · L162-L205 (44 LOC)backend/internal/domain/gallery/validators.go
func (v *WatermarkValidator) Validate(ctx context.Context, req interface{}) error {
createReq, ok := req.(CreateGalleryRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
// Validate watermark type if provided
if createReq.WatermarkType != "" {
validTypes := map[string]bool{
"text": true,
"image": true,
}
if !validTypes[createReq.WatermarkType] {
return errors.NewBadRequest("Invalid watermark type. Must be 'text' or 'image'")
}
}
// Validate watermark position if provided
if createReq.WatermarkPosition != "" {
validPositions := map[string]bool{
"bottom-right": true,
"bottom-left": true,
"center": true,
}
if !validPositions[createReq.WatermarkPosition] {
return errors.NewBadRequest("Invalid watermark position")
}
}
// Validate phase if provided
if createReq.Phase != "" {
validPhases := map[string]bool{
"uploading": true,
"proofing": true,
"editing": true,
"delivered": true,
}
if !validPhases[createReq.Phasgallery.TemplateValidator.Validate method · go · L218-L237 (20 LOC)backend/internal/domain/gallery/validators.go
func (v *TemplateValidator) Validate(ctx context.Context, req interface{}) error {
createReq, ok := req.(CreateGalleryRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
if createReq.Template != "" {
validTemplates := map[string]bool{
"grid": true,
"masonry": true,
"justified": true,
"slideshow": true,
}
if !validTemplates[createReq.Template] {
return errors.NewBadRequest("Invalid template type. Must be one of: grid, masonry, justified, slideshow")
}
}
return v.ValidateNext(ctx, req)
}gallery.NewCreateGalleryValidationChain function · go · L240-L252 (13 LOC)backend/internal/domain/gallery/validators.go
func NewCreateGalleryValidationChain() Validator {
name := NewNameValidator(1, 200)
password := NewPasswordValidator(6)
customURL := NewCustomURLValidator()
expiration := NewExpirationValidator()
watermark := NewWatermarkValidator()
template := NewTemplateValidator()
// Build the chain
name.SetNext(password).SetNext(customURL).SetNext(expiration).SetNext(watermark).SetNext(template)
return name
}photo.NewService function · go · L40-L54 (15 LOC)backend/internal/domain/photo/service.go
func NewService(
photoRepo repository.PhotoRepository,
galleryRepo repository.GalleryRepository,
favoriteRepo repository.FavoriteRepository,
storageService StorageService,
eventBus events.EventBus,
) *Service {
return &Service{
photoRepo: photoRepo,
galleryRepo: galleryRepo,
favoriteRepo: favoriteRepo,
storageService: storageService,
eventBus: eventBus,
}
}photo.Service.GenerateUploadURL method · go · L71-L112 (42 LOC)backend/internal/domain/photo/service.go
func (s *Service) GenerateUploadURL(ctx context.Context, req UploadURLRequest) (*UploadURLResponse, error) {
// Verify gallery exists
gallery, err := s.galleryRepo.GetByID(ctx, req.GalleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil {
return nil, errors.NewNotFound("Gallery")
}
// Validate file type
if !isValidImageType(req.MimeType) {
return nil, errors.NewBadRequest("Invalid image type. Supported: JPEG, PNG, GIF, WebP, HEIC/HEIF, TIFF, BMP, RAW (CR2, NEF, ARW, DNG, ORF, RAF), PSD, EPS, SVG")
}
// Generate photo ID
photoID := utils.GenerateID("photo")
// Generate presigned upload URL
uploadResp, err := s.storageService.GenerateUploadURL(ctx, storage.UploadURLRequest{
GalleryID: req.GalleryID,
PhotoID: photoID,
FileName: req.FileName,
MimeType: req.MimeType,
})
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to generate upload URL")
}
logger.Info("Generated upload URL for photo", mphoto.Service.Create method · go · L132-L194 (63 LOC)backend/internal/domain/photo/service.go
func (s *Service) Create(ctx context.Context, req CreatePhotoRequest) (*repository.Photo, error) {
// Verify gallery exists
gallery, err := s.galleryRepo.GetByID(ctx, req.GalleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil {
return nil, errors.NewNotFound("Gallery")
}
photo := &repository.Photo{
PhotoID: req.PhotoID,
GalleryID: req.GalleryID,
SetID: req.SetID,
SetOrder: req.SetOrder,
FileName: req.FileName,
OriginalKey: req.OriginalKey,
OptimizedKey: req.OptimizedKey,
ThumbnailKey: req.ThumbnailKey,
MimeType: req.MimeType,
Size: req.Size,
Width: req.Width,
Height: req.Height,
UploadedAt: time.Now(),
FavoriteCount: 0,
DownloadCount: 0,
CurrentVersion: 1, // Start at version 1
Metadata: req.Metadata,
}
// Create photo record
if err := s.photoRepo.Create(ctx, photo); err != nil {
logger.Error("Failed Want this analysis on your repo? https://repobility.com/scan/
photo.Service.GetByID method · go · L197-L206 (10 LOC)backend/internal/domain/photo/service.go
func (s *Service) GetByID(ctx context.Context, photoID string) (*repository.Photo, error) {
photo, err := s.photoRepo.GetByID(ctx, photoID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get photo")
}
if photo == nil {
return nil, errors.NewNotFound("Photo")
}
return photo, nil
}photo.Service.Delete method · go · L218-L262 (45 LOC)backend/internal/domain/photo/service.go
func (s *Service) Delete(ctx context.Context, photoID string) error {
// Get photo
photo, err := s.GetByID(ctx, photoID)
if err != nil {
return err
}
// Delete from S3
if err := s.storageService.DeletePhoto(ctx, photo.OriginalKey, photo.OptimizedKey, photo.ThumbnailKey); err != nil {
logger.Error("Failed to delete photo files", map[string]interface{}{"error": err.Error()})
// Continue with deletion even if S3 fails
}
// Delete from DynamoDB
if err := s.photoRepo.Delete(ctx, photoID); err != nil {
logger.Error("Failed to delete photo record", map[string]interface{}{"error": err.Error()})
return errors.Wrap(err, 500, "Failed to delete photo")
}
// Update gallery stats
if err := s.galleryRepo.UpdatePhotoCount(ctx, photo.GalleryID, -1); err != nil {
logger.Error("Failed to update photo count", map[string]interface{}{"error": err.Error()})
}
if err := s.galleryRepo.UpdateTotalSize(ctx, photo.GalleryID, -photo.Size); err != nil {
logger.Error("Failed to update photo.Service.GetDownloadURL method · go · L265-L304 (40 LOC)backend/internal/domain/photo/service.go
func (s *Service) GetDownloadURL(ctx context.Context, photoID string) (string, error) {
photo, err := s.GetByID(ctx, photoID)
if err != nil {
return "", err
}
// Get gallery to find photographer ID for analytics
gallery, err := s.galleryRepo.GetByID(ctx, photo.GalleryID)
if err != nil {
logger.Error("Failed to get gallery for download analytics", map[string]interface{}{"error": err.Error()})
}
// Increment download count
if err := s.photoRepo.IncrementDownloadCount(ctx, photoID); err != nil {
logger.Error("Failed to increment download count", map[string]interface{}{"error": err.Error()})
// Continue anyway
}
// Generate download URL for optimized version
url, err := s.storageService.GenerateDownloadURL(ctx, photo.OptimizedKey, "", photo.FileName)
if err != nil {
return "", errors.Wrap(err, 500, "Failed to generate download URL")
}
logger.Info("Generated download URL", map[string]interface{}{
"photoId": photo.PhotoID,
})
// Publish PhotoDownloaded eventphoto.Service.ToggleFavorite method · go · L307-L367 (61 LOC)backend/internal/domain/photo/service.go
func (s *Service) ToggleFavorite(ctx context.Context, galleryID, sessionID, photoID string) (bool, error) {
// Check if already favorited
isFavorited, err := s.favoriteRepo.IsFavorited(ctx, galleryID, sessionID, photoID)
if err != nil {
return false, errors.Wrap(err, 500, "Failed to check favorite status")
}
if isFavorited {
// Remove favorite
if err := s.favoriteRepo.Delete(ctx, galleryID, sessionID, photoID); err != nil {
return false, errors.Wrap(err, 500, "Failed to remove favorite")
}
// Decrement count
if err := s.photoRepo.IncrementFavoriteCount(ctx, photoID, -1); err != nil {
logger.Error("Failed to decrement favorite count", map[string]interface{}{"error": err.Error()})
}
logger.Info("Photo unfavorited", map[string]interface{}{"photoId": photoID})
// Publish FavoriteToggled event for analytics
if s.eventBus != nil {
event := events.NewEvent(events.FavoriteToggled, &events.FavoriteToggledPayload{
PhotoID: photoID,
GalleryID: galleryphoto.Service.ListFavoritesBySession method · go · L370-L376 (7 LOC)backend/internal/domain/photo/service.go
func (s *Service) ListFavoritesBySession(ctx context.Context, galleryID, sessionID string) ([]*repository.Favorite, error) {
favorites, err := s.favoriteRepo.ListBySession(ctx, galleryID, sessionID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to list favorites")
}
return favorites, nil
}photo.Service.ListFavoritesByGallery method · go · L379-L385 (7 LOC)backend/internal/domain/photo/service.go
func (s *Service) ListFavoritesByGallery(ctx context.Context, galleryID string) ([]*repository.Favorite, error) {
favorites, err := s.favoriteRepo.ListByGallery(ctx, galleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to list gallery favorites")
}
return favorites, nil
}photo.Service.UpdateSetID method · go · L388-L407 (20 LOC)backend/internal/domain/photo/service.go
func (s *Service) UpdateSetID(ctx context.Context, photoID string, setID string, setOrder int) (*repository.Photo, error) {
photo, err := s.GetByID(ctx, photoID)
if err != nil {
return nil, err
}
if err := s.photoRepo.UpdateSetID(ctx, photoID, setID, setOrder); err != nil {
return nil, errors.Wrap(err, 500, "Failed to update photo set")
}
photo.SetID = setID
photo.SetOrder = setOrder
logger.Info("Photo set updated", map[string]interface{}{
"photoId": photoID,
"setId": setID,
"setOrder": setOrder,
})
return photo, nil
}photo.Service.ListBySet method · go · L410-L416 (7 LOC)backend/internal/domain/photo/service.go
func (s *Service) ListBySet(ctx context.Context, setID string) ([]*repository.Photo, error) {
photos, err := s.photoRepo.ListBySet(ctx, setID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to list photos by set")
}
return photos, nil
}Open data scored by Repobility · https://repobility.com
photo.Service.GenerateGalleryZipURL method · go · L420-L557 (138 LOC)backend/internal/domain/photo/service.go
func (s *Service) GenerateGalleryZipURL(ctx context.Context, galleryID string, zipFilename string) (string, error) {
// Verify gallery exists
gallery, err := s.galleryRepo.GetByID(ctx, galleryID)
if err != nil {
return "", errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil {
return "", errors.NewNotFound("Gallery")
}
// Fetch all photos for the gallery with pagination
var allPhotos []*repository.Photo
var lastKey map[string]interface{}
const pageSize = 500
for {
photos, nextKey, err := s.photoRepo.ListByGallery(ctx, galleryID, pageSize, lastKey)
if err != nil {
return "", errors.Wrap(err, 500, "Failed to list photos")
}
allPhotos = append(allPhotos, photos...)
if nextKey == nil || len(photos) < pageSize {
break
}
lastKey = nextKey
}
if len(allPhotos) == 0 {
return "", errors.NewBadRequest("Gallery has no photos to download")
}
// Create ZIP in memory buffer
var buf bytes.Buffer
zipWriter := zip.NewWriter(&buf)
// Track fphoto.isValidImageType function · go · L560-L585 (26 LOC)backend/internal/domain/photo/service.go
func isValidImageType(mimeType string) bool {
validTypes := map[string]bool{
// Standard image formats
"image/jpeg": true,
"image/jpg": true,
"image/png": true,
"image/webp": true,
"image/gif": true,
"image/heic": true,
"image/heif": true,
"image/tiff": true,
"image/bmp": true,
// RAW camera formats
"image/x-canon-cr2": true,
"image/x-nikon-nef": true,
"image/x-sony-arw": true,
"image/x-adobe-dng": true,
"image/x-olympus-orf": true,
"image/x-fuji-raf": true,
// Professional formats
"image/vnd.adobe.photoshop": true,
"application/postscript": true, // EPS
"image/svg+xml": true,
}
return validTypes[mimeType]
}photoset.NewService function · go · L20-L28 (9 LOC)backend/internal/domain/photoset/service.go
func NewService(
photoSetRepo repository.PhotoSetRepository,
galleryRepo repository.GalleryRepository,
) *Service {
return &Service{
photoSetRepo: photoSetRepo,
galleryRepo: galleryRepo,
}
}photoset.Service.Create method · go · L31-L75 (45 LOC)backend/internal/domain/photoset/service.go
func (s *Service) Create(ctx context.Context, galleryID, photographerID, name, description string) (*repository.PhotoSet, error) {
// Verify gallery exists and belongs to photographer
gallery, err := s.galleryRepo.GetByID(ctx, galleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil {
return nil, errors.NewNotFound("Gallery")
}
if gallery.PhotographerID != photographerID {
return nil, errors.NewForbidden("You do not have permission to modify this gallery")
}
// Get existing sets to determine display order
existingSets, err := s.photoSetRepo.ListByGallery(ctx, galleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to list existing sets")
}
// New set goes at the end
displayOrder := len(existingSets)
set := &repository.PhotoSet{
SetID: utils.GenerateID("set"),
GalleryID: galleryID,
Name: name,
Description: description,
DisplayOrder: displayOrder,
PhotoCount: 0,
Creaphotoset.Service.GetByID method · go · L78-L87 (10 LOC)backend/internal/domain/photoset/service.go
func (s *Service) GetByID(ctx context.Context, setID string) (*repository.PhotoSet, error) {
set, err := s.photoSetRepo.GetByID(ctx, setID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get photo set")
}
if set == nil {
return nil, errors.NewNotFound("PhotoSet")
}
return set, nil
}photoset.Service.ListByGallery method · go · L90-L96 (7 LOC)backend/internal/domain/photoset/service.go
func (s *Service) ListByGallery(ctx context.Context, galleryID string) ([]*repository.PhotoSet, error) {
sets, err := s.photoSetRepo.ListByGallery(ctx, galleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to list photo sets")
}
return sets, nil
}photoset.Service.Update method · go · L99-L137 (39 LOC)backend/internal/domain/photoset/service.go
func (s *Service) Update(ctx context.Context, setID, photographerID, name, description string, displayOrder *int) (*repository.PhotoSet, error) {
// Get existing set
set, err := s.GetByID(ctx, setID)
if err != nil {
return nil, err
}
// Verify ownership through gallery
gallery, err := s.galleryRepo.GetByID(ctx, set.GalleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil || gallery.PhotographerID != photographerID {
return nil, errors.NewForbidden("You do not have permission to modify this set")
}
// Update fields
if name != "" {
set.Name = name
}
if description != "" {
set.Description = description
}
if displayOrder != nil {
set.DisplayOrder = *displayOrder
}
set.UpdatedAt = time.Now()
if err := s.photoSetRepo.Update(ctx, set); err != nil {
return nil, errors.Wrap(err, 500, "Failed to update photo set")
}
logger.Info("Photo set updated", map[string]interface{}{
"setId": setID,
"name": set.Namephotoset.Service.Delete method · go · L140-L171 (32 LOC)backend/internal/domain/photoset/service.go
func (s *Service) Delete(ctx context.Context, setID, photographerID string) error {
// Get existing set
set, err := s.GetByID(ctx, setID)
if err != nil {
return err
}
// Verify ownership through gallery
gallery, err := s.galleryRepo.GetByID(ctx, set.GalleryID)
if err != nil {
return errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil || gallery.PhotographerID != photographerID {
return errors.NewForbidden("You do not have permission to delete this set")
}
// Check if set has photos
if set.PhotoCount > 0 {
return errors.NewBadRequest("Cannot delete a set that contains photos. Move or delete the photos first.")
}
if err := s.photoSetRepo.Delete(ctx, setID); err != nil {
return errors.Wrap(err, 500, "Failed to delete photo set")
}
logger.Info("Photo set deleted", map[string]interface{}{
"setId": setID,
"galleryId": set.GalleryID,
})
return nil
}All rows scored by the Repobility analyzer (https://repobility.com)
photoset.Service.ReorderSets method · go · L174-L214 (41 LOC)backend/internal/domain/photoset/service.go
func (s *Service) ReorderSets(ctx context.Context, galleryID, photographerID string, setIDs []string) error {
// Verify gallery ownership
gallery, err := s.galleryRepo.GetByID(ctx, galleryID)
if err != nil {
return errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil {
return errors.NewNotFound("Gallery")
}
if gallery.PhotographerID != photographerID {
return errors.NewForbidden("You do not have permission to modify this gallery")
}
// Verify all sets belong to this gallery
existingSets, err := s.photoSetRepo.ListByGallery(ctx, galleryID)
if err != nil {
return errors.Wrap(err, 500, "Failed to list sets")
}
existingSetIDs := make(map[string]bool)
for _, set := range existingSets {
existingSetIDs[set.SetID] = true
}
for _, setID := range setIDs {
if !existingSetIDs[setID] {
return errors.NewBadRequest("Set " + setID + " does not belong to this gallery")
}
}
if err := s.photoSetRepo.ReorderSets(ctx, galleryID, setIDs); err != nil {
rphoto.BaseValidator.ValidateNext method · go · L38-L43 (6 LOC)backend/internal/domain/photo/validators.go
func (v *BaseValidator) ValidateNext(ctx context.Context, req interface{}) error {
if v.next != nil {
return v.next.Validate(ctx, req)
}
return nil
}photo.NewFileTypeValidator function · go · L52-L78 (27 LOC)backend/internal/domain/photo/validators.go
func NewFileTypeValidator() *FileTypeValidator {
return &FileTypeValidator{
AllowedTypes: []string{
// Standard image formats
"image/jpeg",
"image/jpg", // Non-standard but sometimes used
"image/png",
"image/gif",
"image/webp",
"image/heic",
"image/heif",
"image/tiff",
"image/bmp",
// RAW camera formats
"image/x-canon-cr2",
"image/x-nikon-nef",
"image/x-sony-arw",
"image/x-adobe-dng",
"image/x-olympus-orf",
"image/x-fuji-raf",
// Professional formats
"image/vnd.adobe.photoshop",
"application/postscript", // EPS
"image/svg+xml",
},
}
}photo.FileTypeValidator.Validate method · go · L81-L101 (21 LOC)backend/internal/domain/photo/validators.go
func (v *FileTypeValidator) Validate(ctx context.Context, req interface{}) error {
uploadReq, ok := req.(UploadRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
contentType := strings.ToLower(uploadReq.ContentType)
allowed := false
for _, t := range v.AllowedTypes {
if t == contentType {
allowed = true
break
}
}
if !allowed {
return errors.NewBadRequest("File type not allowed. Supported: JPEG, PNG, GIF, WebP, HEIC/HEIF, TIFF, BMP, RAW (CR2, NEF, ARW, DNG, ORF, RAF), PSD, EPS, SVG")
}
return v.ValidateNext(ctx, req)
}photo.NewFileExtensionValidator function · go · L110-L122 (13 LOC)backend/internal/domain/photo/validators.go
func NewFileExtensionValidator() *FileExtensionValidator {
return &FileExtensionValidator{
AllowedExtensions: []string{
// Standard image extensions
".jpg", ".jpeg", ".png", ".gif", ".webp", ".heic", ".heif",
".tiff", ".tif", ".bmp",
// RAW camera extensions
".cr2", ".nef", ".arw", ".dng", ".orf", ".raf",
// Professional format extensions
".psd", ".eps", ".svg",
},
}
}photo.FileExtensionValidator.Validate method · go · L125-L145 (21 LOC)backend/internal/domain/photo/validators.go
func (v *FileExtensionValidator) Validate(ctx context.Context, req interface{}) error {
uploadReq, ok := req.(UploadRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
ext := strings.ToLower(path.Ext(uploadReq.FileName))
allowed := false
for _, e := range v.AllowedExtensions {
if e == ext {
allowed = true
break
}
}
if !allowed {
return errors.NewBadRequest("File extension not allowed. Supported: .jpg, .jpeg, .png, .gif, .webp, .heic, .heif, .tiff, .tif, .bmp, .cr2, .nef, .arw, .dng, .orf, .raf, .psd, .eps, .svg")
}
return v.ValidateNext(ctx, req)
}photo.NewFileSizeValidator function · go · L155-L160 (6 LOC)backend/internal/domain/photo/validators.go
func NewFileSizeValidator() *FileSizeValidator {
return &FileSizeValidator{
MinSize: 1, // 1 byte minimum
MaxSize: 100 * 1024 * 1024, // 100 MB maximum (increased for RAW files)
}
}photo.FileSizeValidator.Validate method · go · L163-L178 (16 LOC)backend/internal/domain/photo/validators.go
func (v *FileSizeValidator) Validate(ctx context.Context, req interface{}) error {
uploadReq, ok := req.(UploadRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
if uploadReq.Size < v.MinSize {
return errors.NewBadRequest("File is empty")
}
if uploadReq.Size > v.MaxSize {
return errors.NewBadRequest("File size exceeds maximum limit of 100 MB")
}
return v.ValidateNext(ctx, req)
}Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
photo.FileNameValidator.Validate method · go · L194-L217 (24 LOC)backend/internal/domain/photo/validators.go
func (v *FileNameValidator) Validate(ctx context.Context, req interface{}) error {
uploadReq, ok := req.(UploadRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
if uploadReq.FileName == "" {
return errors.NewBadRequest("File name is required")
}
if len(uploadReq.FileName) > v.MaxLength {
return errors.NewBadRequest("File name is too long")
}
// Check for dangerous characters
dangerous := []string{"..", "/", "\\", "\x00"}
for _, d := range dangerous {
if strings.Contains(uploadReq.FileName, d) {
return errors.NewBadRequest("File name contains invalid characters")
}
}
return v.ValidateNext(ctx, req)
}photo.GalleryIDValidator.Validate method · go · L230-L241 (12 LOC)backend/internal/domain/photo/validators.go
func (v *GalleryIDValidator) Validate(ctx context.Context, req interface{}) error {
uploadReq, ok := req.(UploadRequest)
if !ok {
return v.ValidateNext(ctx, req)
}
if uploadReq.GalleryID == "" {
return errors.NewBadRequest("Gallery ID is required")
}
return v.ValidateNext(ctx, req)
}photo.NewUploadValidationChain function · go · L244-L255 (12 LOC)backend/internal/domain/photo/validators.go
func NewUploadValidationChain() Validator {
galleryID := NewGalleryIDValidator()
fileName := NewFileNameValidator()
fileType := NewFileTypeValidator()
fileExt := NewFileExtensionValidator()
fileSize := NewFileSizeValidator()
// Build the chain
galleryID.SetNext(fileName).SetNext(fileType).SetNext(fileExt).SetNext(fileSize)
return galleryID
}photoversion.NewService function · go · L31-L47 (17 LOC)backend/internal/domain/photoversion/service.go
func NewService(
versionRepo repository.PhotoVersionRepository,
photoRepo repository.PhotoRepository,
galleryRepo repository.GalleryRepository,
favoriteRepo repository.FavoriteRepository,
storage StorageService,
eventBus events.EventBus,
) *Service {
return &Service{
versionRepo: versionRepo,
photoRepo: photoRepo,
galleryRepo: galleryRepo,
favoriteRepo: favoriteRepo,
storage: storage,
eventBus: eventBus,
}
}photoversion.Service.GenerateReplaceUploadURL method · go · L67-L122 (56 LOC)backend/internal/domain/photoversion/service.go
func (s *Service) GenerateReplaceUploadURL(ctx context.Context, photoID, photographerID, fileName, mimeType string) (*ReplaceUploadURLResponse, error) {
// Get the photo
photo, err := s.photoRepo.GetByID(ctx, photoID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get photo")
}
if photo == nil {
return nil, errors.NewNotFound("Photo")
}
// Verify ownership through gallery
gallery, err := s.galleryRepo.GetByID(ctx, photo.GalleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil || gallery.PhotographerID != photographerID {
return nil, errors.NewForbidden("You do not have permission to replace this photo")
}
// Get the next version number
currentVersion := photo.CurrentVersion
if currentVersion == 0 {
currentVersion = 1
}
newVersion := currentVersion + 1
// Generate version ID
versionID := utils.GenerateID("ver")
// Generate presigned upload URL
uploadResp, err := s.storage.GenerateUploadURL(photoversion.Service.ReplacePhoto method · go · L125-L205 (81 LOC)backend/internal/domain/photoversion/service.go
func (s *Service) ReplacePhoto(ctx context.Context, photoID, photographerID string, input ReplacePhotoInput) (*repository.Photo, error) {
// Get the photo
photo, err := s.photoRepo.GetByID(ctx, photoID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get photo")
}
if photo == nil {
return nil, errors.NewNotFound("Photo")
}
// Verify ownership through gallery
gallery, err := s.galleryRepo.GetByID(ctx, photo.GalleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil || gallery.PhotographerID != photographerID {
return nil, errors.NewForbidden("You do not have permission to replace this photo")
}
// Archive the current version
oldVersion := photo.CurrentVersion
if oldVersion == 0 {
oldVersion = 1
}
version := &repository.PhotoVersion{
VersionID: utils.GenerateID("ver"),
PhotoID: photoID,
GalleryID: photo.GalleryID,
VersionNumber: oldVersion,
FileName: photo.Filephotoversion.Service.GetVersionHistory method · go · L208-L233 (26 LOC)backend/internal/domain/photoversion/service.go
func (s *Service) GetVersionHistory(ctx context.Context, photoID, photographerID string) ([]*repository.PhotoVersion, error) {
// Get the photo
photo, err := s.photoRepo.GetByID(ctx, photoID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get photo")
}
if photo == nil {
return nil, errors.NewNotFound("Photo")
}
// Verify ownership through gallery
gallery, err := s.galleryRepo.GetByID(ctx, photo.GalleryID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get gallery")
}
if gallery == nil || gallery.PhotographerID != photographerID {
return nil, errors.NewForbidden("You do not have permission to view this photo's history")
}
versions, err := s.versionRepo.ListByPhoto(ctx, photoID)
if err != nil {
return nil, errors.Wrap(err, 500, "Failed to get version history")
}
return versions, nil
}proofing.NewService function · go · L50-L66 (17 LOC)backend/internal/domain/proofing/service.go
func NewService(
galleryRepo GalleryRepository,
photoRepo PhotoRepository,
commentRepo repository.PhotoCommentRepository,
editRequestRepo repository.EditRequestRepository,
favoriteRepo FavoriteRepository,
subscriptionSvc SubscriptionService,
) *Service {
return &Service{
galleryRepo: galleryRepo,
photoRepo: photoRepo,
commentRepo: commentRepo,
editRequestRepo: editRequestRepo,
favoriteRepo: favoriteRepo,
subscriptionSvc: subscriptionSvc,
}
}Want this analysis on your repo? https://repobility.com/scan/
proofing.Service.TransitionToProofing method · go · L80-L111 (32 LOC)backend/internal/domain/proofing/service.go
func (s *Service) TransitionToProofing(ctx context.Context, req TransitionToProofingRequest) error {
// Check tier access
hasFeature, err := s.subscriptionSvc.HasFeature(ctx, req.PhotographerID, subscription.FeatureClientProofing)
if err != nil {
return fmt.Errorf("failed to check proofing feature access: %w", err)
}
if !hasFeature {
return errors.NewForbidden("Client proofing requires Pro or Studio tier")
}
// Get gallery and verify ownership
gallery, err := s.galleryRepo.GetByIDForPhotographer(ctx, req.GalleryID, req.PhotographerID)
if err != nil {
return fmt.Errorf("failed to get gallery: %w", err)
}
if gallery == nil {
return errors.NewNotFound("Gallery")
}
// Transition to proofing phase
gallery.Phase = repository.GalleryPhaseProofing
gallery.PhaseTransitionedAt = time.Now()
gallery.WatermarksEnabled = true
gallery.DownloadsEnabled = false
gallery.CommentsEnabled = true
if err := s.galleryRepo.Update(ctx, gallery); err != nil {
return fmt.Errorf("faproofing.Service.TransitionToUploading method · go · L114-L136 (23 LOC)backend/internal/domain/proofing/service.go
func (s *Service) TransitionToUploading(ctx context.Context, galleryID, photographerID string) error {
// Get gallery and verify ownership
gallery, err := s.galleryRepo.GetByIDForPhotographer(ctx, galleryID, photographerID)
if err != nil {
return fmt.Errorf("failed to get gallery: %w", err)
}
if gallery == nil {
return errors.NewNotFound("Gallery")
}
// Transition to uploading phase
gallery.Phase = repository.GalleryPhaseUploading
gallery.PhaseTransitionedAt = time.Now()
gallery.WatermarksEnabled = false
gallery.DownloadsEnabled = false
gallery.CommentsEnabled = false
if err := s.galleryRepo.Update(ctx, gallery); err != nil {
return fmt.Errorf("failed to transition to uploading: %w", err)
}
return nil
}proofing.Service.IsProofingEnabled method · go · L139-L149 (11 LOC)backend/internal/domain/proofing/service.go
func (s *Service) IsProofingEnabled(ctx context.Context, galleryID string) (bool, error) {
gallery, err := s.galleryRepo.GetByID(ctx, galleryID)
if err != nil {
return false, fmt.Errorf("failed to get gallery: %w", err)
}
if gallery == nil {
return false, errors.NewNotFound("Gallery")
}
return isProofingPhase(gallery.Phase), nil
}