package repository import ( "context" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) type LetterRepo struct { col *mongo.Collection } func NewLetterRepo(db *MongoDB) *LetterRepo { return &LetterRepo{col: db.Collection("letters")} } type LetterDoc struct { ID bson.ObjectID `bson:"_id" json:"id"` Slug string `bson:"slug" json:"slug"` Recipients []string `bson:"recipients" json:"recipients"` RecipientDisplay string `bson:"recipientDisplay" json:"recipient_display"` Date *string `bson:"date,omitempty" json:"date,omitempty"` DateRef *string `bson:"dateRef,omitempty" json:"date_ref,omitempty"` Year *int `bson:"year,omitempty" json:"year,omitempty"` Month *int `bson:"month,omitempty" json:"month,omitempty"` Day *int `bson:"day,omitempty" json:"day,omitempty"` Location *string `bson:"location,omitempty" json:"location,omitempty"` Salutation *string `bson:"salutation,omitempty" json:"salutation,omitempty"` Paragraphs []string `bson:"paragraphs" json:"paragraphs"` } type LetterSummaryDoc struct { ID bson.ObjectID `bson:"_id" json:"id"` Slug string `bson:"slug" json:"slug"` Recipients []string `bson:"recipients" json:"recipients"` RecipientDisplay string `bson:"recipientDisplay" json:"recipient_display"` Date *string `bson:"date,omitempty" json:"date,omitempty"` Year *int `bson:"year,omitempty" json:"year,omitempty"` Month *int `bson:"month,omitempty" json:"month,omitempty"` Location *string `bson:"location,omitempty" json:"location,omitempty"` } var letterSummaryProjection = bson.M{ "slug": 1, "recipients": 1, "recipientDisplay": 1, "date": 1, "year": 1, "month": 1, "location": 1, } func (r *LetterRepo) GetBySlug(ctx context.Context, slug string) (*LetterDoc, error) { var doc LetterDoc err := r.col.FindOne(ctx, bson.M{"slug": slug}).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, err } return &doc, nil } func (r *LetterRepo) GetByYear(ctx context.Context, year int) ([]LetterSummaryDoc, error) { opts := options.Find(). SetSort(bson.M{"month": 1, "day": 1}). SetProjection(letterSummaryProjection) cursor, err := r.col.Find(ctx, bson.M{"year": year}, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var results []LetterSummaryDoc if err := cursor.All(ctx, &results); err != nil { return nil, err } return results, nil } func (r *LetterRepo) GetByRecipient(ctx context.Context, name string) ([]LetterSummaryDoc, error) { opts := options.Find(). SetSort(bson.M{"year": 1, "month": 1, "day": 1}). SetProjection(letterSummaryProjection) cursor, err := r.col.Find(ctx, bson.M{"recipients": name}, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var results []LetterSummaryDoc if err := cursor.All(ctx, &results); err != nil { return nil, err } return results, nil } func (r *LetterRepo) GetByLocation(ctx context.Context, location string) ([]LetterSummaryDoc, error) { opts := options.Find(). SetSort(bson.M{"year": 1, "month": 1, "day": 1}). SetProjection(letterSummaryProjection) cursor, err := r.col.Find(ctx, bson.M{"location": location}, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var results []LetterSummaryDoc if err := cursor.All(ctx, &results); err != nil { return nil, err } return results, nil } func (r *LetterRepo) GetYears(ctx context.Context) ([]bson.M, error) { pipeline := bson.A{ bson.M{"$match": bson.M{"year": bson.M{"$ne": nil}}}, bson.M{"$group": bson.M{"_id": "$year", "count": bson.M{"$sum": 1}}}, bson.M{"$sort": bson.M{"_id": 1}}, } cursor, err := r.col.Aggregate(ctx, pipeline) if err != nil { return nil, err } defer cursor.Close(ctx) var results []bson.M if err := cursor.All(ctx, &results); err != nil { return nil, err } return results, nil } func (r *LetterRepo) GetRecipients(ctx context.Context) ([]bson.M, error) { pipeline := bson.A{ bson.M{"$unwind": "$recipients"}, bson.M{"$group": bson.M{"_id": "$recipients", "count": bson.M{"$sum": 1}}}, bson.M{"$sort": bson.M{"count": -1}}, } cursor, err := r.col.Aggregate(ctx, pipeline) if err != nil { return nil, err } defer cursor.Close(ctx) var results []bson.M if err := cursor.All(ctx, &results); err != nil { return nil, err } return results, nil } func (r *LetterRepo) GetLocations(ctx context.Context) ([]bson.M, error) { pipeline := bson.A{ bson.M{"$match": bson.M{"location": bson.M{"$ne": nil}}}, bson.M{"$group": bson.M{"_id": "$location", "count": bson.M{"$sum": 1}}}, bson.M{"$sort": bson.M{"count": -1}}, } cursor, err := r.col.Aggregate(ctx, pipeline) if err != nil { return nil, err } defer cursor.Close(ctx) var results []bson.M if err := cursor.All(ctx, &results); err != nil { return nil, err } return results, nil } func (r *LetterRepo) Count(ctx context.Context) (int64, error) { return r.col.CountDocuments(ctx, bson.M{}) }