package repository import ( "context" "encoding/json" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) type TranscriptionRepo struct { col *mongo.Collection } func NewTranscriptionRepo(db *MongoDB) *TranscriptionRepo { return &TranscriptionRepo{col: db.Collection("transcriptions")} } type ContentBlockDoc struct { Text string `bson:"text" json:"text"` Format string `bson:"format" json:"format"` Alignment string `bson:"alignment" json:"alignment"` Speaker *string `bson:"speaker,omitempty" json:"speaker,omitempty"` } type TranscriptionDoc struct { ID bson.ObjectID `bson:"_id" json:"id"` Slug string `bson:"slug" json:"slug"` Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` Date *string `bson:"date,omitempty" json:"date,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"` Place *string `bson:"place,omitempty" json:"place,omitempty"` Context *string `bson:"context,omitempty" json:"context,omitempty"` Reference *string `bson:"reference,omitempty" json:"reference,omitempty"` DurationMinutes *int `bson:"durationMinutes,omitempty" json:"duration_minutes,omitempty"` AudioUrl *string `bson:"audioUrl,omitempty" json:"audio_url,omitempty"` Participants []string `bson:"participants" json:"participants"` Content []ContentBlockDoc `bson:"content" json:"content"` ContentJson *string `bson:"contentJson,omitempty" json:"-"` Tags []string `bson:"tags" json:"tags"` Language string `bson:"language" json:"language"` Source *string `bson:"source,omitempty" json:"source,omitempty"` IsPublished bool `bson:"isPublished" json:"is_published"` } type TranscriptionSummaryDoc struct { ID bson.ObjectID `bson:"_id" json:"id"` Slug string `bson:"slug" json:"slug"` Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` Date *string `bson:"date,omitempty" json:"date,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"` Place *string `bson:"place,omitempty" json:"place,omitempty"` Context *string `bson:"context,omitempty" json:"context,omitempty"` Reference *string `bson:"reference,omitempty" json:"reference,omitempty"` DurationMinutes *int `bson:"durationMinutes,omitempty" json:"duration_minutes,omitempty"` AudioUrl *string `bson:"audioUrl,omitempty" json:"audio_url,omitempty"` Participants []string `bson:"participants" json:"participants"` } var publishedFilter = bson.M{"isPublished": true} var transcriptionSummaryProjection = bson.M{ "slug": 1, "name": 1, "type": 1, "date": 1, "year": 1, "month": 1, "day": 1, "place": 1, "context": 1, "reference": 1, "durationMinutes": 1, "audioUrl": 1, "participants": 1, } func (r *TranscriptionRepo) GetBySlug(ctx context.Context, slug string) (*TranscriptionDoc, error) { filter := bson.M{"slug": slug, "isPublished": true} var doc TranscriptionDoc err := r.col.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, err } if len(doc.Content) == 0 && doc.ContentJson != nil && *doc.ContentJson != "" { var blocks []ContentBlockDoc if err := json.Unmarshal([]byte(*doc.ContentJson), &blocks); err == nil { doc.Content = blocks } } doc.ContentJson = nil return &doc, nil } func (r *TranscriptionRepo) GetByYear(ctx context.Context, year int) ([]TranscriptionSummaryDoc, error) { filter := bson.M{"year": year, "isPublished": true} opts := options.Find(). SetSort(bson.M{"month": 1, "day": 1}). SetProjection(transcriptionSummaryProjection) cursor, err := r.col.Find(ctx, filter, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var results []TranscriptionSummaryDoc if err := cursor.All(ctx, &results); err != nil { return nil, err } return results, nil } func (r *TranscriptionRepo) GetByPlace(ctx context.Context, place string) ([]TranscriptionSummaryDoc, error) { filter := bson.M{"place": place, "isPublished": true} opts := options.Find(). SetSort(bson.M{"year": 1, "month": 1, "day": 1}). SetProjection(transcriptionSummaryProjection) cursor, err := r.col.Find(ctx, filter, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var results []TranscriptionSummaryDoc if err := cursor.All(ctx, &results); err != nil { return nil, err } return results, nil } func (r *TranscriptionRepo) GetByType(ctx context.Context, docType string) ([]TranscriptionSummaryDoc, error) { filter := bson.M{"type": docType, "isPublished": true} opts := options.Find(). SetSort(bson.M{"year": 1, "month": 1, "day": 1}). SetProjection(transcriptionSummaryProjection) cursor, err := r.col.Find(ctx, filter, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var results []TranscriptionSummaryDoc if err := cursor.All(ctx, &results); err != nil { return nil, err } return results, nil } func (r *TranscriptionRepo) GetYears(ctx context.Context) ([]bson.M, error) { pipeline := bson.A{ bson.M{"$match": bson.M{"isPublished": true, "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 *TranscriptionRepo) GetPlaces(ctx context.Context) ([]bson.M, error) { pipeline := bson.A{ bson.M{"$match": bson.M{"isPublished": true, "place": bson.M{"$ne": nil}}}, bson.M{"$group": bson.M{"_id": "$place", "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 *TranscriptionRepo) GetTypeDistribution(ctx context.Context) (int64, int64, error) { lectureCount, err := r.col.CountDocuments(ctx, bson.M{"type": "lecture", "isPublished": true}) if err != nil { return 0, 0, err } conversationCount, err := r.col.CountDocuments(ctx, bson.M{"type": "conversation", "isPublished": true}) if err != nil { return 0, 0, err } return lectureCount, conversationCount, nil } func (r *TranscriptionRepo) Count(ctx context.Context) (int64, error) { return r.col.CountDocuments(ctx, publishedFilter) } func (r *TranscriptionRepo) GetAdjacent(ctx context.Context, slug string, year int) (*TranscriptionSummaryDoc, *TranscriptionSummaryDoc, error) { filter := bson.M{"year": year, "isPublished": true} opts := options.Find(). SetSort(bson.M{"month": 1, "day": 1}). SetProjection(transcriptionSummaryProjection) cursor, err := r.col.Find(ctx, filter, opts) if err != nil { return nil, nil, err } defer cursor.Close(ctx) var summaries []TranscriptionSummaryDoc if err := cursor.All(ctx, &summaries); err != nil { return nil, nil, err } idx := -1 for i, s := range summaries { if s.Slug == slug { idx = i break } } if idx < 0 { return nil, nil, nil } var prev, next *TranscriptionSummaryDoc if idx > 0 { prev = &summaries[idx-1] } if idx < len(summaries)-1 { next = &summaries[idx+1] } return prev, next, nil }