Files
linkshare/internal/database/links/links.go

193 lines
4.2 KiB
Go

package links
import (
"database/sql"
"time"
"git.omicron.one/omicron/linkshare/internal/database"
. "git.omicron.one/omicron/linkshare/internal/util/option"
)
// Link represents a stored link
type Link struct {
ID int64
URL string
Title string
CreatedAt time.Time
UpdatedAt Option[time.Time]
IsPrivate bool
}
// Repository handles link storage operations
type Repository struct {
db *database.DB
}
// NewRepository creates a new link repository
func NewRepository(db *database.DB) *Repository {
return &Repository{db: db}
}
// Create adds a new link to the database
func (r *Repository) Create(url, title string, isPrivate bool) (int64, error) {
var id int64
err := r.db.Transaction(func(tx *sql.Tx) error {
now := time.Now().UTC().Format(time.RFC3339)
result, err := tx.Exec(
"INSERT INTO links (url, title, created_at, is_private) VALUES (?, ?, ?, ?)",
url, title, now, isPrivate,
)
if err != nil {
return err
}
id, err = result.LastInsertId()
return err
})
return id, err
}
// Get retrieves a single link by ID
func (r *Repository) Get(id int64) (*Link, error) {
var (
link Link
createdAt string
updatedAt sql.NullString
)
err := r.db.Transaction(func(tx *sql.Tx) error {
row := tx.QueryRow(
"SELECT id, url, title, created_at, updated_at, is_private FROM links WHERE id = ?",
id,
)
err := row.Scan(&link.ID, &link.URL, &link.Title, &createdAt, &updatedAt, &link.IsPrivate)
if err != nil {
return err
}
created, err := time.Parse(time.RFC3339, createdAt)
if err != nil {
return err
}
link.CreatedAt = created
if updatedAt.Valid {
updated, err := time.Parse(time.RFC3339, updatedAt.String)
if err != nil {
return err
}
link.UpdatedAt = Some(updated)
} else {
link.UpdatedAt = None[time.Time]()
}
return nil
})
if err != nil {
return nil, err
}
return &link, nil
}
// Update updates an existing link's fields
func (r *Repository) Update(id int64, url, title string, isPrivate bool) error {
return r.db.Transaction(func(tx *sql.Tx) error {
now := time.Now().UTC().Format(time.RFC3339)
_, err := tx.Exec(
"UPDATE links SET url = ?, title = ?, updated_at = ?, is_private = ? WHERE id = ?",
url, title, now, isPrivate, id,
)
return err
})
}
// Delete removes a link from the database
func (r *Repository) Delete(id int64) error {
return r.db.Transaction(func(tx *sql.Tx) error {
_, err := tx.Exec("DELETE FROM links WHERE id = ?", id)
return err
})
}
// List returns a paginated list of links
func (r *Repository) List(includePrivate bool, offset, limit int) ([]*Link, error) {
var links []*Link
err := r.db.Transaction(func(tx *sql.Tx) error {
var rows *sql.Rows
var err error
if includePrivate {
rows, err = tx.Query(
`SELECT id, url, title, created_at, updated_at, is_private
FROM links ORDER BY created_at DESC LIMIT ? OFFSET ?`,
limit, offset,
)
} else {
rows, err = tx.Query(
`SELECT id, url, title, created_at, updated_at, is_private
FROM links WHERE is_private = 0 ORDER BY created_at DESC LIMIT ? OFFSET ?`,
limit, offset,
)
}
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
link Link
createdAt string
updatedAt sql.NullString
)
err := rows.Scan(&link.ID, &link.URL, &link.Title, &createdAt, &updatedAt, &link.IsPrivate)
if err != nil {
return err
}
created, err := time.Parse(time.RFC3339, createdAt)
if err != nil {
return err
}
link.CreatedAt = created
if updatedAt.Valid {
updated, err := time.Parse(time.RFC3339, updatedAt.String)
if err != nil {
return err
}
link.UpdatedAt = Some(updated)
} else {
link.UpdatedAt = None[time.Time]()
}
links = append(links, &link)
}
return rows.Err()
})
if err != nil {
return nil, err
}
return links, nil
}
// Count returns the total number of links in the database
func (r *Repository) Count(includePrivate bool) (int, error) {
var count int
err := r.db.Transaction(func(tx *sql.Tx) error {
var row *sql.Row
if includePrivate {
row = tx.QueryRow("SELECT COUNT(*) FROM links")
} else {
row = tx.QueryRow("SELECT COUNT(*) FROM links WHERE is_private")
}
return row.Scan(&count)
})
return count, err
}