package database import ( "context" "errors" "time" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/domain/entities" ) // AccountRecoveryRepository implements account recovery operations type AccountRecoveryRepository struct { collection *mongo.Collection } type featureFlagSettings struct { ID string `bson:"_id"` Flags entities.FeatureFlags `bson:"flags"` UpdatedAt time.Time `bson:"updated_at"` } // FeatureFlagRepository implements app-wide feature flag operations. type FeatureFlagRepository struct { collection *mongo.Collection } // NewFeatureFlagRepository creates a new feature flag repository. func NewFeatureFlagRepository(db *mongo.Database) *FeatureFlagRepository { return &FeatureFlagRepository{ collection: db.Collection("app_settings"), } } // GetFeatureFlags returns persisted feature flags or defaults when not set. func (r *FeatureFlagRepository) GetFeatureFlags(ctx context.Context) (*entities.FeatureFlags, error) { var settings featureFlagSettings err := r.collection.FindOne(ctx, bson.M{"_id": "feature_flags"}).Decode(&settings) if err != nil { if err == mongo.ErrNoDocuments { return entities.NewDefaultFeatureFlags(), nil } return nil, err } flags := settings.Flags return &flags, nil } // UpdateFeatureFlags persists feature flags. func (r *FeatureFlagRepository) UpdateFeatureFlags(ctx context.Context, flags *entities.FeatureFlags) error { if flags == nil { flags = entities.NewDefaultFeatureFlags() } now := time.Now() _, err := r.collection.UpdateOne( ctx, bson.M{"_id": "feature_flags"}, bson.M{ "$set": bson.M{ "flags": flags, "updated_at": now, }, }, options.UpdateOne().SetUpsert(true), ) return err } // NewAccountRecoveryRepository creates a new recovery repository func NewAccountRecoveryRepository(db *mongo.Database) *AccountRecoveryRepository { return &AccountRecoveryRepository{ collection: db.Collection("account_recovery"), } } // CreateRecovery creates a new recovery token func (r *AccountRecoveryRepository) CreateRecovery(ctx context.Context, recovery *entities.AccountRecovery) error { recovery.ID = bson.NewObjectID() recovery.CreatedAt = time.Now() _, err := r.collection.InsertOne(ctx, recovery) return err } // GetRecoveryByToken retrieves a recovery record by token func (r *AccountRecoveryRepository) GetRecoveryByToken(ctx context.Context, token string) (*entities.AccountRecovery, error) { var recovery entities.AccountRecovery err := r.collection.FindOne(ctx, bson.M{ "token": token, "expires_at": bson.M{"$gt": time.Now()}, "used_at": bson.M{"$exists": false}, }).Decode(&recovery) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("recovery token not found or expired") } return nil, err } return &recovery, nil } // MarkRecoveryUsed marks a recovery token as used func (r *AccountRecoveryRepository) MarkRecoveryUsed(ctx context.Context, id bson.ObjectID) error { now := time.Now() _, err := r.collection.UpdateOne(ctx, bson.M{"_id": id}, bson.M{ "$set": bson.M{"used_at": now}, }) return err } // NoteRevisionRepository implements note revision operations type NoteRevisionRepository struct { collection *mongo.Collection } // NewNoteRevisionRepository creates a new revision repository func NewNoteRevisionRepository(db *mongo.Database) *NoteRevisionRepository { return &NoteRevisionRepository{ collection: db.Collection("note_revisions"), } } // CreateRevision creates a new note revision func (r *NoteRevisionRepository) CreateRevision(ctx context.Context, revision *entities.NoteRevision) error { revision.ID = bson.NewObjectID() revision.CreatedAt = time.Now() _, err := r.collection.InsertOne(ctx, revision) return err } // GetRevisionsByNoteID retrieves all revisions for a note func (r *NoteRevisionRepository) GetRevisionsByNoteID(ctx context.Context, noteID bson.ObjectID) ([]*entities.NoteRevision, error) { var revisions []*entities.NoteRevision cursor, err := r.collection.Find(ctx, bson.M{"note_id": noteID}) if err != nil { return nil, err } defer cursor.Close(ctx) if err = cursor.All(ctx, &revisions); err != nil { return nil, err } return revisions, nil } // GetRevisionByID retrieves a specific revision func (r *NoteRevisionRepository) GetRevisionByID(ctx context.Context, id bson.ObjectID) (*entities.NoteRevision, error) { var revision entities.NoteRevision err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&revision) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("revision not found") } return nil, err } return &revision, nil } // AuthProviderRepository implements auth provider operations type AuthProviderRepository struct { collection *mongo.Collection } // NewAuthProviderRepository creates a new provider repository func NewAuthProviderRepository(db *mongo.Database) *AuthProviderRepository { return &AuthProviderRepository{ collection: db.Collection("auth_providers"), } } // CreateProvider creates a new provider func (r *AuthProviderRepository) CreateProvider(ctx context.Context, provider *entities.AuthProvider) error { provider.ID = bson.NewObjectID() provider.CreatedAt = time.Now() provider.UpdatedAt = time.Now() _, err := r.collection.InsertOne(ctx, provider) return err } // GetProviderByID retrieves a provider by ID func (r *AuthProviderRepository) GetProviderByID(ctx context.Context, id bson.ObjectID) (*entities.AuthProvider, error) { var provider entities.AuthProvider err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&provider) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("provider not found") } return nil, err } return &provider, nil } // GetAllProviders retrieves all active providers func (r *AuthProviderRepository) GetAllProviders(ctx context.Context) ([]*entities.AuthProvider, error) { var providers []*entities.AuthProvider cursor, err := r.collection.Find(ctx, bson.M{"is_active": true}) if err != nil { return nil, err } defer cursor.Close(ctx) if err = cursor.All(ctx, &providers); err != nil { return nil, err } return providers, nil } // GetAllProvidersForAdmin retrieves all providers, including inactive ones func (r *AuthProviderRepository) GetAllProvidersForAdmin(ctx context.Context) ([]*entities.AuthProvider, error) { var providers []*entities.AuthProvider cursor, err := r.collection.Find(ctx, bson.M{}) if err != nil { return nil, err } defer cursor.Close(ctx) if err = cursor.All(ctx, &providers); err != nil { return nil, err } return providers, nil } // UpdateProvider updates a provider func (r *AuthProviderRepository) UpdateProvider(ctx context.Context, provider *entities.AuthProvider) error { provider.UpdatedAt = time.Now() _, err := r.collection.ReplaceOne(ctx, bson.M{"_id": provider.ID}, provider) return err } // DeleteProvider deletes a provider func (r *AuthProviderRepository) DeleteProvider(ctx context.Context, id bson.ObjectID) error { _, err := r.collection.DeleteOne(ctx, bson.M{"_id": id}) return err } // UserProviderLinkRepository implements user provider link operations type UserProviderLinkRepository struct { collection *mongo.Collection } // NewUserProviderLinkRepository creates a new link repository func NewUserProviderLinkRepository(db *mongo.Database) *UserProviderLinkRepository { return &UserProviderLinkRepository{ collection: db.Collection("user_provider_links"), } } // CreateLink creates a new user provider link func (r *UserProviderLinkRepository) CreateLink(ctx context.Context, link *entities.UserProviderLink) error { link.ID = bson.NewObjectID() link.LinkedAt = time.Now() _, err := r.collection.InsertOne(ctx, link) return err } // GetLink retrieves a user provider link func (r *UserProviderLinkRepository) GetLink(ctx context.Context, userID, providerID bson.ObjectID) (*entities.UserProviderLink, error) { var link entities.UserProviderLink err := r.collection.FindOne(ctx, bson.M{ "user_id": userID, "provider_id": providerID, }).Decode(&link) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("link not found") } return nil, err } return &link, nil } // GetLinkByProviderUserID retrieves a link by provider user ID func (r *UserProviderLinkRepository) GetLinkByProviderUserID(ctx context.Context, providerID bson.ObjectID, providerUserID string) (*entities.UserProviderLink, error) { var link entities.UserProviderLink err := r.collection.FindOne(ctx, bson.M{ "provider_id": providerID, "provider_user_id": providerUserID, }).Decode(&link) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("link not found") } return nil, err } return &link, nil } // GetUserLinks retrieves all provider links for a user func (r *UserProviderLinkRepository) GetUserLinks(ctx context.Context, userID bson.ObjectID) ([]*entities.UserProviderLink, error) { var links []*entities.UserProviderLink cursor, err := r.collection.Find(ctx, bson.M{"user_id": userID}) if err != nil { return nil, err } defer cursor.Close(ctx) if err = cursor.All(ctx, &links); err != nil { return nil, err } return links, nil } // UpdateLink updates a provider link func (r *UserProviderLinkRepository) UpdateLink(ctx context.Context, link *entities.UserProviderLink) error { _, err := r.collection.ReplaceOne(ctx, bson.M{"_id": link.ID}, link) return err } // DeleteLink deletes a provider link func (r *UserProviderLinkRepository) DeleteLink(ctx context.Context, id bson.ObjectID) error { _, err := r.collection.DeleteOne(ctx, bson.M{"_id": id}) return err }