193 lines
4.2 KiB
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
|
|
}
|