Function bodies 1,000 total
main.main function · go · L22-L154 (133 LOC)backend/cmd/admin-fix/main.go
func main() {
userID := flag.String("user-id", "", "User ID to fix (required)")
stripeCustomerID := flag.String("stripe-customer-id", "", "Stripe customer ID to set (required)")
sync := flag.Bool("sync", true, "Sync from Stripe after fix")
flag.Parse()
if *userID == "" || *stripeCustomerID == "" {
fmt.Println("Usage: admin-fix -user-id=<user_id> -stripe-customer-id=<cus_xxx>")
flag.PrintDefaults()
os.Exit(1)
}
ctx := context.Background()
// Load configuration
cfg := appConfig.Load()
// Load AWS config
awsCfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(cfg.AWSRegion))
if err != nil {
logger.Error("Failed to load AWS config", map[string]interface{}{"error": err.Error()})
os.Exit(1)
}
// Initialize DynamoDB client
dynamoClient := dynamodb.NewFromConfig(awsCfg)
// Initialize subscription repository
subRepo := dynamodbRepo.NewSubscriptionRepository(
dynamoClient,
fmt.Sprintf("%s-subscriptions-%s", cfg.DynamoDBTablePrefix, cfg.APIStage),
fmtmain.main function · go · L54-L62 (9 LOC)backend/cmd/api/main.go
func main() {
app, err := initializeApp()
if err != nil {
logger.Error("Failed to initialize app", map[string]interface{}{"error": err.Error()})
os.Exit(1)
}
lambda.Start(app.handler)
}main.initializeApp function · go · L64-L107 (44 LOC)backend/cmd/api/main.go
func initializeApp() (*App, error) {
ctx := context.Background()
cfg := appConfig.Load()
awsCfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(cfg.AWSRegion))
if err != nil {
return nil, fmt.Errorf("failed to load AWS config: %w", err)
}
// Load secrets from Secrets Manager
secretsARN := os.Getenv("SECRETS_ARN")
if secretsARN == "" {
return nil, fmt.Errorf("SECRETS_ARN environment variable is required")
}
secretsMgr := secrets.NewManager(awsCfg, secretsARN)
appSecrets, err := secretsMgr.Load(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load secrets: %w", err)
}
logger.Info("Secrets loaded from Secrets Manager", nil)
// Initialize infrastructure
dynamoClient := dynamodb.NewFromConfig(awsCfg)
s3Client := s3.NewFromConfig(awsCfg)
sqsClient := sqs.NewFromConfig(awsCfg)
// Initialize repositories
repos := initRepositories(dynamoClient, cfg)
// Initialize services
services := initServices(s3Client, sqsClient, repos, cfg, appSecrets)
// main.initRepositories function · go · L124-L145 (22 LOC)backend/cmd/api/main.go
func initRepositories(client *dynamodb.Client, cfg *appConfig.Config) *repositories {
prefix := cfg.DynamoDBTablePrefix
stage := cfg.APIStage
return &repositories{
gallery: dynamodbRepo.NewGalleryRepository(client, fmt.Sprintf("%s-galleries-%s", prefix, stage)),
photo: dynamodbRepo.NewPhotoRepository(client, fmt.Sprintf("%s-photos-%s", prefix, stage)),
favorite: dynamodbRepo.NewFavoriteRepository(client, fmt.Sprintf("%s-favorites-%s", prefix, stage)),
session: dynamodbRepo.NewClientSessionRepository(client, fmt.Sprintf("%s-sessions-%s", prefix, stage)),
photographer: dynamodbRepo.NewPhotographerRepository(client, fmt.Sprintf("%s-photographers-%s", prefix, stage)),
subscription: dynamodbRepo.NewSubscriptionRepository(
client,
fmt.Sprintf("%s-subscriptions-%s", prefix, stage),
fmt.Sprintf("%s-subscription-history-%s", prefix, stage),
),
zipJob: dynamodbRepo.NewZipJobRepository(client, fmt.Sprintf("%s-zip-jobs-%s", prefix, stagmain.initServices function · go · L166-L293 (128 LOC)backend/cmd/api/main.go
func initServices(s3Client *s3.Client, sqsClient *sqs.Client, repos *repositories, cfg *appConfig.Config, appSecrets *secrets.AppSecrets) *services {
storageService := storage.NewService(
s3Client,
cfg.S3BucketOriginal,
cfg.S3BucketOptimized,
cfg.S3BucketThumbnail,
time.Duration(cfg.SignedURLExpiration)*time.Hour,
)
// JWT secret is loaded from Secrets Manager (validated during load)
jwtSecret := appSecrets.JWTSecret
// Get base domain from environment or use default
baseDomain := os.Getenv("BASE_DOMAIN")
if baseDomain == "" {
baseDomain = "framefocal.com"
}
// Initialize event bus
eventBus := pkgEvents.NewEventBus()
// Initialize analytics service
photographerRepoAdapter := &analytics.PhotographerRepoAdapter{
GetByID: func(ctx context.Context, userID string) (*photographer.Photographer, error) {
return repos.photographer.GetByID(ctx, userID)
},
}
analyticsService := analytics.NewService(
photographerRepoAdapter,
repos.gallery,
repos.photo,
main.buildRouter function · go · L295-L482 (188 LOC)backend/cmd/api/main.go
func buildRouter(svc *services, repos *repositories, cfg *appConfig.Config) *api.Router {
// Get base domain
baseDomain := os.Getenv("BASE_DOMAIN")
if baseDomain == "" {
baseDomain = "framefocal.com"
}
// Initialize tier gate middleware for limit enforcement
var tierGate *middleware.TierGateMiddleware
if svc.subscription != nil {
tierGate = middleware.NewTierGateMiddleware(svc.subscription)
}
// Initialize handlers
authHandler := handlers.NewAuthHandler(repos.photographer)
galleryHandler := handlers.NewGalleryHandler(svc.gallery, tierGate, repos.photographer, svc.subscription)
photoHandler := handlers.NewPhotoHandler(svc.photo, tierGate, repos.photographer, repos.gallery)
clientHandler := handlers.NewClientHandler(svc.gallery, svc.photo, svc.session, svc.zipJob, svc.subscription).
WithCookieConfig(cfg.CookieDomain, cfg.SessionTTLHours)
domainHandler := handlers.NewDomainHandler(svc.domain)
portalHandler := handlers.NewPortalHandler(svc.domain, svc.gallery, repos.main.App.handler method · go · L484-L509 (26 LOC)backend/cmd/api/main.go
func (app *App) handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
logger.Info("Received request", map[string]interface{}{
"method": req.HTTPMethod,
"path": req.Path,
})
// Get request origin from headers (case-insensitive)
requestOrigin := req.Headers["Origin"]
if requestOrigin == "" {
requestOrigin = req.Headers["origin"]
}
// Parse allowed origins from config (comma-separated string)
allowedOrigins := middleware.ParseAllowedOrigins(app.config.AllowedOrigins)
if req.HTTPMethod == "OPTIONS" {
return middleware.HandlePreflight(requestOrigin, allowedOrigins), nil
}
response, err := app.router.HandleLambda(ctx, req)
if err != nil {
logger.Error("Request failed", map[string]interface{}{"error": err.Error()})
}
return middleware.AddCORSHeaders(response, requestOrigin, allowedOrigins), nil
}Repobility · severity-and-effort ranking · https://repobility.com
main.wrapHandler function · go · L512-L556 (45 LOC)backend/cmd/api/main.go
func wrapHandler(fn http.HandlerFunc) api.Handler {
return func(req *api.Request) (*api.Response, error) {
rw := &responseCapture{statusCode: http.StatusOK, headers: http.Header{}}
// Build HTTP request from api.Request
httpReq, err := http.NewRequestWithContext(req.Context, "GET", req.Path, strings.NewReader(req.Body))
if err != nil {
return api.InternalError("failed to create request"), err
}
for k, v := range req.Headers {
httpReq.Header.Set(k, v)
}
// Store path params in context
ctx := httpReq.Context()
for k, v := range req.PathParams {
ctx = context.WithValue(ctx, k, v)
}
httpReq = httpReq.WithContext(ctx)
fn(rw, httpReq)
headers := make(map[string]string)
for k, vals := range rw.headers {
if len(vals) > 0 {
headers[k] = vals[0]
}
}
// Parse the JSON body back into an interface{} to avoid double-encoding
// when HandleLambda calls json.Marshal on the response body
var body interface{}
if rw.body != "" {
if err :=main.authMiddlewareWrapper function · go · L558-L572 (15 LOC)backend/cmd/api/main.go
func authMiddlewareWrapper(m *middleware.AuthMiddleware) api.Middleware {
return func(next api.Handler) api.Handler {
return func(req *api.Request) (*api.Response, error) {
// Simulate auth verification
ctx, err := m.VerifyPhotographerToken(req.Context, events.APIGatewayProxyRequest{
Headers: req.Headers,
})
if err != nil {
return api.Unauthorized(err.Error()), nil
}
req.Context = ctx
return next(req)
}
}
}main.sessionMiddlewareWrapper function · go · L574-L587 (14 LOC)backend/cmd/api/main.go
func sessionMiddlewareWrapper(m *middleware.SessionMiddleware) api.Middleware {
return func(next api.Handler) api.Handler {
return func(req *api.Request) (*api.Response, error) {
ctx, err := m.VerifyClientSession(req.Context, events.APIGatewayProxyRequest{
Headers: req.Headers,
})
if err != nil {
return api.Unauthorized(err.Error()), nil
}
req.Context = ctx
return next(req)
}
}
}main.domainMiddlewareWrapper function · go · L589-L603 (15 LOC)backend/cmd/api/main.go
func domainMiddlewareWrapper(m *middleware.DomainMiddleware) api.Middleware {
return func(next api.Handler) api.Handler {
return func(req *api.Request) (*api.Response, error) {
ctx, err := m.ResolvePhotographer(req.Context, events.APIGatewayProxyRequest{
Headers: req.Headers,
})
if err != nil {
// Domain resolution errors are not fatal - continue without photographer context
logger.Warn("Domain resolution failed", map[string]interface{}{"error": err.Error()})
}
req.Context = ctx
return next(req)
}
}
}main.main function · go · L51-L189 (139 LOC)backend/cmd/api/main-local.go
func main() {
app, err := initializeLocalApp()
if err != nil {
logger.Error("Failed to initialize app", map[string]interface{}{"error": err.Error()})
os.Exit(1)
}
// Set up HTTP server
mux := http.NewServeMux()
// Health check
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "healthy"})
})
// API routes - wrap with CORS middleware
mux.HandleFunc("/api/v1/", app.corsMiddleware(app.router))
// Get port from environment or use default
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
logger.Info("Starting local development server", map[string]interface{}{
"port": port,
"url": fmt.Sprintf("http://localhost:%s", port),
})
log.Fatal(http.ListenAndServe(":"+port, mux))
}
func initializeLocalApp() (*LocalApp, error) {
ctx := context.Background()
// Load configuration
cfg := appConfig.Load()
logger.Info("Application initialized", map[strmain.initializeLocalApp function · go · L84-L170 (87 LOC)backend/cmd/api/main-local.go
func initializeLocalApp() (*LocalApp, error) {
ctx := context.Background()
// Load configuration
cfg := appConfig.Load()
logger.Info("Application initialized", map[string]interface{}{
"region": cfg.AWSRegion,
"stage": cfg.APIStage,
})
// Load AWS config
awsCfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(cfg.AWSRegion))
if err != nil {
return nil, fmt.Errorf("failed to load AWS config: %w", err)
}
// Initialize AWS clients
dynamoClient := dynamodb.NewFromConfig(awsCfg)
s3Client := s3.NewFromConfig(awsCfg)
// Initialize repositories
galleryRepo := dynamodbRepo.NewGalleryRepository(
dynamoClient,
fmt.Sprintf("%s-galleries-%s", cfg.DynamoDBTablePrefix, cfg.APIStage),
)
photoRepo := dynamodbRepo.NewPhotoRepository(
dynamoClient,
fmt.Sprintf("%s-photos-%s", cfg.DynamoDBTablePrefix, cfg.APIStage),
)
favoriteRepo := dynamodbRepo.NewFavoriteRepository(
dynamoClient,
fmt.Sprintf("%s-favorites-%s", cfg.DynamoDBTablePrefix, cfg.APIStage),
)
smain.LocalApp.router method · go · L192-L252 (61 LOC)backend/cmd/api/main-local.go
func (app *LocalApp) router(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/api/v1")
// Route matching
switch {
// Gallery routes (photographer - requires auth)
case path == "/galleries" && r.Method == "GET":
app.withAuth(app.galleryHandler.ListGalleries)(w, r)
case path == "/galleries" && r.Method == "POST":
app.withAuth(app.galleryHandler.CreateGallery)(w, r)
case strings.HasPrefix(path, "/galleries/") && !strings.Contains(path, "/photos"):
galleryID := strings.TrimPrefix(path, "/galleries/")
if strings.Contains(galleryID, "/") {
parts := strings.Split(galleryID, "/")
galleryID = parts[0]
}
switch r.Method {
case "GET":
app.withAuth(app.galleryHandler.GetGallery)(w, r)
case "PUT":
app.withAuth(app.galleryHandler.UpdateGallery)(w, r)
case "DELETE":
app.withAuth(app.galleryHandler.DeleteGallery)(w, r)
}
// Photo routes (photographer - requires auth)
case strings.HasPrefix(path, "/galleries/") && strings.Comain.LocalApp.withAuth method · go · L255-L268 (14 LOC)backend/cmd/api/main-local.go
func (app *LocalApp) withAuth(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Extract user ID from auth header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// For local dev, you can bypass actual token validation
// In production, this would validate the JWT
handler(w, r)
}
}Repobility analyzer · published findings · https://repobility.com
main.LocalApp.withSession method · go · L271-L282 (12 LOC)backend/cmd/api/main-local.go
func (app *LocalApp) withSession(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Extract session token from auth header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
handler(w, r)
}
}main.main function · go · L62-L99 (38 LOC)backend/cmd/dlq-reprocessor/main.go
func main() {
// Load configuration from environment variables
cfg := &Config{
AWSRegion: os.Getenv("AWS_REGION_NAME"),
DynamoDBTablePrefix: os.Getenv("DYNAMODB_TABLE_PREFIX"),
S3BucketOriginal: os.Getenv("S3_BUCKET_ORIGINAL"),
S3BucketOptimized: os.Getenv("S3_BUCKET_OPTIMIZED"),
S3BucketThumbnail: os.Getenv("S3_BUCKET_THUMBNAIL"),
ProcessingQueueURL: os.Getenv("PROCESSING_QUEUE_URL"),
APIStage: os.Getenv("STAGE"),
}
// Initialize AWS SDK
awsCfg, err := config.LoadDefaultConfig(context.Background(),
config.WithRegion(cfg.AWSRegion),
)
if err != nil {
log.Fatalf("Failed to load AWS config: %v", err)
}
// Initialize clients
sqsClient := sqs.NewFromConfig(awsCfg)
dynamoClient := dynamodb.NewFromConfig(awsCfg)
// Initialize repositories
photoRepo := dynamodbRepo.NewPhotoRepository(
dynamoClient,
fmt.Sprintf("%s-photos-%s", cfg.DynamoDBTablePrefix, cfg.APIStage),
)
app := &App{
cfg: cfg,
sqsClient: sqsClient,
main.App.handleDLQEvent method · go · L102-L141 (40 LOC)backend/cmd/dlq-reprocessor/main.go
func (app *App) handleDLQEvent(ctx context.Context, sqsEvent events.SQSEvent) error {
log.Printf("Processing %d DLQ event records", len(sqsEvent.Records))
for _, sqsRecord := range sqsEvent.Records {
// Extract retry metadata from message attributes
retryMetadata := app.extractRetryMetadata(sqsRecord)
log.Printf("Processing DLQ message, attempt %d/%d", retryMetadata.AttemptNumber+1, maxRetryAttempts)
// Check if we've exceeded max retry attempts
if retryMetadata.AttemptNumber >= maxRetryAttempts {
log.Printf("Max retry attempts reached for message. Marking as permanently failed.")
if err := app.markAsPermantlyFailed(ctx, sqsRecord.Body, retryMetadata); err != nil {
log.Printf("Failed to mark photo as permanently failed: %v", err)
}
continue
}
// Calculate exponential backoff delay
delaySeconds := calculateBackoffDelay(retryMetadata.AttemptNumber)
log.Printf("Scheduling retry with %d second delay (exponential backoff)", delaySeconds)
// Update main.App.extractRetryMetadata method · go · L144-L184 (41 LOC)backend/cmd/dlq-reprocessor/main.go
func (app *App) extractRetryMetadata(record events.SQSMessage) RetryMetadata {
metadata := RetryMetadata{
AttemptNumber: 0,
}
// Check for retry attempt count in message attributes
if attemptAttr, ok := record.MessageAttributes["RetryAttempt"]; ok {
if attemptAttr.StringValue != nil {
if attempt, err := strconv.Atoi(*attemptAttr.StringValue); err == nil {
metadata.AttemptNumber = attempt
}
}
}
// Check for first failed timestamp
if firstFailedAttr, ok := record.MessageAttributes["FirstFailedAt"]; ok {
if firstFailedAttr.StringValue != nil {
if t, err := time.Parse(time.RFC3339, *firstFailedAttr.StringValue); err == nil {
metadata.FirstFailedAt = t
}
}
}
// Check for last retry timestamp
if lastRetryAttr, ok := record.MessageAttributes["LastRetryAt"]; ok {
if lastRetryAttr.StringValue != nil {
if t, err := time.Parse(time.RFC3339, *lastRetryAttr.StringValue); err == nil {
metadata.LastRetryAt = t
}
}
}
// Check for error messagmain.calculateBackoffDelay function · go · L188-L202 (15 LOC)backend/cmd/dlq-reprocessor/main.go
func calculateBackoffDelay(attemptNumber int) int {
delays := []int{
60, // 1 minute
300, // 5 minutes
900, // 15 minutes
3600, // 1 hour
14400, // 4 hours
}
if attemptNumber >= len(delays) {
return delays[len(delays)-1]
}
return delays[attemptNumber]
}main.App.requeueWithDelay method · go · L205-L238 (34 LOC)backend/cmd/dlq-reprocessor/main.go
func (app *App) requeueWithDelay(ctx context.Context, messageBody string, metadata RetryMetadata, delaySeconds int) error {
// Prepare message attributes with retry metadata
messageAttributes := map[string]types.MessageAttributeValue{
"RetryAttempt": {
DataType: aws.String("Number"),
StringValue: aws.String(strconv.Itoa(metadata.AttemptNumber)),
},
"FirstFailedAt": {
DataType: aws.String("String"),
StringValue: aws.String(metadata.FirstFailedAt.Format(time.RFC3339)),
},
"LastRetryAt": {
DataType: aws.String("String"),
StringValue: aws.String(metadata.LastRetryAt.Format(time.RFC3339)),
},
}
if metadata.ErrorMessage != "" {
messageAttributes["ErrorMessage"] = types.MessageAttributeValue{
DataType: aws.String("String"),
StringValue: aws.String(metadata.ErrorMessage),
}
}
// Send message to processing queue with delay
_, err := app.sqsClient.SendMessage(ctx, &sqs.SendMessageInput{
QueueUrl: aws.String(app.cfg.Processinmain.App.markAsPermantlyFailed method · go · L241-L294 (54 LOC)backend/cmd/dlq-reprocessor/main.go
func (app *App) markAsPermantlyFailed(ctx context.Context, messageBody string, metadata RetryMetadata) error {
// Parse S3 event from message body
var s3Event events.S3Event
if err := json.Unmarshal([]byte(messageBody), &s3Event); err != nil {
return fmt.Errorf("failed to unmarshal S3 event: %w", err)
}
for _, record := range s3Event.Records {
objectKey := record.S3.Object.Key
// Extract photo ID from the object key
photoID, err := extractPhotoID(objectKey)
if err != nil {
log.Printf("Failed to extract photo ID from key %s: %v", objectKey, err)
continue
}
// Get photo from database
photo, err := app.photoRepo.GetByID(ctx, photoID)
if err != nil {
log.Printf("Failed to get photo from database: %v", err)
continue
}
if photo == nil {
log.Printf("Photo not found in database: %s", photoID)
continue
}
// Update photo status to permanently failed
photo.ProcessingStatus = "failed_permanent"
now := time.Now()
photo.ProcessedAt = &now
main.extractPhotoID function · go · L298-L311 (14 LOC)backend/cmd/dlq-reprocessor/main.go
func extractPhotoID(objectKey string) (string, error) {
parts := strings.Split(objectKey, "/")
if len(parts) < 3 {
return "", fmt.Errorf("invalid object key format: %s", objectKey)
}
// The photoID is the second part: galleryID/photoID/filename
photoID := parts[1]
if !strings.HasPrefix(photoID, "photo_") {
return "", fmt.Errorf("invalid photo ID format in key: %s", objectKey)
}
return photoID, nil
}All rows scored by the Repobility analyzer (https://repobility.com)
main.main function · go · L58-L64 (7 LOC)backend/cmd/processor/main.go
func main() {
app, err := initApp()
if err != nil {
log.Fatalf("Failed to initialize: %v", err)
}
lambda.Start(app.handleS3Event)
}main.initApp function · go · L66-L87 (22 LOC)backend/cmd/processor/main.go
func initApp() (*App, error) {
cfg, err := appconfig.NewProcessorConfigBuilder().FromEnvironment().Build()
if err != nil {
return nil, err
}
awsCfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(cfg.AWSRegion))
if err != nil {
return nil, err
}
dynamoClient := dynamodb.NewFromConfig(awsCfg)
return &App{
cfg: cfg,
s3Client: s3.NewFromConfig(awsCfg),
photoRepo: dynamodbRepo.NewPhotoRepository(dynamoClient, cfg.PhotosTableName()),
galleryRepo: dynamodbRepo.NewGalleryRepository(dynamoClient, cfg.GalleriesTableName()),
photographerRepo: dynamodbRepo.NewPhotographerRepository(dynamoClient, cfg.PhotographersTableName()),
processor: image.NewProcessor(),
}, nil
}main.App.handleS3Event method · go · L90-L126 (37 LOC)backend/cmd/processor/main.go
func (app *App) handleS3Event(ctx context.Context, sqsEvent events.SQSEvent) error {
log.Printf("Processing %d SQS records", len(sqsEvent.Records))
for _, sqsRecord := range sqsEvent.Records {
var s3Event events.S3Event
if err := json.Unmarshal([]byte(sqsRecord.Body), &s3Event); err != nil {
log.Printf("Failed to unmarshal S3 event: %v", err)
continue
}
for _, record := range s3Event.Records {
bucket := record.S3.Bucket.Name
// S3 event keys are URL-encoded (spaces become '+' or '%20')
// We must decode them to get the actual S3 key
key, err := url.QueryUnescape(record.S3.Object.Key)
if err != nil {
log.Printf("Failed to decode S3 key %s: %v", record.S3.Object.Key, err)
continue
}
log.Printf("Processing: %s/%s", bucket, key)
parsed, err := s3key.Parse(key)
if err != nil {
log.Printf("Invalid key %s: %v", key, err)
continue
}
if err := app.processPhoto(ctx, parsed, bucket, key); err != nil {
log.Printf("Failed to pmain.App.processPhoto method · go · L129-L228 (100 LOC)backend/cmd/processor/main.go
func (app *App) processPhoto(ctx context.Context, key *s3key.Key, bucket, objectKey string) error {
// Download from S3
imageData, contentLength, err := app.downloadImage(ctx, bucket, objectKey)
if err != nil {
return err
}
// Get dimensions and metadata
width, height, _ := app.processor.GetImageDimensions(bytes.NewReader(imageData))
metadata, _ := app.processor.ExtractEXIF(bytes.NewReader(imageData))
if metadata == nil {
metadata = &image.ImageMetadata{}
}
if metadata.Width == 0 {
metadata.Width = width
}
if metadata.Height == 0 {
metadata.Height = height
}
// Fetch gallery for watermark settings
gallery, err := app.galleryRepo.GetByID(ctx, key.GalleryID)
if err != nil {
log.Printf("Warning: Failed to fetch gallery %s for watermark settings: %v", key.GalleryID, err)
// Continue processing without watermark
}
// Fetch photographer for watermark text (custom domain/subdomain priority)
var photographerData *photographer.Photographer
if gallery != nil {
main.App.downloadImage method · go · L230-L249 (20 LOC)backend/cmd/processor/main.go
func (app *App) downloadImage(ctx context.Context, bucket, key string) (data []byte, size int64, err error) {
output, err := app.s3Client.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
if err != nil {
return nil, 0, fmt.Errorf("S3 download failed: %w", err)
}
defer output.Body.Close()
data, err = io.ReadAll(output.Body)
if err != nil {
return nil, 0, fmt.Errorf("read failed: %w", err)
}
if output.ContentLength != nil {
size = *output.ContentLength
}
return data, size, nil
}main.App.updatePhotoRecord method · go · L251-L294 (44 LOC)backend/cmd/processor/main.go
func (app *App) updatePhotoRecord(ctx context.Context, key *s3key.Key, objectKey, thumbnailKey, optimizedKey string, metadata *image.ImageMetadata, size int64) error {
photo, _ := app.photoRepo.GetByID(ctx, key.PhotoID)
isNew := photo == nil
if isNew {
now := time.Now()
photo = &repository.Photo{
PhotoID: key.PhotoID,
GalleryID: key.GalleryID,
FileName: key.FileName,
OriginalKey: objectKey,
MimeType: s3key.GetMimeType(key.Extension),
Size: size,
Width: metadata.Width,
Height: metadata.Height,
UploadedAt: now,
ProcessingStatus: "processing",
Metadata: make(map[string]string),
}
if err := app.photoRepo.Create(ctx, photo); err != nil {
return fmt.Errorf("create photo failed: %w", err)
}
}
// Update with processing results
photo.OptimizedKey = optimizedKey
photo.ThumbnailKey = thumbnailKey
photo.Width = metadata.Width
photo.Height = metadata.Height
pmain.App.storeEXIFMetadata method · go · L296-L322 (27 LOC)backend/cmd/processor/main.go
func (app *App) storeEXIFMetadata(photo *repository.Photo, m *image.ImageMetadata) {
if photo.Metadata == nil {
photo.Metadata = make(map[string]string)
}
if m.CameraModel != "" {
photo.Metadata["cameraModel"] = m.CameraModel
}
if m.DateTaken != "" {
photo.Metadata["dateTaken"] = m.DateTaken
}
if m.ISO > 0 {
photo.Metadata["iso"] = fmt.Sprintf("%d", m.ISO)
}
if m.Aperture != "" {
photo.Metadata["aperture"] = m.Aperture
}
if m.ShutterSpeed != "" {
photo.Metadata["shutterSpeed"] = m.ShutterSpeed
}
if m.FocalLength != "" {
photo.Metadata["focalLength"] = m.FocalLength
}
if m.GPS != nil {
gpsData, _ := json.Marshal(m.GPS)
photo.Metadata["gps"] = string(gpsData)
}
}main.App.uploadToS3 method · go · L324-L332 (9 LOC)backend/cmd/processor/main.go
func (app *App) uploadToS3(ctx context.Context, bucket, key string, data []byte, contentType string) error {
_, err := app.s3Client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
Body: bytes.NewReader(data),
ContentType: aws.String(contentType),
})
return err
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
main.App.updatePhotoStatus method · go · L334-L345 (12 LOC)backend/cmd/processor/main.go
func (app *App) updatePhotoStatus(ctx context.Context, photoID, status string) {
photo, err := app.photoRepo.GetByID(ctx, photoID)
if err != nil || photo == nil {
return
}
photo.ProcessingStatus = status
if status == "completed" || status == "failed" {
now := time.Now()
photo.ProcessedAt = &now
}
_ = app.photoRepo.Update(ctx, photo)
}main.App.generateThumbnail method · go · L349-L371 (23 LOC)backend/cmd/processor/main.go
func (app *App) generateThumbnail(imageData io.Reader, gallery *repository.Gallery) ([]byte, error) {
// Use the processor's orientation-aware decoder
img, _, err := app.processor.DecodeWithOrientation(imageData)
if err != nil {
return nil, fmt.Errorf("decode failed: %w", err)
}
// Create thumbnail with smart cropping (center)
thumbnail := imaging.Fill(img, image.ThumbnailWidth, image.ThumbnailHeight, imaging.Center, imaging.Lanczos)
// Apply photographer's custom watermark as a banner if enabled
var result imageType.Image = thumbnail
if gallery != nil && gallery.WatermarksEnabled && gallery.WatermarkText != "" {
result = app.processor.ApplyWatermarkBanner(thumbnail, gallery.WatermarkText)
}
// Encode to JPEG
var buf bytes.Buffer
if err := imaging.Encode(&buf, result, imaging.JPEG, imaging.JPEGQuality(image.ThumbnailJPEGQuality)); err != nil {
return nil, fmt.Errorf("encode failed: %w", err)
}
return buf.Bytes(), nil
}main.App.generateOptimized method · go · L373-L406 (34 LOC)backend/cmd/processor/main.go
func (app *App) generateOptimized(imageData io.Reader, gallery *repository.Gallery) ([]byte, error) {
// Use the processor's orientation-aware decoder
img, _, err := app.processor.DecodeWithOrientation(imageData)
if err != nil {
return nil, fmt.Errorf("decode failed: %w", err)
}
bounds := img.Bounds()
var result imageType.Image
if bounds.Dx() > image.OptimizedMaxWidth || bounds.Dy() > image.OptimizedMaxHeight {
result = imaging.Fit(img, image.OptimizedMaxWidth, image.OptimizedMaxHeight, imaging.Lanczos)
} else {
// Ensure consistent NRGBA format even for images that don't need resizing
result = imaging.Clone(img)
}
if gallery != nil && gallery.WatermarksEnabled && gallery.WatermarkText != "" {
position := gallery.WatermarkPosition
if position == "" {
position = "bottom-right"
}
result = app.processor.ApplyWatermark(result, image.WatermarkOptions{
Text: gallery.WatermarkText,
Position: position,
})
}
// Encode with configurable quality
var main.App.generateWatermarkedThumbnail method · go · L410-L440 (31 LOC)backend/cmd/processor/main.go
func (app *App) generateWatermarkedThumbnail(imageData io.Reader, watermarkText string, overlay imageType.Image) ([]byte, error) {
// Use the processor's orientation-aware decoder
img, _, err := app.processor.DecodeWithOrientation(imageData)
if err != nil {
return nil, fmt.Errorf("decode failed: %w", err)
}
// Create thumbnail with smart cropping (center)
thumbnail := imaging.Fill(img, image.ThumbnailWidth, image.ThumbnailHeight, imaging.Center, imaging.Lanczos)
// Apply watermark banner
var watermarked imageType.Image
switch {
case overlay != nil:
// Use the photographer's uploaded image watermark
watermarked = app.processor.ApplyImageWatermarkBanner(thumbnail, overlay)
case watermarkText == "FrameFocal":
// Use the cached FrameFocal brand watermark for efficiency
watermarked = app.processor.ApplyBrandWatermarkBanner(thumbnail)
default:
// Use dynamic text watermark for custom domain/subdomain
watermarked = app.processor.ApplyTextWatermarkBanner(thumbnail, main.App.generateWatermarkedOptimized method · go · L444-L479 (36 LOC)backend/cmd/processor/main.go
func (app *App) generateWatermarkedOptimized(imageData io.Reader, watermarkText string, overlay imageType.Image) ([]byte, error) {
// Use the processor's orientation-aware decoder
img, _, err := app.processor.DecodeWithOrientation(imageData)
if err != nil {
return nil, fmt.Errorf("decode failed: %w", err)
}
bounds := img.Bounds()
var result imageType.Image
if bounds.Dx() > image.OptimizedMaxWidth || bounds.Dy() > image.OptimizedMaxHeight {
result = imaging.Fit(img, image.OptimizedMaxWidth, image.OptimizedMaxHeight, imaging.Lanczos)
} else {
result = imaging.Clone(img)
}
// Apply watermark
var watermarked imageType.Image
switch {
case overlay != nil:
// Use the photographer's uploaded image watermark
watermarked = app.processor.ApplyImageWatermark(result, overlay, "bottom-right")
case watermarkText == "FrameFocal":
// Use the cached FrameFocal brand watermark for efficiency
watermarked = app.processor.ApplyBrandWatermark(result, "bottom-right")
default:
main.App.fetchWatermarkOverlay method · go · L482-L508 (27 LOC)backend/cmd/processor/main.go
func (app *App) fetchWatermarkOverlay(ctx context.Context, s3Key string) (imageType.Image, error) {
const maxWatermarkFileSize = 5 * 1024 * 1024 // 5MB
output, err := app.s3Client.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(app.cfg.S3BucketOriginal),
Key: aws.String(s3Key),
})
if err != nil {
return nil, fmt.Errorf("failed to download watermark overlay: %w", err)
}
defer output.Body.Close()
data, err := io.ReadAll(io.LimitReader(output.Body, maxWatermarkFileSize+1))
if err != nil {
return nil, fmt.Errorf("failed to read watermark overlay: %w", err)
}
if len(data) > maxWatermarkFileSize {
return nil, fmt.Errorf("watermark overlay exceeds maximum size of %d bytes", maxWatermarkFileSize)
}
img, _, err := imageType.Decode(bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("failed to decode watermark overlay: %w", err)
}
return img, nil
}main.main function · go · L16-L108 (93 LOC)backend/cmd/processor/manual_check.go
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run manual_test.go <image_path>")
fmt.Println("\nThis will generate thumbnail and optimized versions of your image.")
os.Exit(1)
}
imagePath := os.Args[1]
fmt.Printf("Processing: %s\n\n", imagePath)
// Read the image
data, err := os.ReadFile(imagePath)
if err != nil {
fmt.Printf("Error reading file: %v\n", err)
os.Exit(1)
}
fmt.Printf("Original size: %d bytes\n", len(data))
processor := image.NewProcessor()
// Test DecodeWithOrientation
img, format, err := processor.DecodeWithOrientation(bytes.NewReader(data))
if err != nil {
fmt.Printf("Error decoding: %v\n", err)
os.Exit(1)
}
bounds := img.Bounds()
fmt.Printf("Format: %s, Dimensions: %dx%d (after orientation correction)\n\n", format, bounds.Dx(), bounds.Dy())
// Extract EXIF
metadata, _ := processor.ExtractEXIF(bytes.NewReader(data))
fmt.Println("EXIF Metadata:")
if metadata.CameraModel != "" {
fmt.Printf(" Camera: %s\n", metadata.Cammain.main function · go · L26-L33 (8 LOC)backend/cmd/scheduler/main.go
func main() {
app, err := initializeApp()
if err != nil {
log.Fatalf("Failed to initialize app: %v", err)
}
lambda.Start(app.handleScheduledEvent)
}Repobility · severity-and-effort ranking · https://repobility.com
main.initializeApp function · go · L35-L79 (45 LOC)backend/cmd/scheduler/main.go
func initializeApp() (*SchedulerApp, error) {
ctx := context.Background()
// Load AWS config
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load AWS config: %w", err)
}
// Initialize AWS clients
dynamoClient := dynamodb.NewFromConfig(cfg)
s3Client := s3.NewFromConfig(cfg)
// Get environment variables
tablePrefix := os.Getenv("DYNAMODB_TABLE_PREFIX")
stage := os.Getenv("STAGE")
originalBucket := os.Getenv("S3_BUCKET_ORIGINAL")
optimizedBucket := os.Getenv("S3_BUCKET_OPTIMIZED")
thumbnailBucket := os.Getenv("S3_BUCKET_THUMBNAIL")
if tablePrefix == "" {
tablePrefix = "photographer-gallery"
}
if stage == "" {
stage = "dev"
}
// Initialize repositories
galleriesTable := fmt.Sprintf("%s-galleries-%s", tablePrefix, stage)
photosTable := fmt.Sprintf("%s-photos-%s", tablePrefix, stage)
galleryRepo := dynamodbRepo.NewGalleryRepository(dynamoClient, galleriesTable)
photoRepo := dynamodbRepo.NewPhotoRepository(dynamoClmain.SchedulerApp.handleScheduledEvent method · go · L82-L102 (21 LOC)backend/cmd/scheduler/main.go
func (app *SchedulerApp) handleScheduledEvent(ctx context.Context, _ interface{}) error {
startTime := time.Now().UTC()
log.Printf("=== SCHEDULER START === Time: %v", startTime.Format(time.RFC3339))
log.Printf("Environment: STAGE=%s, TABLE_PREFIX=%s", os.Getenv("STAGE"), os.Getenv("DYNAMODB_TABLE_PREFIX"))
// Process scheduled galleries first (publishes galleries where publishAt < now)
if err := app.galleryService.ProcessScheduledGalleries(ctx, 100); err != nil {
log.Printf("ERROR: Failed to process scheduled galleries: %v", err)
// Continue to process expired galleries even if scheduled processing fails
}
// Process expired galleries (deletes galleries where expiresAt < now)
if err := app.galleryService.ProcessExpiredGalleries(ctx, 100); err != nil {
log.Printf("ERROR: Failed to process expired galleries: %v", err)
return fmt.Errorf("failed to process expired galleries: %w", err)
}
duration := time.Since(startTime)
log.Printf("=== SCHEDULER END === Duration: %v",main.main function · go · L50-L56 (7 LOC)backend/cmd/zip-processor/main.go
func main() {
app, err := initApp()
if err != nil {
log.Fatalf("Failed to initialize: %v", err)
}
lambda.Start(app.handleSQSEvent)
}main.initApp function · go · L58-L79 (22 LOC)backend/cmd/zip-processor/main.go
func initApp() (*App, error) {
cfg, err := appconfig.NewZipProcessorConfigBuilder().FromEnvironment().Build()
if err != nil {
return nil, err
}
awsCfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(cfg.AWSRegion))
if err != nil {
return nil, err
}
s3Client := s3.NewFromConfig(awsCfg)
dynamoClient := dynamodb.NewFromConfig(awsCfg)
return &App{
cfg: cfg,
s3Client: s3Client,
presignClient: s3.NewPresignClient(s3Client),
jobRepo: dynamodbRepo.NewZipJobRepository(dynamoClient, cfg.ZipJobsTableName()),
photoRepo: dynamodbRepo.NewPhotoRepository(dynamoClient, cfg.PhotosTableName()),
}, nil
}main.App.handleSQSEvent method · go · L82-L109 (28 LOC)backend/cmd/zip-processor/main.go
func (app *App) handleSQSEvent(ctx context.Context, sqsEvent events.SQSEvent) error {
log.Printf("Processing %d SQS records", len(sqsEvent.Records))
for _, record := range sqsEvent.Records {
var msg ZipJobMessage
if err := json.Unmarshal([]byte(record.Body), &msg); err != nil {
log.Printf("Failed to unmarshal message: %v", err)
continue
}
log.Printf("Processing ZIP job: %s for gallery: %s", msg.JobID, msg.GalleryID)
if err := app.processZipJob(ctx, &msg); err != nil {
log.Printf("Failed to process ZIP job %s: %v", msg.JobID, err)
// Mark job as failed
if job, _ := app.jobRepo.GetByID(ctx, msg.JobID); job != nil {
job.Status = repository.ZipJobStatusFailed
job.ErrorMsg = err.Error()
_ = app.jobRepo.Update(ctx, job)
}
continue
}
log.Printf("Successfully processed ZIP job: %s", msg.JobID)
}
return nil
}main.App.processZipJob method · go · L111-L247 (137 LOC)backend/cmd/zip-processor/main.go
func (app *App) processZipJob(ctx context.Context, msg *ZipJobMessage) error {
// Get and update job status to processing
job, err := app.jobRepo.GetByID(ctx, msg.JobID)
if err != nil {
return fmt.Errorf("failed to get job: %w", err)
}
if job == nil {
return fmt.Errorf("job not found: %s", msg.JobID)
}
job.Status = repository.ZipJobStatusProcessing
if updateErr := app.jobRepo.Update(ctx, job); updateErr != nil {
return fmt.Errorf("failed to update job status: %w", updateErr)
}
// Fetch all photos for the gallery
var allPhotos []*repository.Photo
var lastKey map[string]interface{}
const pageSize = 500
for {
photos, nextKey, listErr := app.photoRepo.ListByGallery(ctx, msg.GalleryID, pageSize, lastKey)
if listErr != nil {
return fmt.Errorf("failed to list photos: %w", listErr)
}
allPhotos = append(allPhotos, photos...)
if nextKey == nil || len(photos) < pageSize {
break
}
lastKey = nextKey
}
if len(allPhotos) == 0 {
return fmt.Errorf("no phmain.App.downloadPhoto method · go · L249-L258 (10 LOC)backend/cmd/zip-processor/main.go
func (app *App) downloadPhoto(ctx context.Context, key string) (io.ReadCloser, error) {
output, err := app.s3Client.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(app.cfg.S3BucketOptimized),
Key: aws.String(key),
})
if err != nil {
return nil, err
}
return output.Body, nil
}main.App.uploadZip method · go · L260-L268 (9 LOC)backend/cmd/zip-processor/main.go
func (app *App) uploadZip(ctx context.Context, key string, data []byte) error {
_, err := app.s3Client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(app.cfg.S3BucketOptimized),
Key: aws.String(key),
Body: bytes.NewReader(data),
ContentType: aws.String("application/zip"),
})
return err
}Repobility analyzer · published findings · https://repobility.com
main.App.generateDownloadURL method · go · L270-L282 (13 LOC)backend/cmd/zip-processor/main.go
func (app *App) generateDownloadURL(ctx context.Context, key, filename string) (string, error) {
presignedReq, err := app.presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(app.cfg.S3BucketOptimized),
Key: aws.String(key),
ResponseContentDisposition: aws.String(fmt.Sprintf("attachment; filename=\"%s\"", filename)),
}, func(opts *s3.PresignOptions) {
opts.Expires = 24 * time.Hour
})
if err != nil {
return "", err
}
return presignedReq.URL, nil
}adapters.S3Adapter.Download method · go · L31-L43 (13 LOC)backend/internal/adapters/s3_adapter.go
func (a *S3Adapter) Download(ctx context.Context, bucket, key string) (io.ReadCloser, error) {
log.Printf("[S3Adapter] Downloading from %s/%s", bucket, key)
output, err := a.client.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
if err != nil {
return nil, err
}
return output.Body, nil
}adapters.S3Adapter.DownloadBytes method · go · L46-L54 (9 LOC)backend/internal/adapters/s3_adapter.go
func (a *S3Adapter) DownloadBytes(ctx context.Context, bucket, key string) ([]byte, error) {
reader, err := a.Download(ctx, bucket, key)
if err != nil {
return nil, err
}
defer reader.Close()
return io.ReadAll(reader)
}page 1 / 20next ›