Label editing
This commit is contained in:
@@ -6,6 +6,7 @@ from pydantic import BaseModel
|
||||
|
||||
from backend.app.api.dependencies import require_admin
|
||||
from backend.app.database import get_connection, hash_password
|
||||
from backend.app.services.link_service import delete_label
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -127,6 +128,26 @@ def delete_user(user_id: str, current_user: dict = Depends(require_admin)):
|
||||
return {'status': 'deleted', 'id': user_id}
|
||||
|
||||
|
||||
@router.delete('/labels/{label_id}')
|
||||
def admin_delete_label(label_id: str, _: dict = Depends(require_admin)):
|
||||
if not delete_label(label_id, is_admin=True):
|
||||
raise HTTPException(status_code=404, detail='Label not found')
|
||||
return {'status': 'deleted', 'id': label_id}
|
||||
|
||||
|
||||
@router.get('/labels')
|
||||
def admin_list_labels(_: dict = Depends(require_admin)):
|
||||
with get_connection() as conn:
|
||||
rows = conn.execute(
|
||||
'''
|
||||
SELECT tags.id, tags.name, tags.created_by, users.username AS creator
|
||||
FROM tags LEFT JOIN users ON users.id = tags.created_by
|
||||
ORDER BY tags.name
|
||||
'''
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
@router.get('/plugins')
|
||||
def list_plugins(_: dict = Depends(require_admin)):
|
||||
with get_connection() as conn:
|
||||
|
||||
@@ -6,6 +6,7 @@ from pydantic import BaseModel
|
||||
|
||||
from backend.app.api.dependencies import get_current_user
|
||||
from backend.app.database import AVATARS_DIR, get_connection, hash_password
|
||||
from backend.app.services.link_service import create_label, delete_label, list_user_labels, update_label
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -27,6 +28,10 @@ class UserPluginConfigUpdate(BaseModel):
|
||||
hashtag: str | None = None
|
||||
|
||||
|
||||
class LabelUpdate(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
@router.get('/me')
|
||||
def get_current_user_profile(user: dict = Depends(get_current_user)):
|
||||
with get_connection() as conn:
|
||||
@@ -81,6 +86,37 @@ def update_password(payload: PasswordUpdate, user: dict = Depends(get_current_us
|
||||
return {'status': 'password_updated'}
|
||||
|
||||
|
||||
@router.get('/labels')
|
||||
def get_labels(user: dict = Depends(get_current_user)):
|
||||
return list_user_labels(user['id'])
|
||||
|
||||
|
||||
@router.post('/labels', status_code=201)
|
||||
def add_label(payload: LabelUpdate, user: dict = Depends(get_current_user)):
|
||||
try:
|
||||
return create_label(user['id'], payload.name)
|
||||
except ValueError as error:
|
||||
raise HTTPException(status_code=409, detail=str(error)) from error
|
||||
|
||||
|
||||
@router.put('/labels/{label_id}')
|
||||
def edit_label(label_id: str, payload: LabelUpdate, user: dict = Depends(get_current_user)):
|
||||
try:
|
||||
result = update_label(label_id, user['id'], payload.name)
|
||||
except ValueError as error:
|
||||
raise HTTPException(status_code=409, detail=str(error)) from error
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail='Label not found or not owned by user')
|
||||
return result
|
||||
|
||||
|
||||
@router.delete('/labels/{label_id}')
|
||||
def remove_label(label_id: str, user: dict = Depends(get_current_user)):
|
||||
if not delete_label(label_id, user['id']):
|
||||
raise HTTPException(status_code=404, detail='Label not found or not owned by user')
|
||||
return {'status': 'deleted', 'id': label_id}
|
||||
|
||||
|
||||
@router.post('/avatar')
|
||||
async def upload_avatar(
|
||||
avatar: UploadFile = File(...),
|
||||
|
||||
@@ -94,6 +94,10 @@ CREATE INDEX IF NOT EXISTS idx_link_tags_tag_id ON link_tags(tag_id);
|
||||
'''),
|
||||
(3, '''
|
||||
UPDATE tags SET name = '#' || name WHERE name NOT LIKE '#%';
|
||||
'''),
|
||||
(4, '''
|
||||
ALTER TABLE tags ADD COLUMN created_by TEXT REFERENCES users(id) ON DELETE SET NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_tags_created_by ON tags(created_by);
|
||||
'''),
|
||||
]
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ async def user_profile_page(request: Request):
|
||||
return templates.TemplateResponse(request, 'user_profile.html', {})
|
||||
|
||||
|
||||
@app.get('/labels', response_class=HTMLResponse)
|
||||
async def labels_page(request: Request):
|
||||
return templates.TemplateResponse(request, 'labels.html', {})
|
||||
|
||||
|
||||
@app.get('/login', response_class=HTMLResponse)
|
||||
async def login_page(request: Request):
|
||||
return templates.TemplateResponse(request, 'login.html', {})
|
||||
|
||||
@@ -172,3 +172,65 @@ def list_tags():
|
||||
tags.append(row['name'])
|
||||
seen.add(row['name'].casefold())
|
||||
return tags
|
||||
|
||||
|
||||
def list_user_labels(user_id: str):
|
||||
with get_connection() as conn:
|
||||
rows = conn.execute(
|
||||
'SELECT id, name, created_by FROM tags WHERE created_by = ? ORDER BY name',
|
||||
(user_id,),
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
def create_label(user_id: str, name: str):
|
||||
normalized = normalize_tags([name])
|
||||
label = normalized[0] if normalized else ''
|
||||
if not label:
|
||||
raise ValueError('Label cannot be empty')
|
||||
with get_connection() as conn:
|
||||
existing = conn.execute(
|
||||
'SELECT id FROM tags WHERE lower(name) = lower(?)', (label,)
|
||||
).fetchone()
|
||||
if existing:
|
||||
raise ValueError('Label already exists')
|
||||
label_id = str(uuid4())
|
||||
conn.execute(
|
||||
'INSERT INTO tags (id, name, created_by) VALUES (?, ?, ?)',
|
||||
(label_id, label, user_id),
|
||||
)
|
||||
conn.commit()
|
||||
return {'id': label_id, 'name': label, 'created_by': user_id}
|
||||
|
||||
|
||||
def update_label(label_id: str, user_id: str, name: str):
|
||||
normalized = normalize_tags([name])
|
||||
if not normalized:
|
||||
raise ValueError('Label cannot be empty')
|
||||
with get_connection() as conn:
|
||||
current = conn.execute(
|
||||
'SELECT id, name, created_by FROM tags WHERE id = ?', (label_id,)
|
||||
).fetchone()
|
||||
if current is None or current['created_by'] != user_id:
|
||||
return None
|
||||
duplicate = conn.execute(
|
||||
'SELECT id FROM tags WHERE lower(name) = lower(?) AND id != ?',
|
||||
(normalized[0], label_id),
|
||||
).fetchone()
|
||||
if duplicate:
|
||||
raise ValueError('Label already exists')
|
||||
conn.execute('UPDATE tags SET name = ? WHERE id = ?', (normalized[0], label_id))
|
||||
conn.commit()
|
||||
return {'id': label_id, 'name': normalized[0], 'created_by': user_id}
|
||||
|
||||
|
||||
def delete_label(label_id: str, user_id: str | None = None, is_admin: bool = False):
|
||||
with get_connection() as conn:
|
||||
current = conn.execute(
|
||||
'SELECT id, created_by FROM tags WHERE id = ?', (label_id,)
|
||||
).fetchone()
|
||||
if current is None or (not is_admin and current['created_by'] != user_id):
|
||||
return False
|
||||
conn.execute('DELETE FROM tags WHERE id = ?', (label_id,))
|
||||
conn.commit()
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user