Files
Link-Log/backend/app/services/link_service.py
T
2026-08-24 20:42:15 +02:00

175 lines
5.2 KiB
Python

from datetime import datetime, timezone
from uuid import uuid4
from backend.app.core.security import clean_url
from backend.app.database import get_connection
MAX_TAGS = 10
def normalize_tags(tags: list[str] | None) -> list[str]:
normalized = []
normalized_keys = set()
for tag in tags or []:
value = tag.strip()
if value and not value.startswith('#'):
value = f'#{value}'
key = value.casefold()
if value and key not in normalized_keys:
normalized.append(value)
normalized_keys.add(key)
if len(normalized) > MAX_TAGS:
raise ValueError(f'A link can have at most {MAX_TAGS} tags')
return normalized
def save_link_tags(conn, link_id: str, tags: list[str]) -> None:
canonical_tags = []
for tag in tags:
tag_row = conn.execute(
'SELECT id FROM tags WHERE lower(name) = lower(?)',
(tag,),
).fetchone()
if tag_row is None:
conn.execute(
'INSERT INTO tags (id, name) VALUES (?, ?)',
(str(uuid4()), tag),
)
tag_row = conn.execute('SELECT id FROM tags WHERE name = ?', (tag,)).fetchone()
conn.execute(
'INSERT OR IGNORE INTO link_tags (link_id, tag_id) VALUES (?, ?)',
(link_id, tag_row['id']),
)
canonical_tags.append(conn.execute(
'SELECT name FROM tags WHERE id = ?',
(tag_row['id'],),
).fetchone()['name'])
return canonical_tags
def get_link_tags(conn, link_id: str) -> list[str]:
rows = conn.execute(
'''
SELECT tags.name FROM tags
JOIN link_tags ON link_tags.tag_id = tags.id
WHERE link_tags.link_id = ? ORDER BY tags.name
''',
(link_id,),
).fetchall()
return [row['name'] for row in rows]
def create_link(
user_id: str,
title: str,
url: str,
comment: str,
timestamp: str | None,
tags: list[str] | None = None,
):
cleaned_url = clean_url(url)
normalized_tags = normalize_tags(tags)
created_at = datetime.now(timezone.utc).isoformat()
record = {
'id': str(uuid4()),
'user_id': user_id,
'title': title,
'url': cleaned_url,
'comment': comment,
'timestamp': timestamp or created_at,
'created_at': created_at,
'updated_at': created_at,
'is_public': 1,
}
with get_connection() as conn:
conn.execute(
'''
INSERT INTO links (id, user_id, title, url, comment, timestamp, created_at, updated_at, is_public)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
''',
(
record['id'],
record['user_id'],
record['title'],
record['url'],
record['comment'],
record['timestamp'],
record['created_at'],
record['updated_at'],
record['is_public'],
),
)
stored_tags = save_link_tags(conn, record['id'], normalized_tags)
conn.commit()
record['tags'] = stored_tags
return record
def list_public_links(username: str | None = None):
with get_connection() as conn:
rows = conn.execute(
'''
SELECT links.*, users.username, users.avatar_url, users.bio
FROM links
JOIN users ON users.id = links.user_id
WHERE links.is_public = 1
AND (? IS NULL OR users.username = ?)
ORDER BY created_at DESC
''',
(username, username),
).fetchall()
records = [dict(row) for row in rows]
for record in records:
record['tags'] = get_link_tags(conn, record['id'])
return records
def update_link(
link_id: str,
user_id: str,
title: str,
url: str,
comment: str,
tags: list[str] | None = None,
):
cleaned_url = clean_url(url)
normalized_tags = normalize_tags(tags)
with get_connection() as conn:
cursor = conn.execute(
'''
UPDATE links
SET title = ?, url = ?, comment = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND user_id = ?
''',
(title, cleaned_url, comment, link_id, user_id),
)
if cursor.rowcount == 0:
return None
conn.execute('DELETE FROM link_tags WHERE link_id = ?', (link_id,))
stored_tags = save_link_tags(conn, link_id, normalized_tags)
conn.commit()
row = conn.execute('SELECT * FROM links WHERE id = ?', (link_id,)).fetchone()
record = dict(row)
record['tags'] = stored_tags
return record
def list_public_users():
with get_connection() as conn:
rows = conn.execute(
'SELECT username FROM users ORDER BY username'
).fetchall()
return [row['username'] for row in rows]
def list_tags():
with get_connection() as conn:
rows = conn.execute('SELECT name FROM tags ORDER BY name').fetchall()
tags = []
seen = set()
for row in rows:
if row['name'].casefold() not in seen:
tags.append(row['name'])
seen.add(row['name'].casefold())
return tags