noticecard 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- noticecard/__init__.py +1 -1
- noticecard/app.py +9 -7
- noticecard/cli.py +21 -1
- noticecard/migrations.py +84 -0
- noticecard/models.py +1 -2
- noticecard/schemas.py +4 -7
- noticecard/templates.py +6 -14
- {noticecard-0.1.0.dist-info → noticecard-0.2.1.dist-info}/METADATA +3 -5
- noticecard-0.2.1.dist-info/RECORD +12 -0
- noticecard-0.1.0.dist-info/RECORD +0 -11
- {noticecard-0.1.0.dist-info → noticecard-0.2.1.dist-info}/WHEEL +0 -0
- {noticecard-0.1.0.dist-info → noticecard-0.2.1.dist-info}/entry_points.txt +0 -0
- {noticecard-0.1.0.dist-info → noticecard-0.2.1.dist-info}/top_level.txt +0 -0
noticecard/__init__.py
CHANGED
noticecard/app.py
CHANGED
|
@@ -32,6 +32,10 @@ def create_app(db_path: str) -> FastAPI:
|
|
|
32
32
|
"""Create and configure FastAPI application."""
|
|
33
33
|
global _session_maker, _db_path
|
|
34
34
|
|
|
35
|
+
# Check and migrate database if needed
|
|
36
|
+
from .migrations import check_and_migrate
|
|
37
|
+
check_and_migrate(db_path)
|
|
38
|
+
|
|
35
39
|
_db_path = db_path
|
|
36
40
|
engine = init_db(db_path)
|
|
37
41
|
_session_maker = get_session_maker(engine)
|
|
@@ -81,7 +85,7 @@ def create_app(db_path: str) -> FastAPI:
|
|
|
81
85
|
},
|
|
82
86
|
"serverInfo": {
|
|
83
87
|
"name": "noticecard",
|
|
84
|
-
"version": "0.1
|
|
88
|
+
"version": "0.2.1"
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
}
|
|
@@ -100,8 +104,7 @@ def create_app(db_path: str) -> FastAPI:
|
|
|
100
104
|
"type": "object",
|
|
101
105
|
"properties": {
|
|
102
106
|
"title": {"type": "string", "description": "卡片标题"},
|
|
103
|
-
"
|
|
104
|
-
"desc2": {"type": "string", "description": "描述2(可选)"}
|
|
107
|
+
"description": {"type": "string", "description": "卡片描述(可选)"}
|
|
105
108
|
},
|
|
106
109
|
"required": ["title"]
|
|
107
110
|
}
|
|
@@ -136,8 +139,7 @@ def create_app(db_path: str) -> FastAPI:
|
|
|
136
139
|
"properties": {
|
|
137
140
|
"card_id": {"type": "number", "description": "卡片ID"},
|
|
138
141
|
"title": {"type": "string", "description": "新标题(可选)"},
|
|
139
|
-
"
|
|
140
|
-
"desc2": {"type": "string", "description": "新描述2(可选)"}
|
|
142
|
+
"description": {"type": "string", "description": "新描述(可选)"}
|
|
141
143
|
},
|
|
142
144
|
"required": ["card_id"]
|
|
143
145
|
}
|
|
@@ -175,7 +177,7 @@ def create_app(db_path: str) -> FastAPI:
|
|
|
175
177
|
"result": {
|
|
176
178
|
"content": [{
|
|
177
179
|
"type": "text",
|
|
178
|
-
"text": f"✅ 创建成功!\nID: {card.id}\n标题: {card.title}\n
|
|
180
|
+
"text": f"✅ 创建成功!\nID: {card.id}\n标题: {card.title}\n描述: {card.description or '无'}"
|
|
179
181
|
}]
|
|
180
182
|
}
|
|
181
183
|
}
|
|
@@ -217,7 +219,7 @@ def create_app(db_path: str) -> FastAPI:
|
|
|
217
219
|
"result": {
|
|
218
220
|
"content": [{
|
|
219
221
|
"type": "text",
|
|
220
|
-
"text": f"📄 卡片详情\n\nID: {card.id}\n标题: {card.title}\n
|
|
222
|
+
"text": f"📄 卡片详情\n\nID: {card.id}\n标题: {card.title}\n描述: {card.description or '无'}\n创建时间: {card.created_at}\n更新时间: {card.updated_at}"
|
|
221
223
|
}]
|
|
222
224
|
}
|
|
223
225
|
}
|
noticecard/cli.py
CHANGED
|
@@ -7,7 +7,7 @@ import uvicorn
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@click.group()
|
|
10
|
-
@click.version_option(version="0.1
|
|
10
|
+
@click.version_option(version="0.2.1")
|
|
11
11
|
def main():
|
|
12
12
|
"""NoticeCard 服务端命令行工具"""
|
|
13
13
|
pass
|
|
@@ -76,5 +76,25 @@ def init(work_dir: str):
|
|
|
76
76
|
click.echo(f"数据库已初始化: {db_path}")
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
@main.command()
|
|
80
|
+
@click.option("--dir", "work_dir", default=".", help="工作目录")
|
|
81
|
+
def migrate(work_dir: str):
|
|
82
|
+
"""迁移数据库到最新版本"""
|
|
83
|
+
work_path = Path(work_dir).resolve()
|
|
84
|
+
db_path = work_path / "noticecard.db"
|
|
85
|
+
|
|
86
|
+
if not db_path.exists():
|
|
87
|
+
click.echo(f"数据库不存在: {db_path}")
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
from .migrations import migrate_to_v2
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
migrate_to_v2(str(db_path))
|
|
94
|
+
click.echo(f"✅ 数据库迁移成功: {db_path}")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
click.echo(f"❌ 迁移失败: {e}", err=True)
|
|
97
|
+
|
|
98
|
+
|
|
79
99
|
if __name__ == "__main__":
|
|
80
100
|
main()
|
noticecard/migrations.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Database migration utilities."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def migrate_to_v2(db_path: str):
|
|
8
|
+
"""Migrate database from v1 (desc1, desc2) to v2 (description)."""
|
|
9
|
+
conn = sqlite3.connect(db_path)
|
|
10
|
+
cursor = conn.cursor()
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
# Check if we need to migrate
|
|
14
|
+
cursor.execute("PRAGMA table_info(cards)")
|
|
15
|
+
columns = [col[1] for col in cursor.fetchall()]
|
|
16
|
+
|
|
17
|
+
if 'description' in columns:
|
|
18
|
+
print("Database already migrated to v2")
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
if 'desc1' not in columns:
|
|
22
|
+
print("Database is already in correct format")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
print("Migrating database to v2...")
|
|
26
|
+
|
|
27
|
+
# Create new table with description field
|
|
28
|
+
cursor.execute("""
|
|
29
|
+
CREATE TABLE cards_new (
|
|
30
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
31
|
+
title TEXT NOT NULL,
|
|
32
|
+
description TEXT,
|
|
33
|
+
created_at TIMESTAMP NOT NULL,
|
|
34
|
+
updated_at TIMESTAMP NOT NULL
|
|
35
|
+
)
|
|
36
|
+
""")
|
|
37
|
+
|
|
38
|
+
# Migrate data: combine desc1 and desc2 into description
|
|
39
|
+
cursor.execute("""
|
|
40
|
+
INSERT INTO cards_new (id, title, description, created_at, updated_at)
|
|
41
|
+
SELECT
|
|
42
|
+
id,
|
|
43
|
+
title,
|
|
44
|
+
CASE
|
|
45
|
+
WHEN desc1 IS NOT NULL AND desc2 IS NOT NULL THEN desc1 || '\n' || desc2
|
|
46
|
+
WHEN desc1 IS NOT NULL THEN desc1
|
|
47
|
+
WHEN desc2 IS NOT NULL THEN desc2
|
|
48
|
+
ELSE NULL
|
|
49
|
+
END as description,
|
|
50
|
+
created_at,
|
|
51
|
+
updated_at
|
|
52
|
+
FROM cards
|
|
53
|
+
""")
|
|
54
|
+
|
|
55
|
+
# Drop old table and rename new one
|
|
56
|
+
cursor.execute("DROP TABLE cards")
|
|
57
|
+
cursor.execute("ALTER TABLE cards_new RENAME TO cards")
|
|
58
|
+
|
|
59
|
+
# Create index
|
|
60
|
+
cursor.execute("CREATE INDEX ix_cards_title ON cards (title)")
|
|
61
|
+
cursor.execute("CREATE INDEX ix_cards_id ON cards (id)")
|
|
62
|
+
|
|
63
|
+
conn.commit()
|
|
64
|
+
print("Migration completed successfully!")
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
conn.rollback()
|
|
68
|
+
print(f"Migration failed: {e}")
|
|
69
|
+
raise
|
|
70
|
+
finally:
|
|
71
|
+
conn.close()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def check_and_migrate(db_path: str):
|
|
75
|
+
"""Check database version and migrate if needed."""
|
|
76
|
+
if not Path(db_path).exists():
|
|
77
|
+
# New database, no migration needed
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
migrate_to_v2(db_path)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f"Warning: Could not migrate database: {e}")
|
|
84
|
+
print("You may need to delete the old database and start fresh.")
|
noticecard/models.py
CHANGED
|
@@ -17,8 +17,7 @@ class Card(Base):
|
|
|
17
17
|
|
|
18
18
|
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
|
19
19
|
title = Column(String, nullable=False, index=True)
|
|
20
|
-
|
|
21
|
-
desc2 = Column(String, nullable=True)
|
|
20
|
+
description = Column(String, nullable=True)
|
|
22
21
|
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False)
|
|
23
22
|
updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False)
|
|
24
23
|
|
noticecard/schemas.py
CHANGED
|
@@ -8,8 +8,7 @@ from pydantic import BaseModel, Field
|
|
|
8
8
|
class CardBase(BaseModel):
|
|
9
9
|
"""Base card schema."""
|
|
10
10
|
title: str = Field(..., min_length=1, max_length=200, description="Card title")
|
|
11
|
-
|
|
12
|
-
desc2: Optional[str] = Field(None, max_length=500, description="Second description")
|
|
11
|
+
description: Optional[str] = Field(None, max_length=1000, description="Card description")
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class CardCreate(CardBase):
|
|
@@ -20,8 +19,7 @@ class CardCreate(CardBase):
|
|
|
20
19
|
"examples": [
|
|
21
20
|
{
|
|
22
21
|
"title": "会议通知",
|
|
23
|
-
"
|
|
24
|
-
"desc2": "一号会议室"
|
|
22
|
+
"description": "明天下午2点在一号会议室"
|
|
25
23
|
}
|
|
26
24
|
]
|
|
27
25
|
}
|
|
@@ -31,15 +29,14 @@ class CardCreate(CardBase):
|
|
|
31
29
|
class CardUpdate(BaseModel):
|
|
32
30
|
"""Schema for updating an existing card."""
|
|
33
31
|
title: Optional[str] = Field(None, min_length=1, max_length=200, description="Card title")
|
|
34
|
-
|
|
35
|
-
desc2: Optional[str] = Field(None, max_length=500, description="Second description")
|
|
32
|
+
description: Optional[str] = Field(None, max_length=1000, description="Card description")
|
|
36
33
|
|
|
37
34
|
model_config = {
|
|
38
35
|
"json_schema_extra": {
|
|
39
36
|
"examples": [
|
|
40
37
|
{
|
|
41
38
|
"title": "更新后的标题",
|
|
42
|
-
"
|
|
39
|
+
"description": "更新后的描述"
|
|
43
40
|
}
|
|
44
41
|
]
|
|
45
42
|
}
|
noticecard/templates.py
CHANGED
|
@@ -193,14 +193,9 @@ def get_index_html() -> str:
|
|
|
193
193
|
<small id="titleHint" class="form-hint">必填,最多200字符</small>
|
|
194
194
|
</div>
|
|
195
195
|
<div class="mb-3">
|
|
196
|
-
<label for="
|
|
197
|
-
<textarea class="form-control" id="
|
|
198
|
-
<small id="
|
|
199
|
-
</div>
|
|
200
|
-
<div class="mb-3">
|
|
201
|
-
<label for="desc2" class="form-label">描述2</label>
|
|
202
|
-
<textarea class="form-control" id="desc2" name="desc2" rows="2" maxlength="500" aria-describedby="desc2Hint"></textarea>
|
|
203
|
-
<small id="desc2Hint" class="form-hint">可选,最多500字符</small>
|
|
196
|
+
<label for="description" class="form-label">描述</label>
|
|
197
|
+
<textarea class="form-control" id="description" name="description" rows="4" maxlength="1000" aria-describedby="descriptionHint"></textarea>
|
|
198
|
+
<small id="descriptionHint" class="form-hint">可选,最多1000字符</small>
|
|
204
199
|
</div>
|
|
205
200
|
</form>
|
|
206
201
|
</div>
|
|
@@ -260,8 +255,7 @@ def get_index_html() -> str:
|
|
|
260
255
|
<article class="notice-card" data-card-id="${card.id}" role="listitem">
|
|
261
256
|
<div class="card-body">
|
|
262
257
|
<h3 class="card-title heading">${escapeHtml(card.title)}</h3>
|
|
263
|
-
${card.
|
|
264
|
-
${card.desc2 ? `<p class="text-muted mb-0">${escapeHtml(card.desc2)}</p>` : ''}
|
|
258
|
+
${card.description ? `<p class="text-muted mb-0">${escapeHtml(card.description)}</p>` : ''}
|
|
265
259
|
</div>
|
|
266
260
|
<div class="card-footer">
|
|
267
261
|
<div class="d-flex justify-content-between align-items-center">
|
|
@@ -308,8 +302,7 @@ def get_index_html() -> str:
|
|
|
308
302
|
document.getElementById('modalTitle').textContent = '编辑卡片';
|
|
309
303
|
document.getElementById('cardId').value = card.id;
|
|
310
304
|
document.getElementById('title').value = card.title;
|
|
311
|
-
document.getElementById('
|
|
312
|
-
document.getElementById('desc2').value = card.desc2 || '';
|
|
305
|
+
document.getElementById('description').value = card.description || '';
|
|
313
306
|
|
|
314
307
|
modal.show();
|
|
315
308
|
} catch (error) {
|
|
@@ -329,8 +322,7 @@ def get_index_html() -> str:
|
|
|
329
322
|
const id = document.getElementById('cardId').value;
|
|
330
323
|
const data = {
|
|
331
324
|
title: document.getElementById('title').value.trim(),
|
|
332
|
-
|
|
333
|
-
desc2: document.getElementById('desc2').value.trim() || null
|
|
325
|
+
description: document.getElementById('description').value.trim() || null
|
|
334
326
|
};
|
|
335
327
|
|
|
336
328
|
const spinner = document.getElementById('saveSpinner');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: noticecard
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: NoticeCard server with RESTful API and web UI
|
|
5
5
|
Author: NoticeCard Team
|
|
6
6
|
License: MIT
|
|
@@ -115,8 +115,7 @@ curl -X POST "http://localhost:3143/cards/" \
|
|
|
115
115
|
-H "Content-Type: application/json" \
|
|
116
116
|
-d '{
|
|
117
117
|
"title": "会议通知",
|
|
118
|
-
"
|
|
119
|
-
"desc2": "一号会议室"
|
|
118
|
+
"description": "明天下午2点在一号会议室"
|
|
120
119
|
}'
|
|
121
120
|
```
|
|
122
121
|
|
|
@@ -136,8 +135,7 @@ curl "http://localhost:3143/cards/?skip=0&limit=10"
|
|
|
136
135
|
|------|------|------|
|
|
137
136
|
| id | Integer | 主键,自动生成 |
|
|
138
137
|
| title | String | 标题(必填) |
|
|
139
|
-
|
|
|
140
|
-
| desc2 | String | 描述2(可选) |
|
|
138
|
+
| description | String | 描述(可选) |
|
|
141
139
|
| created_at | DateTime | 创建时间 |
|
|
142
140
|
| updated_at | DateTime | 更新时间 |
|
|
143
141
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
noticecard/__init__.py,sha256=wx4Y1_IMhWSuRhcAJ8tUmdBV1YxQdEihvzS2TY4BDno,92
|
|
2
|
+
noticecard/app.py,sha256=HAg43uB0nS2aNViBq3eJFqTuY26s5O-_i5sGarHz4Bc,18746
|
|
3
|
+
noticecard/cli.py,sha256=rS0mlJ72TbBm2Y0sQH908nAiwiD8jnAu1wLV1Fj_Glc,3156
|
|
4
|
+
noticecard/migrations.py,sha256=YM8-CPZA-a2cTRCsBKP1ZljqXyTYLykrydxswOfP-9o,2731
|
|
5
|
+
noticecard/models.py,sha256=P6eirD7tVwOjAatA5MOZHDnjPr4-GJ_5zYCPMp0tFZs,1475
|
|
6
|
+
noticecard/schemas.py,sha256=FMEKNcnMR078F9T2hgEsygOVQgyJykHPRhlN_WSGU5A,1491
|
|
7
|
+
noticecard/templates.py,sha256=TCOr9Jv44I45GmrEPwQ_2JWRdXZNL-nRS6FKkU-65z4,19162
|
|
8
|
+
noticecard-0.2.1.dist-info/METADATA,sha256=mBQcsTDOELA1ACpHbqbCNI6KIAllbMNVV-aiv4Ulmgc,5553
|
|
9
|
+
noticecard-0.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10
|
+
noticecard-0.2.1.dist-info/entry_points.txt,sha256=j-F8uQSatTV2uyMh0JM3iXjTX2PiTIHX5FozbGy-u1A,46
|
|
11
|
+
noticecard-0.2.1.dist-info/top_level.txt,sha256=WvuJAJMD8eTfRfGdeMAzSLXiv8UYjlfUBa5mw9g7Mqg,11
|
|
12
|
+
noticecard-0.2.1.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
noticecard/__init__.py,sha256=UC1UfxkAo2--veC7cav9Udf2fSk8_ZMty0wcr-g3EXc,92
|
|
2
|
-
noticecard/app.py,sha256=CWNRM-I7D-OiY5uKU2jyBIo_KR6NdjPnpy0-LfPu3mM,18882
|
|
3
|
-
noticecard/cli.py,sha256=xxikKsKvFeL8lssC5j24wwi3lbJOjnm6syz0gQVnGK4,2556
|
|
4
|
-
noticecard/models.py,sha256=B4XY9xLkI7JYSQwF1y7rYMZfbNsd7hprZ7bm6-ohWks,1512
|
|
5
|
-
noticecard/schemas.py,sha256=70Wn_1NGMxFL-34DnmSNr7_grYBNiHzuAMxCJYF6kkQ,1675
|
|
6
|
-
noticecard/templates.py,sha256=IihqLS823JXGcCKHRkxiAcsMqn5L94x_Bs3M9i-HMNE,19766
|
|
7
|
-
noticecard-0.1.0.dist-info/METADATA,sha256=sA8kqjey-1hYPAWahyAb7Qw2iOAjw4JwONjh7-YqyQg,5595
|
|
8
|
-
noticecard-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
-
noticecard-0.1.0.dist-info/entry_points.txt,sha256=j-F8uQSatTV2uyMh0JM3iXjTX2PiTIHX5FozbGy-u1A,46
|
|
10
|
-
noticecard-0.1.0.dist-info/top_level.txt,sha256=WvuJAJMD8eTfRfGdeMAzSLXiv8UYjlfUBa5mw9g7Mqg,11
|
|
11
|
-
noticecard-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|