Files
notely/backend/internal/infrastructure/database/note_repository.go
2026-03-26 16:27:14 +00:00

339 lines
8.9 KiB
Go

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"
)
// NoteRepository implements the note repository interface
type NoteRepository struct {
collection *mongo.Collection
}
func notePrioritySortOptions(skip, limit int) *options.FindOptionsBuilder {
return options.Find().
SetSkip(int64(skip)).
SetLimit(int64(limit)).
SetSort(bson.D{
{Key: "is_pinned", Value: -1},
{Key: "is_favorite", Value: -1},
{Key: "title", Value: 1},
}).
SetCollation(&options.Collation{Locale: "en", Strength: 2})
}
// NewNoteRepository creates a new note repository
func NewNoteRepository(db *mongo.Database) *NoteRepository {
return &NoteRepository{
collection: db.Collection("notes"),
}
}
// CreateNote creates a new note
func (r *NoteRepository) CreateNote(ctx context.Context, note *entities.Note) error {
note.ID = bson.NewObjectID()
note.CreatedAt = time.Now()
note.UpdatedAt = time.Now()
_, err := r.collection.InsertOne(ctx, note)
return err
}
// GetNoteByID retrieves a note by ID
func (r *NoteRepository) GetNoteByID(ctx context.Context, id bson.ObjectID) (*entities.Note, error) {
var note entities.Note
err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&note)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, errors.New("note not found")
}
return nil, err
}
return &note, nil
}
// GetNotesBySpaceID retrieves all notes in a space with pagination
func (r *NoteRepository) GetNotesBySpaceID(ctx context.Context, spaceID bson.ObjectID, skip, limit int) ([]*entities.Note, error) {
var notes []*entities.Note
opts := notePrioritySortOptions(skip, limit)
cursor, err := r.collection.Find(ctx, bson.M{"space_id": spaceID}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &notes); err != nil {
return nil, err
}
return notes, nil
}
// GetPublicNotesBySpaceID retrieves public notes in a space with pagination.
func (r *NoteRepository) GetPublicNotesBySpaceID(ctx context.Context, spaceID bson.ObjectID, skip, limit int) ([]*entities.Note, error) {
var notes []*entities.Note
opts := notePrioritySortOptions(skip, limit)
cursor, err := r.collection.Find(ctx, bson.M{"space_id": spaceID, "is_public": true}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &notes); err != nil {
return nil, err
}
return notes, nil
}
// GetNotesByCategory retrieves notes in a category
func (r *NoteRepository) GetNotesByCategory(ctx context.Context, spaceID, categoryID bson.ObjectID) ([]*entities.Note, error) {
var notes []*entities.Note
opts := options.Find().
SetSort(bson.D{
{Key: "is_pinned", Value: -1},
{Key: "is_favorite", Value: -1},
{Key: "title", Value: 1},
}).
SetCollation(&options.Collation{Locale: "en", Strength: 2})
cursor, err := r.collection.Find(ctx, bson.M{
"space_id": spaceID,
"category_id": categoryID,
}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &notes); err != nil {
return nil, err
}
return notes, nil
}
// SearchNotes performs full-text search on notes
func (r *NoteRepository) SearchNotes(ctx context.Context, spaceID bson.ObjectID, query string) ([]*entities.Note, error) {
var notes []*entities.Note
cursor, err := r.collection.Find(ctx, bson.M{
"space_id": spaceID,
"$text": bson.M{
"$search": query,
},
})
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &notes); err != nil {
return nil, err
}
return notes, nil
}
// UpdateNote updates a note
func (r *NoteRepository) UpdateNote(ctx context.Context, note *entities.Note) error {
note.UpdatedAt = time.Now()
_, err := r.collection.ReplaceOne(ctx, bson.M{"_id": note.ID}, note)
return err
}
// DeleteNote deletes a note
func (r *NoteRepository) DeleteNote(ctx context.Context, id bson.ObjectID) error {
_, err := r.collection.DeleteOne(ctx, bson.M{"_id": id})
return err
}
// DeleteNotesBySpaceID deletes all notes in a space
func (r *NoteRepository) DeleteNotesBySpaceID(ctx context.Context, spaceID bson.ObjectID) error {
_, err := r.collection.DeleteMany(ctx, bson.M{"space_id": spaceID})
return err
}
// EnsureIndexes creates necessary indexes
func (r *NoteRepository) EnsureIndexes(ctx context.Context) error {
indexModel := []mongo.IndexModel{
{
Keys: bson.D{bson.E{Key: "space_id", Value: 1}},
},
{
Keys: bson.D{bson.E{Key: "category_id", Value: 1}},
},
{
Keys: bson.D{
bson.E{Key: "title", Value: "text"},
bson.E{Key: "content", Value: "text"},
bson.E{Key: "tags", Value: "text"},
},
},
{
Keys: bson.D{bson.E{Key: "updated_at", Value: -1}},
},
{
Keys: bson.D{
{Key: "space_id", Value: 1},
{Key: "is_pinned", Value: -1},
{Key: "is_favorite", Value: -1},
{Key: "title", Value: 1},
},
},
{
Keys: bson.D{
{Key: "space_id", Value: 1},
{Key: "is_public", Value: 1},
{Key: "is_pinned", Value: -1},
{Key: "is_favorite", Value: -1},
{Key: "title", Value: 1},
},
},
}
_, err := r.collection.Indexes().CreateMany(ctx, indexModel)
return err
}
// ========== CATEGORY REPOSITORY ==========
// CategoryRepository implements the category repository interface
type CategoryRepository struct {
collection *mongo.Collection
}
// NewCategoryRepository creates a new category repository
func NewCategoryRepository(db *mongo.Database) *CategoryRepository {
return &CategoryRepository{
collection: db.Collection("categories"),
}
}
// CreateCategory creates a new category
func (r *CategoryRepository) CreateCategory(ctx context.Context, category *entities.Category) error {
category.ID = bson.NewObjectID()
category.CreatedAt = time.Now()
category.UpdatedAt = time.Now()
_, err := r.collection.InsertOne(ctx, category)
return err
}
// GetCategoryByID retrieves a category by ID
func (r *CategoryRepository) GetCategoryByID(ctx context.Context, id bson.ObjectID) (*entities.Category, error) {
var category entities.Category
err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&category)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, errors.New("category not found")
}
return nil, err
}
return &category, nil
}
// GetCategoriesBySpaceID retrieves all categories in a space
func (r *CategoryRepository) GetCategoriesBySpaceID(ctx context.Context, spaceID bson.ObjectID) ([]*entities.Category, error) {
var categories []*entities.Category
opts := options.Find().SetSort(bson.M{"order": 1})
cursor, err := r.collection.Find(ctx, bson.M{"space_id": spaceID}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &categories); err != nil {
return nil, err
}
return categories, nil
}
// GetRootCategories retrieves root level categories in a space
func (r *CategoryRepository) GetRootCategories(ctx context.Context, spaceID bson.ObjectID) ([]*entities.Category, error) {
var categories []*entities.Category
opts := options.Find().SetSort(bson.M{"order": 1})
cursor, err := r.collection.Find(ctx, bson.M{
"space_id": spaceID,
"parent_id": nil,
}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &categories); err != nil {
return nil, err
}
return categories, nil
}
// GetSubcategories retrieves subcategories of a category
func (r *CategoryRepository) GetSubcategories(ctx context.Context, parentID bson.ObjectID) ([]*entities.Category, error) {
var categories []*entities.Category
opts := options.Find().SetSort(bson.M{"order": 1})
cursor, err := r.collection.Find(ctx, bson.M{"parent_id": parentID}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &categories); err != nil {
return nil, err
}
return categories, nil
}
// UpdateCategory updates a category
func (r *CategoryRepository) UpdateCategory(ctx context.Context, category *entities.Category) error {
category.UpdatedAt = time.Now()
_, err := r.collection.ReplaceOne(ctx, bson.M{"_id": category.ID}, category)
return err
}
// DeleteCategory deletes a category
func (r *CategoryRepository) DeleteCategory(ctx context.Context, id bson.ObjectID) error {
_, err := r.collection.DeleteOne(ctx, bson.M{"_id": id})
return err
}
// DeleteCategoriesBySpaceID deletes all categories in a space
func (r *CategoryRepository) DeleteCategoriesBySpaceID(ctx context.Context, spaceID bson.ObjectID) error {
_, err := r.collection.DeleteMany(ctx, bson.M{"space_id": spaceID})
return err
}
// EnsureIndexes creates necessary indexes
func (r *CategoryRepository) EnsureIndexes(ctx context.Context) error {
indexModel := []mongo.IndexModel{
{
Keys: bson.D{bson.E{Key: "space_id", Value: 1}},
},
{
Keys: bson.D{bson.E{Key: "parent_id", Value: 1}},
},
{
Keys: bson.D{bson.E{Key: "order", Value: 1}},
},
}
_, err := r.collection.Indexes().CreateMany(ctx, indexModel)
return err
}