first commit
This commit is contained in:
338
backend/internal/infrastructure/database/note_repository.go
Normal file
338
backend/internal/infrastructure/database/note_repository.go
Normal file
@@ -0,0 +1,338 @@
|
||||
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"
|
||||
|
||||
"github.com/noteapp/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(¬e)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, errors.New("note not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return ¬e, 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, ¬es); 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, ¬es); 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, ¬es); 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, ¬es); 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
|
||||
}
|
||||
Reference in New Issue
Block a user