package repositories import ( "context" "strings" "github.com/KristianJBorgwarth/dendrite.daemon/core/dtos" "github.com/KristianJBorgwarth/dendrite.daemon/core/models" "github.com/KristianJBorgwarth/dendrite.daemon/persistence" ) type ILinkRepository interface { Insert(ctx context.Context, dbContext persistence.IDbContext, links []*models.Link) error InsertRange(ctx context.Context, dbContext persistence.IDbContext, links []*models.Link) error GetByNoteID(ctx context.Context, fromNoteID string) ([]*models.Link, error) GetBySlug(ctx context.Context, dbContext persistence.IDbContext, targetSlug string) ([]*models.Link, error) GetBacklinks(ctx context.Context, slug string) ([]*dtos.BacklinkDto, error) Delete(ctx context.Context, dbContext persistence.IDbContext, fromNoteID string) error GetBrokenLinks(ctx context.Context, fromNoteID string) ([]*models.Link, error) } type linkRepository struct { readDBContext persistence.ReadContext } func NewLinkRepository(rdb persistence.ReadContext) ILinkRepository { return &linkRepository{readDBContext: rdb} } func (r *linkRepository) Insert(ctx context.Context, dbContext persistence.IDbContext, links []*models.Link) error { if len(links) == 0 { return nil } placeholders := make([]string, 0, len(links)) args := make([]any, 0, len(links)) for _, link := range links { placeholders = append(placeholders, "(?, ?, ?, ?, ?, ?, ?)") args = append(args, link.ID(), link.FromNoteID(), link.TargetSlug(), link.Raw(), link.Display(), link.Line(), link.Col()) } query := "INSERT OR IGNORE INTO link(id, from_note_id, target_slug, raw, display, line, col) VALUES " + strings.Join(placeholders, ",") _, err := dbContext.ExecContext(ctx, query, args...) return err } func (r *linkRepository) InsertRange(ctx context.Context, dbContext persistence.IDbContext, links []*models.Link) error { if len(links) == 0 { return nil } linkStatement, err := dbContext.Prepare(`INSERT OR IGNORE INTO link (id, from_note_id, target_slug, raw, display, line, col) VALUES (?, ?, ?, ?, ?, ?, ?)`) if err != nil { return err } for _, link := range links { if _, err := linkStatement.ExecContext(ctx, link.ID(), link.FromNoteID(), link.TargetSlug(), link.Raw(), link.Display(), link.Line(), link.Col()); err != nil { return err } } return nil } func (r *linkRepository) GetByNoteID(ctx context.Context, fromNoteID string) ([]*models.Link, error) { rows, err := r.readDBContext.QueryContext(ctx, "SELECT id, from_note_id, target_slug, raw, display, line, col FROM link WHERE from_note_id = ?", fromNoteID) if err != nil { return nil, err } defer rows.Close() var links []*models.Link for rows.Next() { var id, fromNoteID, targetSlug, raw, display string var line, col int if err := rows.Scan(&id, &fromNoteID, &targetSlug, &raw, &display, &line, &col); err != nil { return nil, err } links = append(links, models.NewLink(id, fromNoteID, targetSlug, raw, display, line, col)) } return links, nil } func (r *linkRepository) GetBySlug(ctx context.Context, dbContext persistence.IDbContext, targetSlug string) ([]*models.Link, error) { rows, err := dbContext.QueryContext(ctx, `SELECT id, from_note_id, target_slug, raw, display, line, col FROM link WHERE target_slug = ?`, targetSlug) if err != nil { return nil, err } defer rows.Close() var links []*models.Link for rows.Next() { var id, fromNoteID, targetSlug, raw, display string var line, col int if err := rows.Scan(&id, &fromNoteID, &targetSlug, &raw, &display, &line, &col); err != nil { return nil, err } links = append(links, models.NewLink(id, fromNoteID, targetSlug, raw, display, line, col)) } return links, nil } func (r *linkRepository) GetBacklinks(ctx context.Context, slug string) ([]*dtos.BacklinkDto, error) { query := ` SELECT n.id, n.slug, n.title, n.path, l.raw, l.line, l.col FROM link l JOIN note n ON l.from_note_id = n.id WHERE l.target_slug = ?` rows, err := r.readDBContext.QueryContext(ctx, query, slug) if err != nil { return nil, err } defer rows.Close() backlinks := make([]*dtos.BacklinkDto, 0) for rows.Next() { var noteID, noteSlug, noteTitle, notePath, raw string var line, col int if err := rows.Scan(¬eID, ¬eSlug, ¬eTitle, ¬ePath, &raw, &line, &col); err != nil { return nil, err } backlinks = append(backlinks, dtos.NewBacklink(noteID, noteTitle, noteSlug, notePath, raw, line, col)) } return backlinks, nil } func (r *linkRepository) Delete(ctx context.Context, dbContext persistence.IDbContext, fromNoteID string) error { _, err := dbContext.ExecContext(ctx, "DELETE FROM link WHERE from_note_id = ?", fromNoteID) return err } func (r *linkRepository) GetBrokenLinks(ctx context.Context, fromNoteID string) ([]*models.Link, error) { query := ` SELECT l.id, l.from_note_id, l.target_slug, l.raw, l.display, l.line, l.col FROM link l LEFT JOIN note n ON l.target_slug = n.slug WHERE l.from_note_id = ? AND n.id IS NULL` rows, err := r.readDBContext.QueryContext(ctx, query, fromNoteID) if err != nil { return nil, err } defer rows.Close() var brokenLinks []*models.Link for rows.Next() { var id, rowFromNoteID, targetSlug, raw, display string var line, col int if err := rows.Scan(&id, &rowFromNoteID, &targetSlug, &raw, &display, &line, &col); err != nil { return nil, err } brokenLinks = append(brokenLinks, models.NewLink(id, fromNoteID, targetSlug, raw, display, line, col)) } return brokenLinks, nil }