database.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
  2. from sqlalchemy.orm import DeclarativeBase
  3. from backend.app.core.config import settings
  4. engine = create_async_engine(
  5. settings.database_url,
  6. echo=settings.debug,
  7. )
  8. async_session = async_sessionmaker(
  9. engine,
  10. class_=AsyncSession,
  11. expire_on_commit=False,
  12. )
  13. class Base(DeclarativeBase):
  14. pass
  15. async def get_db() -> AsyncSession:
  16. async with async_session() as session:
  17. try:
  18. yield session
  19. await session.commit()
  20. except Exception:
  21. await session.rollback()
  22. raise
  23. finally:
  24. await session.close()
  25. async def init_db():
  26. # Import models to register them with SQLAlchemy
  27. from backend.app.models import ( # noqa: F401
  28. api_key,
  29. archive,
  30. external_link,
  31. filament,
  32. kprofile_note,
  33. library,
  34. maintenance,
  35. notification,
  36. notification_template,
  37. print_queue,
  38. printer,
  39. project,
  40. project_bom,
  41. settings,
  42. smart_plug,
  43. )
  44. async with engine.begin() as conn:
  45. await conn.run_sync(Base.metadata.create_all)
  46. # Run migrations for new columns (SQLite doesn't auto-add columns)
  47. await run_migrations(conn)
  48. # Seed default notification templates
  49. await seed_notification_templates()
  50. async def run_migrations(conn):
  51. """Add new columns to existing tables if they don't exist."""
  52. from sqlalchemy import text
  53. # Migration: Add is_favorite column to print_archives
  54. try:
  55. await conn.execute(text("ALTER TABLE print_archives ADD COLUMN is_favorite BOOLEAN DEFAULT 0"))
  56. except Exception:
  57. # Column already exists
  58. pass
  59. # Migration: Add content_hash column to print_archives for duplicate detection
  60. try:
  61. await conn.execute(text("ALTER TABLE print_archives ADD COLUMN content_hash VARCHAR(64)"))
  62. except Exception:
  63. # Column already exists
  64. pass
  65. # Migration: Add auto_off_executed column to smart_plugs
  66. try:
  67. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN auto_off_executed BOOLEAN DEFAULT 0"))
  68. except Exception:
  69. # Column already exists
  70. pass
  71. # Migration: Add on_print_stopped column to notification_providers
  72. try:
  73. await conn.execute(text("ALTER TABLE notification_providers ADD COLUMN on_print_stopped BOOLEAN DEFAULT 1"))
  74. except Exception:
  75. # Column already exists
  76. pass
  77. # Migration: Add source_3mf_path column to print_archives
  78. try:
  79. await conn.execute(text("ALTER TABLE print_archives ADD COLUMN source_3mf_path VARCHAR(500)"))
  80. except Exception:
  81. # Column already exists
  82. pass
  83. # Migration: Add f3d_path column to print_archives for Fusion 360 design files
  84. try:
  85. await conn.execute(text("ALTER TABLE print_archives ADD COLUMN f3d_path VARCHAR(500)"))
  86. except Exception:
  87. # Column already exists
  88. pass
  89. # Migration: Add on_maintenance_due column to notification_providers
  90. try:
  91. await conn.execute(text("ALTER TABLE notification_providers ADD COLUMN on_maintenance_due BOOLEAN DEFAULT 0"))
  92. except Exception:
  93. # Column already exists
  94. pass
  95. # Migration: Add location column to printers for grouping
  96. try:
  97. await conn.execute(text("ALTER TABLE printers ADD COLUMN location VARCHAR(100)"))
  98. except Exception:
  99. # Column already exists
  100. pass
  101. # Migration: Add interval_type column to maintenance_types
  102. try:
  103. await conn.execute(text("ALTER TABLE maintenance_types ADD COLUMN interval_type VARCHAR(20) DEFAULT 'hours'"))
  104. except Exception:
  105. # Column already exists
  106. pass
  107. # Migration: Add custom_interval_type column to printer_maintenance
  108. try:
  109. await conn.execute(text("ALTER TABLE printer_maintenance ADD COLUMN custom_interval_type VARCHAR(20)"))
  110. except Exception:
  111. # Column already exists
  112. pass
  113. # Migration: Add power alert columns to smart_plugs
  114. try:
  115. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN power_alert_enabled BOOLEAN DEFAULT 0"))
  116. except Exception:
  117. pass
  118. try:
  119. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN power_alert_high REAL"))
  120. except Exception:
  121. pass
  122. try:
  123. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN power_alert_low REAL"))
  124. except Exception:
  125. pass
  126. try:
  127. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN power_alert_last_triggered DATETIME"))
  128. except Exception:
  129. pass
  130. # Migration: Add schedule columns to smart_plugs
  131. try:
  132. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN schedule_enabled BOOLEAN DEFAULT 0"))
  133. except Exception:
  134. pass
  135. try:
  136. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN schedule_on_time VARCHAR(5)"))
  137. except Exception:
  138. pass
  139. try:
  140. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN schedule_off_time VARCHAR(5)"))
  141. except Exception:
  142. pass
  143. # Migration: Add daily digest columns to notification_providers
  144. try:
  145. await conn.execute(text("ALTER TABLE notification_providers ADD COLUMN daily_digest_enabled BOOLEAN DEFAULT 0"))
  146. except Exception:
  147. pass
  148. try:
  149. await conn.execute(text("ALTER TABLE notification_providers ADD COLUMN daily_digest_time VARCHAR(5)"))
  150. except Exception:
  151. pass
  152. # Migration: Add project_id column to print_archives
  153. try:
  154. await conn.execute(
  155. text("ALTER TABLE print_archives ADD COLUMN project_id INTEGER REFERENCES projects(id) ON DELETE SET NULL")
  156. )
  157. except Exception:
  158. pass
  159. # Migration: Add project_id column to print_queue
  160. try:
  161. await conn.execute(
  162. text("ALTER TABLE print_queue ADD COLUMN project_id INTEGER REFERENCES projects(id) ON DELETE SET NULL")
  163. )
  164. except Exception:
  165. pass
  166. # Migration: Create FTS5 virtual table for archive full-text search
  167. try:
  168. await conn.execute(
  169. text("""
  170. CREATE VIRTUAL TABLE IF NOT EXISTS archive_fts USING fts5(
  171. print_name,
  172. filename,
  173. tags,
  174. notes,
  175. designer,
  176. filament_type,
  177. content='print_archives',
  178. content_rowid='id'
  179. )
  180. """)
  181. )
  182. except Exception:
  183. pass
  184. # Migration: Create triggers to keep FTS index in sync
  185. try:
  186. await conn.execute(
  187. text("""
  188. CREATE TRIGGER IF NOT EXISTS archive_fts_insert AFTER INSERT ON print_archives BEGIN
  189. INSERT INTO archive_fts(rowid, print_name, filename, tags, notes, designer, filament_type)
  190. VALUES (new.id, new.print_name, new.filename, new.tags, new.notes, new.designer, new.filament_type);
  191. END
  192. """)
  193. )
  194. except Exception:
  195. pass
  196. try:
  197. await conn.execute(
  198. text("""
  199. CREATE TRIGGER IF NOT EXISTS archive_fts_delete AFTER DELETE ON print_archives BEGIN
  200. INSERT INTO archive_fts(archive_fts, rowid, print_name, filename, tags, notes, designer, filament_type)
  201. VALUES ('delete', old.id, old.print_name, old.filename, old.tags, old.notes, old.designer, old.filament_type);
  202. END
  203. """)
  204. )
  205. except Exception:
  206. pass
  207. try:
  208. await conn.execute(
  209. text("""
  210. CREATE TRIGGER IF NOT EXISTS archive_fts_update AFTER UPDATE ON print_archives BEGIN
  211. INSERT INTO archive_fts(archive_fts, rowid, print_name, filename, tags, notes, designer, filament_type)
  212. VALUES ('delete', old.id, old.print_name, old.filename, old.tags, old.notes, old.designer, old.filament_type);
  213. INSERT INTO archive_fts(rowid, print_name, filename, tags, notes, designer, filament_type)
  214. VALUES (new.id, new.print_name, new.filename, new.tags, new.notes, new.designer, new.filament_type);
  215. END
  216. """)
  217. )
  218. except Exception:
  219. pass
  220. # Migration: Add auto_off_pending columns to smart_plugs (for restart recovery)
  221. try:
  222. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN auto_off_pending BOOLEAN DEFAULT 0"))
  223. except Exception:
  224. pass
  225. try:
  226. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN auto_off_pending_since DATETIME"))
  227. except Exception:
  228. pass
  229. # Migration: Add AMS alarm notification columns to notification_providers
  230. try:
  231. await conn.execute(text("ALTER TABLE notification_providers ADD COLUMN on_ams_humidity_high BOOLEAN DEFAULT 0"))
  232. except Exception:
  233. pass
  234. try:
  235. await conn.execute(
  236. text("ALTER TABLE notification_providers ADD COLUMN on_ams_temperature_high BOOLEAN DEFAULT 0")
  237. )
  238. except Exception:
  239. pass
  240. # Migration: Add AMS-HT alarm notification columns to notification_providers
  241. try:
  242. await conn.execute(
  243. text("ALTER TABLE notification_providers ADD COLUMN on_ams_ht_humidity_high BOOLEAN DEFAULT 0")
  244. )
  245. except Exception:
  246. pass
  247. try:
  248. await conn.execute(
  249. text("ALTER TABLE notification_providers ADD COLUMN on_ams_ht_temperature_high BOOLEAN DEFAULT 0")
  250. )
  251. except Exception:
  252. pass
  253. # Migration: Add notes column to projects (Phase 2)
  254. try:
  255. await conn.execute(text("ALTER TABLE projects ADD COLUMN notes TEXT"))
  256. except Exception:
  257. pass
  258. # Migration: Add attachments column to projects (Phase 3)
  259. try:
  260. await conn.execute(text("ALTER TABLE projects ADD COLUMN attachments JSON"))
  261. except Exception:
  262. pass
  263. # Migration: Add tags column to projects (Phase 4)
  264. try:
  265. await conn.execute(text("ALTER TABLE projects ADD COLUMN tags TEXT"))
  266. except Exception:
  267. pass
  268. # Migration: Add due_date column to projects (Phase 5)
  269. try:
  270. await conn.execute(text("ALTER TABLE projects ADD COLUMN due_date DATETIME"))
  271. except Exception:
  272. pass
  273. # Migration: Add priority column to projects (Phase 5)
  274. try:
  275. await conn.execute(text("ALTER TABLE projects ADD COLUMN priority VARCHAR(20) DEFAULT 'normal'"))
  276. except Exception:
  277. pass
  278. # Migration: Add budget column to projects (Phase 6)
  279. try:
  280. await conn.execute(text("ALTER TABLE projects ADD COLUMN budget REAL"))
  281. except Exception:
  282. pass
  283. # Migration: Add is_template column to projects (Phase 8)
  284. try:
  285. await conn.execute(text("ALTER TABLE projects ADD COLUMN is_template BOOLEAN DEFAULT 0"))
  286. except Exception:
  287. pass
  288. # Migration: Add template_source_id column to projects (Phase 8)
  289. try:
  290. await conn.execute(text("ALTER TABLE projects ADD COLUMN template_source_id INTEGER"))
  291. except Exception:
  292. pass
  293. # Migration: Add parent_id column to projects (Phase 10)
  294. try:
  295. await conn.execute(
  296. text("ALTER TABLE projects ADD COLUMN parent_id INTEGER REFERENCES projects(id) ON DELETE SET NULL")
  297. )
  298. except Exception:
  299. pass
  300. # Migration: Rename quantity_printed to quantity_acquired in project_bom_items
  301. try:
  302. await conn.execute(text("ALTER TABLE project_bom_items RENAME COLUMN quantity_printed TO quantity_acquired"))
  303. except Exception:
  304. pass
  305. # Migration: Add unit_price column to project_bom_items
  306. try:
  307. await conn.execute(text("ALTER TABLE project_bom_items ADD COLUMN unit_price REAL"))
  308. except Exception:
  309. pass
  310. # Migration: Add sourcing_url column to project_bom_items
  311. try:
  312. await conn.execute(text("ALTER TABLE project_bom_items ADD COLUMN sourcing_url VARCHAR(512)"))
  313. except Exception:
  314. pass
  315. # Migration: Rename notes to remarks in project_bom_items
  316. try:
  317. await conn.execute(text("ALTER TABLE project_bom_items RENAME COLUMN notes TO remarks"))
  318. except Exception:
  319. pass
  320. # Migration: Add show_in_switchbar column to smart_plugs
  321. try:
  322. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN show_in_switchbar BOOLEAN DEFAULT 0"))
  323. except Exception:
  324. pass
  325. # Migration: Add runtime tracking columns to printers
  326. try:
  327. await conn.execute(text("ALTER TABLE printers ADD COLUMN runtime_seconds INTEGER DEFAULT 0"))
  328. except Exception:
  329. pass
  330. try:
  331. await conn.execute(text("ALTER TABLE printers ADD COLUMN last_runtime_update DATETIME"))
  332. except Exception:
  333. pass
  334. # Migration: Add quantity column to print_archives for tracking item count
  335. try:
  336. await conn.execute(text("ALTER TABLE print_archives ADD COLUMN quantity INTEGER DEFAULT 1"))
  337. except Exception:
  338. pass
  339. # Migration: Add manual_start column to print_queue for staged prints
  340. try:
  341. await conn.execute(text("ALTER TABLE print_queue ADD COLUMN manual_start BOOLEAN DEFAULT 0"))
  342. except Exception:
  343. pass
  344. # Migration: Add wiki_url column to maintenance_types for documentation links
  345. try:
  346. await conn.execute(text("ALTER TABLE maintenance_types ADD COLUMN wiki_url VARCHAR(500)"))
  347. except Exception:
  348. pass
  349. # Migration: Add ams_mapping column to print_queue for storing filament slot assignments
  350. try:
  351. await conn.execute(text("ALTER TABLE print_queue ADD COLUMN ams_mapping TEXT"))
  352. except Exception:
  353. pass
  354. # Migration: Add target_parts_count column to projects for tracking total parts needed
  355. try:
  356. await conn.execute(text("ALTER TABLE projects ADD COLUMN target_parts_count INTEGER"))
  357. except Exception:
  358. pass
  359. # Migration: Make printer_id nullable in print_queue for unassigned queue items
  360. # SQLite doesn't support ALTER COLUMN, so we need to recreate the table
  361. try:
  362. # Check if printer_id is already nullable by trying to insert NULL
  363. # This is a safe check that won't affect existing data
  364. result = await conn.execute(text("SELECT sql FROM sqlite_master WHERE type='table' AND name='print_queue'"))
  365. row = result.fetchone()
  366. if row and "printer_id INTEGER NOT NULL" in (row[0] or ""):
  367. # Need to migrate - printer_id is currently NOT NULL
  368. await conn.execute(
  369. text("""
  370. CREATE TABLE print_queue_new (
  371. id INTEGER PRIMARY KEY,
  372. printer_id INTEGER REFERENCES printers(id) ON DELETE CASCADE,
  373. archive_id INTEGER NOT NULL REFERENCES print_archives(id) ON DELETE CASCADE,
  374. project_id INTEGER REFERENCES projects(id) ON DELETE SET NULL,
  375. position INTEGER DEFAULT 0,
  376. scheduled_time DATETIME,
  377. manual_start BOOLEAN DEFAULT 0,
  378. require_previous_success BOOLEAN DEFAULT 0,
  379. auto_off_after BOOLEAN DEFAULT 0,
  380. ams_mapping TEXT,
  381. status VARCHAR(20) DEFAULT 'pending',
  382. started_at DATETIME,
  383. completed_at DATETIME,
  384. error_message TEXT,
  385. created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  386. )
  387. """)
  388. )
  389. await conn.execute(
  390. text("""
  391. INSERT INTO print_queue_new
  392. SELECT id, printer_id, archive_id, project_id, position, scheduled_time,
  393. manual_start, require_previous_success, auto_off_after, ams_mapping,
  394. status, started_at, completed_at, error_message, created_at
  395. FROM print_queue
  396. """)
  397. )
  398. await conn.execute(text("DROP TABLE print_queue"))
  399. await conn.execute(text("ALTER TABLE print_queue_new RENAME TO print_queue"))
  400. except Exception:
  401. pass
  402. # Migration: Add plug_type column to smart_plugs for HA integration
  403. try:
  404. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN plug_type VARCHAR(20) DEFAULT 'tasmota'"))
  405. except Exception:
  406. pass
  407. # Migration: Add ha_entity_id column to smart_plugs for HA integration
  408. try:
  409. await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN ha_entity_id VARCHAR(100)"))
  410. except Exception:
  411. pass
  412. # Migration: Add project_id column to library_folders for linking folders to projects
  413. try:
  414. await conn.execute(
  415. text("ALTER TABLE library_folders ADD COLUMN project_id INTEGER REFERENCES projects(id) ON DELETE SET NULL")
  416. )
  417. except Exception:
  418. pass
  419. # Migration: Add archive_id column to library_folders for linking folders to archives
  420. try:
  421. await conn.execute(
  422. text(
  423. "ALTER TABLE library_folders ADD COLUMN archive_id INTEGER REFERENCES print_archives(id) ON DELETE SET NULL"
  424. )
  425. )
  426. except Exception:
  427. pass
  428. # Migration: Make ip_address nullable for HA plugs (SQLite requires table recreation)
  429. try:
  430. # Check if ip_address is currently NOT NULL
  431. result = await conn.execute(text("SELECT sql FROM sqlite_master WHERE type='table' AND name='smart_plugs'"))
  432. row = result.fetchone()
  433. if row and "ip_address VARCHAR(45) NOT NULL" in (row[0] or ""):
  434. # Need to migrate - ip_address is currently NOT NULL
  435. await conn.execute(
  436. text("""
  437. CREATE TABLE smart_plugs_new (
  438. id INTEGER PRIMARY KEY,
  439. name VARCHAR(100) NOT NULL,
  440. ip_address VARCHAR(45),
  441. plug_type VARCHAR(20) DEFAULT 'tasmota',
  442. ha_entity_id VARCHAR(100),
  443. printer_id INTEGER UNIQUE REFERENCES printers(id) ON DELETE SET NULL,
  444. enabled BOOLEAN NOT NULL DEFAULT 1,
  445. auto_on BOOLEAN NOT NULL DEFAULT 1,
  446. auto_off BOOLEAN NOT NULL DEFAULT 1,
  447. off_delay_mode VARCHAR(20) NOT NULL DEFAULT 'time',
  448. off_delay_minutes INTEGER NOT NULL DEFAULT 5,
  449. off_temp_threshold INTEGER NOT NULL DEFAULT 70,
  450. username VARCHAR(50),
  451. password VARCHAR(100),
  452. power_alert_enabled BOOLEAN NOT NULL DEFAULT 0,
  453. power_alert_high FLOAT,
  454. power_alert_low FLOAT,
  455. power_alert_last_triggered DATETIME,
  456. schedule_enabled BOOLEAN NOT NULL DEFAULT 0,
  457. schedule_on_time VARCHAR(5),
  458. schedule_off_time VARCHAR(5),
  459. show_in_switchbar BOOLEAN DEFAULT 0,
  460. last_state VARCHAR(10),
  461. last_checked DATETIME,
  462. auto_off_executed BOOLEAN NOT NULL DEFAULT 0,
  463. auto_off_pending BOOLEAN DEFAULT 0,
  464. auto_off_pending_since DATETIME,
  465. created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
  466. updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
  467. )
  468. """)
  469. )
  470. await conn.execute(
  471. text("""
  472. INSERT INTO smart_plugs_new
  473. SELECT id, name, ip_address,
  474. COALESCE(plug_type, 'tasmota'), ha_entity_id, printer_id,
  475. enabled, auto_on, auto_off, off_delay_mode, off_delay_minutes, off_temp_threshold,
  476. username, password, power_alert_enabled, power_alert_high, power_alert_low,
  477. power_alert_last_triggered, schedule_enabled, schedule_on_time, schedule_off_time,
  478. COALESCE(show_in_switchbar, 0), last_state, last_checked, auto_off_executed,
  479. COALESCE(auto_off_pending, 0), auto_off_pending_since, created_at, updated_at
  480. FROM smart_plugs
  481. """)
  482. )
  483. await conn.execute(text("DROP TABLE smart_plugs"))
  484. await conn.execute(text("ALTER TABLE smart_plugs_new RENAME TO smart_plugs"))
  485. except Exception:
  486. pass
  487. # Migration: Add plate_id column to print_queue for multi-plate 3MF support
  488. try:
  489. await conn.execute(text("ALTER TABLE print_queue ADD COLUMN plate_id INTEGER"))
  490. except Exception:
  491. pass
  492. # Migration: Add print options columns to print_queue
  493. try:
  494. await conn.execute(text("ALTER TABLE print_queue ADD COLUMN bed_levelling BOOLEAN DEFAULT 1"))
  495. except Exception:
  496. pass
  497. try:
  498. await conn.execute(text("ALTER TABLE print_queue ADD COLUMN flow_cali BOOLEAN DEFAULT 0"))
  499. except Exception:
  500. pass
  501. try:
  502. await conn.execute(text("ALTER TABLE print_queue ADD COLUMN vibration_cali BOOLEAN DEFAULT 1"))
  503. except Exception:
  504. pass
  505. try:
  506. await conn.execute(text("ALTER TABLE print_queue ADD COLUMN layer_inspect BOOLEAN DEFAULT 0"))
  507. except Exception:
  508. pass
  509. try:
  510. await conn.execute(text("ALTER TABLE print_queue ADD COLUMN timelapse BOOLEAN DEFAULT 0"))
  511. except Exception:
  512. pass
  513. try:
  514. await conn.execute(text("ALTER TABLE print_queue ADD COLUMN use_ams BOOLEAN DEFAULT 1"))
  515. except Exception:
  516. pass
  517. async def seed_notification_templates():
  518. """Seed default notification templates if they don't exist."""
  519. from sqlalchemy import select
  520. from backend.app.models.notification_template import DEFAULT_TEMPLATES, NotificationTemplate
  521. async with async_session() as session:
  522. # Check if templates already exist
  523. result = await session.execute(select(NotificationTemplate).limit(1))
  524. if result.scalar_one_or_none() is not None:
  525. # Templates already seeded
  526. return
  527. # Insert default templates
  528. for template_data in DEFAULT_TEMPLATES:
  529. template = NotificationTemplate(
  530. event_type=template_data["event_type"],
  531. name=template_data["name"],
  532. title_template=template_data["title_template"],
  533. body_template=template_data["body_template"],
  534. is_default=True,
  535. )
  536. session.add(template)
  537. await session.commit()