chore: sync local updates
This commit is contained in:
618
mengyaprofile-backend/db.py
Normal file
618
mengyaprofile-backend/db.py
Normal file
@@ -0,0 +1,618 @@
|
||||
import os
|
||||
import sqlite3
|
||||
import unicodedata
|
||||
from contextlib import contextmanager
|
||||
|
||||
DATA_DIR = os.environ.get('DATA_DIR', os.path.join(os.path.dirname(__file__), 'data'))
|
||||
DATABASE_PATH = os.environ.get('DATABASE_PATH', os.path.join(DATA_DIR, 'site.db'))
|
||||
_SCHEMA_PATH = os.path.join(os.path.dirname(__file__), 'schema.sql')
|
||||
|
||||
|
||||
def _new_connection():
|
||||
os.makedirs(os.path.dirname(DATABASE_PATH) or '.', exist_ok=True)
|
||||
conn = sqlite3.connect(DATABASE_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute('PRAGMA foreign_keys = ON')
|
||||
conn.execute('PRAGMA journal_mode = WAL')
|
||||
return conn
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_connection():
|
||||
conn = _new_connection()
|
||||
try:
|
||||
yield conn
|
||||
conn.commit()
|
||||
except Exception:
|
||||
conn.rollback()
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _apply_schema(conn):
|
||||
with open(_SCHEMA_PATH, 'r', encoding='utf-8') as f:
|
||||
conn.executescript(f.read())
|
||||
|
||||
|
||||
def _profile_is_empty(conn):
|
||||
row = conn.execute('SELECT COUNT(*) AS c FROM profile').fetchone()
|
||||
return row['c'] == 0
|
||||
|
||||
|
||||
def _clean_tag_list(tags):
|
||||
out = []
|
||||
for t in tags or []:
|
||||
s = str(t).strip()
|
||||
if s:
|
||||
out.append(s)
|
||||
return out
|
||||
|
||||
|
||||
def _tech_name_set(conn):
|
||||
return {
|
||||
str(r['name']).strip().lower()
|
||||
for r in conn.execute('SELECT name FROM techstack_item')
|
||||
if r['name'] and str(r['name']).strip()
|
||||
}
|
||||
|
||||
|
||||
def _split_merged_tags(conn, merged):
|
||||
tech_set = _tech_name_set(conn)
|
||||
cat, tech = [], []
|
||||
for t in merged or []:
|
||||
s = str(t).strip()
|
||||
if not s:
|
||||
continue
|
||||
(tech if s.lower() in tech_set else cat).append(s)
|
||||
return cat, tech
|
||||
|
||||
|
||||
def _project_tag_payload_from_body(conn, data):
|
||||
if 'techTags' in data:
|
||||
return _clean_tag_list(data.get('tags')), _clean_tag_list(data.get('techTags'))
|
||||
return _split_merged_tags(conn, data.get('tags') or [])
|
||||
|
||||
|
||||
def _replace_project_tags(conn, project_id, tags):
|
||||
conn.execute('DELETE FROM project_tag WHERE project_id = ?', (project_id,))
|
||||
for j, tag in enumerate(_clean_tag_list(tags)):
|
||||
conn.execute(
|
||||
'INSERT INTO project_tag (project_id, sort_order, tag) VALUES (?, ?, ?)',
|
||||
(project_id, j, tag),
|
||||
)
|
||||
|
||||
|
||||
def _replace_project_tech_tags(conn, project_id, tags):
|
||||
conn.execute('DELETE FROM project_tech_tag WHERE project_id = ?', (project_id,))
|
||||
for j, tag in enumerate(_clean_tag_list(tags)):
|
||||
conn.execute(
|
||||
'INSERT INTO project_tech_tag (project_id, sort_order, tag) VALUES (?, ?, ?)',
|
||||
(project_id, j, tag),
|
||||
)
|
||||
|
||||
|
||||
def _migrate_legacy_project_tags_if_needed(conn):
|
||||
tech_set = _tech_name_set(conn)
|
||||
for row in conn.execute('SELECT id FROM project'):
|
||||
pid = row['id']
|
||||
if conn.execute(
|
||||
'SELECT 1 FROM project_tech_tag WHERE project_id = ? LIMIT 1',
|
||||
(pid,),
|
||||
).fetchone():
|
||||
continue
|
||||
tag_rows = conn.execute(
|
||||
'SELECT tag FROM project_tag WHERE project_id = ? ORDER BY sort_order, id',
|
||||
(pid,),
|
||||
).fetchall()
|
||||
if not tag_rows:
|
||||
continue
|
||||
cat, tech = [], []
|
||||
for r in tag_rows:
|
||||
s = str(r['tag']).strip()
|
||||
if not s:
|
||||
continue
|
||||
if s.lower() in tech_set:
|
||||
tech.append(s)
|
||||
else:
|
||||
cat.append(s)
|
||||
if not tech:
|
||||
continue
|
||||
conn.execute('DELETE FROM project_tag WHERE project_id = ?', (pid,))
|
||||
for j, t in enumerate(cat):
|
||||
conn.execute(
|
||||
'INSERT INTO project_tag (project_id, sort_order, tag) VALUES (?,?,?)',
|
||||
(pid, j, t),
|
||||
)
|
||||
for j, t in enumerate(tech):
|
||||
conn.execute(
|
||||
'INSERT INTO project_tech_tag (project_id, sort_order, tag) VALUES (?,?,?)',
|
||||
(pid, j, t),
|
||||
)
|
||||
|
||||
|
||||
def _seed_minimal_defaults(conn):
|
||||
"""空库时写入 profile 与技术栈区块占位行,内容通过管理后台或 SQL 维护。"""
|
||||
conn.execute('INSERT INTO profile (id, nickname) VALUES (1, ?)', ('',))
|
||||
conn.execute('INSERT INTO techstack_section (id, title) VALUES (1, ?)', ('技术栈',))
|
||||
|
||||
|
||||
def _ensure_builtin_tech_items(conn):
|
||||
"""内置技术栈项:按名称幂等追加(无则插入)。"""
|
||||
builtin = (
|
||||
(
|
||||
'Vite',
|
||||
'https://vitejs.dev/',
|
||||
'https://pan.shumengya.top/d/smy/image/devkit/vite.svg',
|
||||
'#646CFF',
|
||||
),
|
||||
(
|
||||
'Tailwind CSS',
|
||||
'https://tailwindcss.com/',
|
||||
'https://pan.shumengya.top/d/smy/image/devkit/tailwind.svg',
|
||||
'#06B6D4',
|
||||
),
|
||||
(
|
||||
'RabbitMQ',
|
||||
'https://www.rabbitmq.com/',
|
||||
'https://pan.shumengya.top/d/smy/image/devkit/rabbitmq.svg',
|
||||
'#FF6600',
|
||||
),
|
||||
)
|
||||
for name, link, svg, color in builtin:
|
||||
if conn.execute(
|
||||
'SELECT 1 FROM techstack_item WHERE name = ? LIMIT 1',
|
||||
(name,),
|
||||
).fetchone():
|
||||
continue
|
||||
m = conn.execute('SELECT COALESCE(MAX(sort_order), 0) AS m FROM techstack_item').fetchone()['m']
|
||||
sort_order = int(m) + 10
|
||||
conn.execute(
|
||||
'''INSERT INTO techstack_item (sort_order, name, link, svg, color, is_show)
|
||||
VALUES (?, ?, ?, ?, ?, ?)''',
|
||||
(sort_order, name, link, svg, color, 1),
|
||||
)
|
||||
|
||||
|
||||
def _ensure_contact_is_show_column(conn):
|
||||
"""旧库 contact 表无 is_show 时追加列(默认展示)。"""
|
||||
cols = {str(r['name']) for r in conn.execute('PRAGMA table_info(contact)').fetchall()}
|
||||
if cols and 'is_show' not in cols:
|
||||
conn.execute(
|
||||
'ALTER TABLE contact ADD COLUMN is_show INTEGER NOT NULL DEFAULT 1'
|
||||
)
|
||||
|
||||
|
||||
def init_storage():
|
||||
with get_connection() as conn:
|
||||
_apply_schema(conn)
|
||||
_ensure_contact_is_show_column(conn)
|
||||
if _profile_is_empty(conn):
|
||||
_seed_minimal_defaults(conn)
|
||||
else:
|
||||
_migrate_legacy_project_tags_if_needed(conn)
|
||||
_ensure_builtin_tech_items(conn)
|
||||
_apply_tech_item_display_patches(conn)
|
||||
_purge_retired_tech_labels(conn)
|
||||
|
||||
|
||||
def _normalize_tech_label_key(s):
|
||||
"""仅保留字母数字并小写;NFKC 合并全角/兼容字形,匹配 d1SQL 各类写法。"""
|
||||
if s is None:
|
||||
return ''
|
||||
t = unicodedata.normalize('NFKC', str(s)).strip()
|
||||
return ''.join(c.lower() for c in t if c.isalnum())
|
||||
|
||||
|
||||
def _purge_retired_tech_labels(conn):
|
||||
"""移除已废弃的技术名:项目标签 + 技术栈表(幂等)。"""
|
||||
retired_keys = {'d1sql'}
|
||||
for table in ('project_tech_tag', 'project_tag'):
|
||||
rows = conn.execute(f'SELECT id, tag FROM {table}').fetchall()
|
||||
for r in rows:
|
||||
if _normalize_tech_label_key(r['tag']) in retired_keys:
|
||||
conn.execute(f'DELETE FROM {table} WHERE id = ?', (r['id'],))
|
||||
rows = conn.execute('SELECT id, name FROM techstack_item').fetchall()
|
||||
for r in rows:
|
||||
if _normalize_tech_label_key(r['name']) in retired_keys:
|
||||
conn.execute('DELETE FROM techstack_item WHERE id = ?', (r['id'],))
|
||||
|
||||
|
||||
def _apply_tech_item_display_patches(conn):
|
||||
"""微调技术栈徽章底色(图标为自带颜色的 SVG 时避免与底色撞色)。"""
|
||||
patches = (
|
||||
# Docker.svg 为蓝色填充:深蓝→亮蓝渐变,与白字、鲸鱼标层次更清晰
|
||||
(
|
||||
'Docker',
|
||||
'linear-gradient(135deg, #052e4a 0%, #0d47a1 42%, #1e88e5 100%)',
|
||||
),
|
||||
# Flask.svg 为 #3babc3 青色,避免纯黑底;深青绿过渡到品牌青
|
||||
(
|
||||
'Flask',
|
||||
'linear-gradient(135deg, #0f3a40 0%, #1e6570 45%, #2d8fa0 100%)',
|
||||
),
|
||||
# JSON / Markdown:Simple Icons 等品牌资料中的标准色(实心)
|
||||
# JSON: https://simpleicons.org — #000000(与 Crockford JSON 标识一致)
|
||||
('JSON', '#000000'),
|
||||
# Markdown: Markdown 标识常用主色 — #083FA5
|
||||
('Markdown', '#083FA5'),
|
||||
)
|
||||
for name, color in patches:
|
||||
conn.execute(
|
||||
'UPDATE techstack_item SET color = ? WHERE name = ?',
|
||||
(color, name),
|
||||
)
|
||||
|
||||
|
||||
def _row_to_profile(row):
|
||||
if row is None:
|
||||
return None
|
||||
keys = ('favicon', 'nickname', 'avatar', 'introduction', 'footer', 'site', 'homepage')
|
||||
return {k: row[k] for k in keys if row[k] is not None}
|
||||
|
||||
|
||||
def get_profile():
|
||||
with get_connection() as conn:
|
||||
row = conn.execute(
|
||||
'''SELECT favicon, nickname, avatar, introduction, footer, site, homepage
|
||||
FROM profile WHERE id = 1'''
|
||||
).fetchone()
|
||||
return _row_to_profile(row)
|
||||
|
||||
|
||||
def get_projects():
|
||||
with get_connection() as conn:
|
||||
rows = conn.execute(
|
||||
'SELECT id, title, description, link, icon, is_admin, is_show, is_develop FROM project ORDER BY sort_order, id'
|
||||
).fetchall()
|
||||
out = []
|
||||
for row in rows:
|
||||
tags = [
|
||||
r['tag']
|
||||
for r in conn.execute(
|
||||
'SELECT tag FROM project_tag WHERE project_id = ? ORDER BY sort_order, id',
|
||||
(row['id'],),
|
||||
)
|
||||
]
|
||||
tech_tags = [
|
||||
r['tag']
|
||||
for r in conn.execute(
|
||||
'SELECT tag FROM project_tech_tag WHERE project_id = ? ORDER BY sort_order, id',
|
||||
(row['id'],),
|
||||
)
|
||||
]
|
||||
out.append({
|
||||
'id': row['id'],
|
||||
'title': row['title'],
|
||||
'description': row['description'] or '',
|
||||
'link': row['link'] or '',
|
||||
'icon': row['icon'] or '',
|
||||
'tags': tags,
|
||||
'techTags': tech_tags,
|
||||
'admin': bool(row['is_admin']),
|
||||
'show': bool(row['is_show']),
|
||||
'develop': bool(row['is_develop']),
|
||||
})
|
||||
return {'projects': out}
|
||||
|
||||
|
||||
def _contact_row_to_dict(row):
|
||||
return {
|
||||
'id': row['id'],
|
||||
'type': row['type'],
|
||||
'label': row['label'] or '',
|
||||
'value': row['value'] or '',
|
||||
'link': row['link'] or '',
|
||||
'icon': row['icon'] or '',
|
||||
'show': bool(row['is_show']),
|
||||
}
|
||||
|
||||
|
||||
def get_contacts():
|
||||
"""前台公开接口:仅返回首页展示的联系方式。"""
|
||||
with get_connection() as conn:
|
||||
rows = conn.execute(
|
||||
'''SELECT id, type, label, value, link, icon, is_show FROM contact
|
||||
WHERE COALESCE(is_show, 1) = 1
|
||||
ORDER BY sort_order, id'''
|
||||
).fetchall()
|
||||
return {
|
||||
'contacts': [_contact_row_to_dict(row) for row in rows],
|
||||
}
|
||||
|
||||
|
||||
def get_techstack():
|
||||
"""技术栈 svg 字段:可为 data/logo 下文件名,或 http(s) 外链 SVG 地址。"""
|
||||
with get_connection() as conn:
|
||||
sec = conn.execute('SELECT title FROM techstack_section WHERE id = 1').fetchone()
|
||||
title = sec['title'] if sec else '技术栈'
|
||||
rows = conn.execute(
|
||||
'SELECT id, name, link, svg, color, is_show FROM techstack_item ORDER BY sort_order, id'
|
||||
).fetchall()
|
||||
return {
|
||||
'title': title,
|
||||
'items': [
|
||||
{
|
||||
'id': row['id'],
|
||||
'name': row['name'],
|
||||
'link': row['link'] or '',
|
||||
'svg': row['svg'] or '',
|
||||
'color': row['color'] or '',
|
||||
'show': bool(row['is_show']),
|
||||
}
|
||||
for row in rows
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def get_all(include_hidden_contacts=False):
|
||||
with get_connection() as conn:
|
||||
profile_row = conn.execute(
|
||||
'''SELECT favicon, nickname, avatar, introduction, footer, site, homepage
|
||||
FROM profile WHERE id = 1'''
|
||||
).fetchone()
|
||||
profile = _row_to_profile(profile_row)
|
||||
|
||||
tech_sec = conn.execute('SELECT title FROM techstack_section WHERE id = 1').fetchone()
|
||||
tech_title = tech_sec['title'] if tech_sec else '技术栈'
|
||||
tech_rows = conn.execute(
|
||||
'SELECT id, name, link, svg, color, is_show FROM techstack_item ORDER BY sort_order, id'
|
||||
).fetchall()
|
||||
techstack = {
|
||||
'title': tech_title,
|
||||
'items': [
|
||||
{
|
||||
'id': r['id'],
|
||||
'name': r['name'],
|
||||
'link': r['link'] or '',
|
||||
'svg': r['svg'] or '',
|
||||
'color': r['color'] or '',
|
||||
'show': bool(r['is_show']),
|
||||
}
|
||||
for r in tech_rows
|
||||
],
|
||||
}
|
||||
|
||||
proj_rows = conn.execute(
|
||||
'SELECT id, title, description, link, icon, is_admin, is_show, is_develop FROM project ORDER BY sort_order, id'
|
||||
).fetchall()
|
||||
projects = []
|
||||
for row in proj_rows:
|
||||
tags = [
|
||||
r['tag']
|
||||
for r in conn.execute(
|
||||
'SELECT tag FROM project_tag WHERE project_id = ? ORDER BY sort_order, id',
|
||||
(row['id'],),
|
||||
)
|
||||
]
|
||||
tech_tags = [
|
||||
r['tag']
|
||||
for r in conn.execute(
|
||||
'SELECT tag FROM project_tech_tag WHERE project_id = ? ORDER BY sort_order, id',
|
||||
(row['id'],),
|
||||
)
|
||||
]
|
||||
projects.append({
|
||||
'id': row['id'],
|
||||
'title': row['title'],
|
||||
'description': row['description'] or '',
|
||||
'link': row['link'] or '',
|
||||
'icon': row['icon'] or '',
|
||||
'tags': tags,
|
||||
'techTags': tech_tags,
|
||||
'admin': bool(row['is_admin']),
|
||||
'show': bool(row['is_show']),
|
||||
'develop': bool(row['is_develop']),
|
||||
})
|
||||
|
||||
contact_rows = conn.execute(
|
||||
'SELECT id, type, label, value, link, icon, is_show FROM contact ORDER BY sort_order, id'
|
||||
).fetchall()
|
||||
contact_list = []
|
||||
for cr in contact_rows:
|
||||
if not include_hidden_contacts and not bool(cr['is_show']):
|
||||
continue
|
||||
contact_list.append(_contact_row_to_dict(cr))
|
||||
contacts = {'contacts': contact_list}
|
||||
|
||||
return {
|
||||
'profile': profile,
|
||||
'techstack': techstack,
|
||||
'projects': {'projects': projects},
|
||||
'contacts': contacts,
|
||||
}
|
||||
|
||||
|
||||
def update_profile(payload):
|
||||
allowed = {'favicon', 'nickname', 'avatar', 'introduction', 'footer', 'site', 'homepage'}
|
||||
updates = {k: payload[k] for k in allowed if k in payload}
|
||||
if not updates:
|
||||
return
|
||||
sets = ', '.join(f'{k} = ?' for k in updates)
|
||||
vals = list(updates.values())
|
||||
with get_connection() as conn:
|
||||
conn.execute(f'UPDATE profile SET {sets} WHERE id = 1', vals)
|
||||
|
||||
|
||||
def create_project(data):
|
||||
with get_connection() as conn:
|
||||
cat_tags, tech_tags = _project_tag_payload_from_body(conn, data)
|
||||
m = conn.execute('SELECT COALESCE(MAX(sort_order), 0) AS m FROM project').fetchone()['m']
|
||||
sort_order = int(m) + 10
|
||||
cur = conn.execute(
|
||||
'''INSERT INTO project (sort_order, title, description, link, icon, is_admin, is_show, is_develop)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
|
||||
(
|
||||
sort_order,
|
||||
data['title'],
|
||||
data.get('description') or '',
|
||||
data.get('link') or '',
|
||||
data.get('icon') or '',
|
||||
1 if data.get('admin') else 0,
|
||||
1 if data.get('show', True) else 0,
|
||||
1 if data.get('develop') else 0,
|
||||
),
|
||||
)
|
||||
pid = cur.lastrowid
|
||||
_replace_project_tags(conn, pid, cat_tags)
|
||||
_replace_project_tech_tags(conn, pid, tech_tags)
|
||||
return pid
|
||||
|
||||
|
||||
def update_project(project_id, data):
|
||||
with get_connection() as conn:
|
||||
fields = []
|
||||
vals = []
|
||||
if 'title' in data:
|
||||
fields.append('title = ?')
|
||||
vals.append(data['title'])
|
||||
if 'description' in data:
|
||||
fields.append('description = ?')
|
||||
vals.append(data['description'] or '')
|
||||
if 'link' in data:
|
||||
fields.append('link = ?')
|
||||
vals.append(data['link'] or '')
|
||||
if 'icon' in data:
|
||||
fields.append('icon = ?')
|
||||
vals.append(data['icon'] or '')
|
||||
if 'admin' in data:
|
||||
fields.append('is_admin = ?')
|
||||
vals.append(1 if data['admin'] else 0)
|
||||
if 'show' in data:
|
||||
fields.append('is_show = ?')
|
||||
vals.append(1 if data['show'] else 0)
|
||||
if 'develop' in data:
|
||||
fields.append('is_develop = ?')
|
||||
vals.append(1 if data['develop'] else 0)
|
||||
if fields:
|
||||
vals.append(project_id)
|
||||
conn.execute(
|
||||
f"UPDATE project SET {', '.join(fields)} WHERE id = ?",
|
||||
vals,
|
||||
)
|
||||
if 'tags' in data or 'techTags' in data:
|
||||
if 'tags' in data and 'techTags' in data:
|
||||
_replace_project_tags(conn, project_id, data['tags'])
|
||||
_replace_project_tech_tags(conn, project_id, data['techTags'])
|
||||
elif 'tags' in data:
|
||||
cat, tech = _split_merged_tags(conn, data['tags'])
|
||||
_replace_project_tags(conn, project_id, cat)
|
||||
_replace_project_tech_tags(conn, project_id, tech)
|
||||
else:
|
||||
_replace_project_tech_tags(conn, project_id, data['techTags'])
|
||||
|
||||
|
||||
def delete_project(project_id):
|
||||
with get_connection() as conn:
|
||||
conn.execute('DELETE FROM project WHERE id = ?', (project_id,))
|
||||
|
||||
|
||||
def create_contact(data):
|
||||
with get_connection() as conn:
|
||||
m = conn.execute('SELECT COALESCE(MAX(sort_order), 0) AS m FROM contact').fetchone()['m']
|
||||
sort_order = int(m) + 10
|
||||
cur = conn.execute(
|
||||
'''INSERT INTO contact (sort_order, type, label, value, link, icon, is_show)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)''',
|
||||
(
|
||||
sort_order,
|
||||
data['type'],
|
||||
data.get('label') or '',
|
||||
data.get('value') or '',
|
||||
data.get('link') or '',
|
||||
data.get('icon') or '',
|
||||
1 if data.get('show', True) else 0,
|
||||
),
|
||||
)
|
||||
return cur.lastrowid
|
||||
|
||||
|
||||
def update_contact(contact_id, data):
|
||||
with get_connection() as conn:
|
||||
fields = []
|
||||
vals = []
|
||||
for col, key in (
|
||||
('type', 'type'),
|
||||
('label', 'label'),
|
||||
('value', 'value'),
|
||||
('link', 'link'),
|
||||
('icon', 'icon'),
|
||||
):
|
||||
if key in data:
|
||||
fields.append(f'{col} = ?')
|
||||
vals.append(data[key] or '')
|
||||
if 'show' in data:
|
||||
fields.append('is_show = ?')
|
||||
vals.append(1 if data['show'] else 0)
|
||||
if fields:
|
||||
vals.append(contact_id)
|
||||
conn.execute(
|
||||
f"UPDATE contact SET {', '.join(fields)} WHERE id = ?",
|
||||
vals,
|
||||
)
|
||||
|
||||
|
||||
def delete_contact(contact_id):
|
||||
with get_connection() as conn:
|
||||
conn.execute('DELETE FROM contact WHERE id = ?', (contact_id,))
|
||||
|
||||
|
||||
def set_techstack_title(title):
|
||||
t = title or '技术栈'
|
||||
with get_connection() as conn:
|
||||
row = conn.execute('SELECT id FROM techstack_section WHERE id = 1').fetchone()
|
||||
if row:
|
||||
conn.execute('UPDATE techstack_section SET title = ? WHERE id = 1', (t,))
|
||||
else:
|
||||
conn.execute('INSERT INTO techstack_section (id, title) VALUES (1, ?)', (t,))
|
||||
|
||||
|
||||
def create_techstack_item(data):
|
||||
with get_connection() as conn:
|
||||
m = conn.execute('SELECT COALESCE(MAX(sort_order), 0) AS m FROM techstack_item').fetchone()['m']
|
||||
sort_order = int(m) + 10
|
||||
cur = conn.execute(
|
||||
'''INSERT INTO techstack_item (sort_order, name, link, svg, color, is_show)
|
||||
VALUES (?, ?, ?, ?, ?, ?)''',
|
||||
(
|
||||
sort_order,
|
||||
data['name'],
|
||||
data.get('link') or '',
|
||||
data.get('svg') or '',
|
||||
data.get('color') or '',
|
||||
1 if data.get('show', True) else 0,
|
||||
),
|
||||
)
|
||||
return cur.lastrowid
|
||||
|
||||
|
||||
def update_techstack_item(item_id, data):
|
||||
with get_connection() as conn:
|
||||
fields = []
|
||||
vals = []
|
||||
if 'name' in data:
|
||||
fields.append('name = ?')
|
||||
vals.append(data['name'])
|
||||
if 'link' in data:
|
||||
fields.append('link = ?')
|
||||
vals.append(data['link'] or '')
|
||||
if 'svg' in data:
|
||||
fields.append('svg = ?')
|
||||
vals.append(data['svg'] or '')
|
||||
if 'color' in data:
|
||||
fields.append('color = ?')
|
||||
vals.append(data['color'] or '')
|
||||
if 'show' in data:
|
||||
fields.append('is_show = ?')
|
||||
vals.append(1 if data['show'] else 0)
|
||||
if fields:
|
||||
vals.append(item_id)
|
||||
conn.execute(
|
||||
f"UPDATE techstack_item SET {', '.join(fields)} WHERE id = ?",
|
||||
vals,
|
||||
)
|
||||
|
||||
|
||||
def delete_techstack_item(item_id):
|
||||
with get_connection() as conn:
|
||||
conn.execute('DELETE FROM techstack_item WHERE id = ?', (item_id,))
|
||||
Reference in New Issue
Block a user