@serifpersia/dango 3.6.7 → 3.6.9

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.
Files changed (156) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DKDJK8Mp.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-CTcvfjva.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_UiacIN.js} +2 -2
  5. package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-DMSN2vxl.js} +2 -2
  6. package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-CRwfOatX.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-CRJsi-vz.js} +1 -1
  8. package/client/dist/assets/{Home-DUkUTTTy.js → Home-pwmK9_Eh.js} +1 -1
  9. package/client/dist/assets/{Insights-QCh4xW12.js → Insights-m7kFzg2A.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-C1wpPvu6.js} +1 -1
  11. package/client/dist/assets/ListeningList-OzfEI1Ga.js +1 -0
  12. package/client/dist/assets/{Manga-HydR-L7o.js → Manga-ChRCExhK.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DSHJPo2A.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-CzOVY1wA.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-CJfzdUBb.js} +1 -1
  16. package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-CifQMfhs.js} +1 -1
  17. package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CM10yB9V.js} +1 -1
  18. package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-6Kovbsqw.js} +1 -1
  19. package/client/dist/assets/{Player-CsH4x_I_.js → Player-CK0hT-u4.js} +2 -2
  20. package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-Bb66uqT9.js} +1 -1
  21. package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-C_nTj8_q.js} +1 -1
  22. package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-DUK7URAE.js} +1 -1
  23. package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-Ck9LJThU.js} +1 -1
  24. package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BUphIv0k.js} +1 -1
  25. package/client/dist/assets/{Search-DFkhuk-x.js → Search-zJyP15h6.js} +1 -1
  26. package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-BYdoSEUh.js} +1 -1
  27. package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CqgbOhjX.js} +1 -1
  28. package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-BVjyv6CM.js} +1 -1
  29. package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-L13ylswR.js} +1 -1
  30. package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-Cvcm5U2D.js} +1 -1
  31. package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-DhxfKvYm.js} +1 -1
  32. package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-P8bFE6_X.js} +1 -1
  33. package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-D5gVPjtb.js} +1 -1
  34. package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-Cjq8l3rU.js} +1 -1
  35. package/client/dist/assets/{hls-BGCX7mJy.js → hls-BkzuANAg.js} +1 -1
  36. package/client/dist/assets/{index-CnfZwsds.js → index-CLVgzKLk.js} +19 -19
  37. package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-DZJZu-so.js} +1 -1
  38. package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CvrMGHuo.js} +1 -1
  39. package/client/dist/assets/useAsmrPopup-Bimy8Ouw.js +1 -0
  40. package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary--MiN11uR.js} +1 -1
  41. package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-CR5XhKKS.js} +1 -1
  42. package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-DqdT4fA5.js} +1 -1
  43. package/client/dist/assets/{useTv-DGflCsSI.js → useTv-BL0HMDyP.js} +1 -1
  44. package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-D4U4iqN8.js} +1 -1
  45. package/client/dist/index.html +1 -1
  46. package/package.json +3 -5
  47. package/server/dist/config.js +1 -174
  48. package/server/dist/server.js +1068 -289
  49. package/server/dist/shipped-defaults.js +1 -6
  50. package/server/package.json +4 -5
  51. package/client/dist/assets/Asmr.module-CdWlioTY.js +0 -1
  52. package/client/dist/assets/ListeningList-DguKjI_t.js +0 -1
  53. package/client/dist/assets/Music-BGH_0voi.css +0 -1
  54. package/client/dist/assets/Music-CQpyXTLH.js +0 -1
  55. package/client/dist/assets/Radio-CCHNFN7R.css +0 -1
  56. package/client/dist/assets/Radio-DF6g6odd.js +0 -1
  57. package/client/dist/assets/Radio.module-DGSZLpKM.js +0 -1
  58. package/client/dist/assets/useAsmrPopup-SymV1J-b.js +0 -1
  59. package/server/dist/app-auth.js +0 -157
  60. package/server/dist/app-hono.js +0 -598
  61. package/server/dist/db/drizzle.js +0 -16
  62. package/server/dist/db/schema-anime.js +0 -61
  63. package/server/dist/db/schema-asmr.js +0 -13
  64. package/server/dist/db/schema-manga.js +0 -31
  65. package/server/dist/db/schema-tv.js +0 -18
  66. package/server/dist/db.js +0 -217
  67. package/server/dist/discord-gateway.js +0 -385
  68. package/server/dist/discord-rpc.js +0 -413
  69. package/server/dist/github-sync.js +0 -508
  70. package/server/dist/google.js +0 -741
  71. package/server/dist/hono/asmr.js +0 -10
  72. package/server/dist/hono/auth.js +0 -49
  73. package/server/dist/hono/music.js +0 -209
  74. package/server/dist/hono/proxy.js +0 -827
  75. package/server/dist/hono/tv.js +0 -200
  76. package/server/dist/hono/watchlist.js +0 -341
  77. package/server/dist/lib/anilist.js +0 -1583
  78. package/server/dist/lib/anischedule.js +0 -191
  79. package/server/dist/lib/insights.js +0 -185
  80. package/server/dist/lib/ipc.js +0 -23
  81. package/server/dist/lib/kitsu.js +0 -517
  82. package/server/dist/lib/mal-import.js +0 -291
  83. package/server/dist/lib/mal.js +0 -957
  84. package/server/dist/lib/migration.js +0 -310
  85. package/server/dist/lib/offline-db.js +0 -395
  86. package/server/dist/lib/potoken.js +0 -135
  87. package/server/dist/lib/recommendations.service.js +0 -216
  88. package/server/dist/lib/temp-ids.js +0 -3
  89. package/server/dist/lib/tmdb.js +0 -72
  90. package/server/dist/lib/tracker/anilist-tracker.js +0 -308
  91. package/server/dist/lib/tracker/manga-sync.service.js +0 -773
  92. package/server/dist/lib/tracker/sync-state.js +0 -21
  93. package/server/dist/lib/tracker/sync.service.js +0 -476
  94. package/server/dist/lib/translate.js +0 -79
  95. package/server/dist/lib/ump.js +0 -106
  96. package/server/dist/lib/ytmusic.js +0 -162
  97. package/server/dist/logger.js +0 -16
  98. package/server/dist/providers/manga/manga.types.js +0 -1
  99. package/server/dist/providers/provider.interface.js +0 -1
  100. package/server/dist/providers/remote-loader.js +0 -614
  101. package/server/dist/providers/remote-types.js +0 -159
  102. package/server/dist/providers/title-matching.js +0 -226
  103. package/server/dist/providers/tv.types.js +0 -1
  104. package/server/dist/rclone.js +0 -120
  105. package/server/dist/repositories/asmr.repository.js +0 -77
  106. package/server/dist/repositories/insights.repository.js +0 -106
  107. package/server/dist/repositories/library.repository.js +0 -94
  108. package/server/dist/repositories/mal-cache.repository.js +0 -52
  109. package/server/dist/repositories/manga.repository.js +0 -95
  110. package/server/dist/repositories/notifications.repository.js +0 -60
  111. package/server/dist/repositories/queue.repository.js +0 -72
  112. package/server/dist/repositories/settings.repository.js +0 -30
  113. package/server/dist/repositories/shows-meta.repository.js +0 -58
  114. package/server/dist/repositories/temp-show-ids.repository.js +0 -63
  115. package/server/dist/repositories/tv.repository.js +0 -93
  116. package/server/dist/repositories/watched-episodes.repository.js +0 -82
  117. package/server/dist/repositories/watchlist.repository.js +0 -60
  118. package/server/dist/scripts/backfill-episode-duration.js +0 -42
  119. package/server/dist/sync-payload.js +0 -91
  120. package/server/dist/sync.js +0 -657
  121. package/server/dist/trpc/context.js +0 -7
  122. package/server/dist/trpc/errors.js +0 -15
  123. package/server/dist/trpc/index.js +0 -18
  124. package/server/dist/trpc/router.js +0 -49
  125. package/server/dist/trpc/routers/asmr-library.js +0 -112
  126. package/server/dist/trpc/routers/asmr-progress.js +0 -121
  127. package/server/dist/trpc/routers/asmr.js +0 -87
  128. package/server/dist/trpc/routers/auth.js +0 -194
  129. package/server/dist/trpc/routers/continue-watching.js +0 -81
  130. package/server/dist/trpc/routers/data.js +0 -1026
  131. package/server/dist/trpc/routers/discord.js +0 -285
  132. package/server/dist/trpc/routers/discovery.js +0 -11
  133. package/server/dist/trpc/routers/insights.js +0 -14
  134. package/server/dist/trpc/routers/manga-library.js +0 -263
  135. package/server/dist/trpc/routers/manga-progress.js +0 -169
  136. package/server/dist/trpc/routers/manga.js +0 -201
  137. package/server/dist/trpc/routers/music.js +0 -655
  138. package/server/dist/trpc/routers/notifications.js +0 -80
  139. package/server/dist/trpc/routers/progress.js +0 -109
  140. package/server/dist/trpc/routers/queue.js +0 -93
  141. package/server/dist/trpc/routers/radio.js +0 -145
  142. package/server/dist/trpc/routers/settings.js +0 -96
  143. package/server/dist/trpc/routers/tracker.js +0 -213
  144. package/server/dist/trpc/routers/tv-library.js +0 -135
  145. package/server/dist/trpc/routers/tv-progress.js +0 -159
  146. package/server/dist/trpc/routers/tv.js +0 -551
  147. package/server/dist/trpc/routers/watchlist.js +0 -550
  148. package/server/dist/trpc/validation.js +0 -258
  149. package/server/dist/utils/cache.utils.js +0 -25
  150. package/server/dist/utils/cookie.utils.js +0 -13
  151. package/server/dist/utils/db-utils.js +0 -17
  152. package/server/dist/utils/env.utils.js +0 -77
  153. package/server/dist/utils/http.utils.js +0 -82
  154. package/server/dist/utils/request-context.js +0 -2
  155. package/server/dist/utils/security.utils.js +0 -76
  156. /package/client/dist/assets/{Asmr-D1KuQwQy.css → useAsmrPopup-D1KuQwQy.css} +0 -0
@@ -1,82 +0,0 @@
1
- import { eq, sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { watchedEpisodes } from '../db/schema-anime.js';
4
- export const WatchedEpisodesRepository = {
5
- getByShowAndEpisode: async (db, showId, episodeNumber) => {
6
- const rows = await getDrizzle(db).all(sql `
7
- SELECT currentTime, duration FROM watched_episodes WHERE showId = ${showId} AND episodeNumber = ${episodeNumber}`);
8
- return rows[0];
9
- },
10
- getWatchedEpisodeNumbers: async (db, showId) => {
11
- const rows = await getDrizzle(db).all(sql `SELECT episodeNumber FROM watched_episodes WHERE showId = ${showId}`);
12
- return rows.map((r) => r.episodeNumber);
13
- },
14
- getByShow: (db, showId) => getDrizzle(db).all(sql `
15
- SELECT showId, episodeNumber, currentTime, duration, watchedAt FROM watched_episodes WHERE showId = ${showId} ORDER BY CAST(episodeNumber AS REAL) ASC`),
16
- getLatestResumeProgress: async (db, showId) => {
17
- const rows = await getDrizzle(db).all(sql `
18
- SELECT showId, episodeNumber, currentTime, duration, watchedAt
19
- FROM watched_episodes
20
- WHERE showId = ${showId} AND currentTime > 5 AND (duration <= 0 OR currentTime < duration * 0.8)
21
- ORDER BY watchedAt DESC
22
- LIMIT 1`);
23
- return rows[0];
24
- },
25
- upsert: (db, data) => getDrizzle(db)
26
- .insert(watchedEpisodes)
27
- .values({
28
- showId: data.showId,
29
- episodeNumber: data.episodeNumber,
30
- watchedAt: sql `CURRENT_TIMESTAMP`,
31
- currentTime: data.currentTime,
32
- duration: data.duration,
33
- })
34
- .onConflictDoUpdate({
35
- target: [watchedEpisodes.showId, watchedEpisodes.episodeNumber],
36
- set: {
37
- watchedAt: sql `CURRENT_TIMESTAMP`,
38
- currentTime: data.currentTime,
39
- duration: data.duration,
40
- },
41
- }),
42
- insertIfMissing: (db, data) => getDrizzle(db)
43
- .insert(watchedEpisodes)
44
- .values({
45
- showId: data.showId,
46
- episodeNumber: data.episodeNumber,
47
- watchedAt: data.watchedAt ? sql `${data.watchedAt}` : sql `CURRENT_TIMESTAMP`,
48
- currentTime: 0,
49
- duration: 0,
50
- })
51
- .onConflictDoNothing(),
52
- deleteByShow: (db, showId) => getDrizzle(db).delete(watchedEpisodes).where(eq(watchedEpisodes.showId, showId)),
53
- getContinueWatching: (db, limit) => {
54
- const limitClause = typeof limit === 'number' ? `LIMIT ${limit}` : '';
55
- return getDrizzle(db).all(sql `
56
- SELECT
57
- we.showId as _id,
58
- we.showId as id,
59
- COALESCE(NULLIF(w.name, ''), sm.name) as name,
60
- COALESCE(NULLIF(w.thumbnail, ''), NULLIF(sm.thumbnail, ''), '') as thumbnail,
61
- COALESCE(NULLIF(w.nativeName, ''), sm.nativeName) as nativeName,
62
- COALESCE(NULLIF(w.englishName, ''), sm.englishName) as englishName,
63
- COALESCE(NULLIF(w.type, ''), sm.type) as type,
64
- sm.episodeCount,
65
- sm.type as smType,
66
- sm.isAdult as isAdult,
67
- w.status as watchlistStatus,
68
- (SELECT COUNT(DISTINCT episodeNumber) FROM watched_episodes WHERE showId = we.showId) as watchedCount,
69
- we.episodeNumber, we.currentTime, we.duration, we.watchedAt
70
- FROM (
71
- SELECT *, ROW_NUMBER() OVER(PARTITION BY showId ORDER BY (currentTime > 0 OR duration > 0) DESC, watchedAt DESC) as rn
72
- FROM watched_episodes
73
- ) we
74
- LEFT JOIN watchlist w ON we.showId = w.id
75
- LEFT JOIN shows_meta sm ON we.showId = sm.id
76
- WHERE we.rn = 1
77
- AND (w.status IS NULL OR w.status = 'Watching')
78
- AND (w.id IS NOT NULL OR sm.id IS NOT NULL)
79
- ORDER BY we.watchedAt DESC
80
- ${sql.raw(limitClause)}`);
81
- },
82
- };
@@ -1,60 +0,0 @@
1
- import { eq, inArray, sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { watchlist } from '../db/schema-anime.js';
4
- export const WatchlistRepository = {
5
- getById: async (db, id) => {
6
- const rows = await getDrizzle(db).all(sql `SELECT * FROM watchlist WHERE id = ${id}`);
7
- return rows[0];
8
- },
9
- exists: async (db, id) => {
10
- const rows = await getDrizzle(db).all(sql `SELECT EXISTS(SELECT 1 FROM watchlist WHERE id = ${id}) as inWatchlist`);
11
- return !!(rows[0] && rows[0].inWatchlist);
12
- },
13
- getAll: (db, status, limit, offset) => {
14
- const q = sql `SELECT * FROM watchlist`;
15
- if (status && status !== 'All') {
16
- q.append(sql ` WHERE status = ${status}`);
17
- }
18
- q.append(sql ` ORDER BY rowid DESC`);
19
- if (limit !== undefined && offset !== undefined) {
20
- q.append(sql ` LIMIT ${limit} OFFSET ${offset}`);
21
- }
22
- return getDrizzle(db).all(q);
23
- },
24
- getCount: async (db, status) => {
25
- const q = sql `SELECT COUNT(*) as total FROM watchlist`;
26
- if (status && status !== 'All') {
27
- q.append(sql ` WHERE status = ${status}`);
28
- }
29
- const rows = await getDrizzle(db).all(q);
30
- return rows[0]?.total || 0;
31
- },
32
- upsert: (db, data) => getDrizzle(db).run(sql `
33
- INSERT INTO watchlist (id, name, thumbnail, status, nativeName, englishName, type) VALUES (${data.id}, ${data.name}, ${data.thumbnail}, ${data.status}, ${data.nativeName}, ${data.englishName}, ${data.type})
34
- ON CONFLICT(id) DO UPDATE SET
35
- name = COALESCE(NULLIF(EXCLUDED.name, ''), watchlist.name),
36
- thumbnail = COALESCE(NULLIF(EXCLUDED.thumbnail, ''), watchlist.thumbnail),
37
- status = COALESCE(NULLIF(EXCLUDED.status, ''), watchlist.status),
38
- nativeName = COALESCE(NULLIF(EXCLUDED.nativeName, ''), watchlist.nativeName),
39
- englishName = COALESCE(NULLIF(EXCLUDED.englishName, ''), watchlist.englishName),
40
- type = COALESCE(NULLIF(EXCLUDED.type, ''), watchlist.type)`),
41
- updateStatus: (db, id, status) => getDrizzle(db).update(watchlist).set({ status }).where(eq(watchlist.id, id)),
42
- updateThumbnail: (db, id, thumbnail) => {
43
- if (!thumbnail || thumbnail.trim() === '')
44
- return Promise.resolve();
45
- return getDrizzle(db).update(watchlist).set({ thumbnail }).where(eq(watchlist.id, id));
46
- },
47
- delete: (db, id) => getDrizzle(db).delete(watchlist).where(eq(watchlist.id, id)),
48
- deleteMany: (db, ids) => {
49
- if (ids.length === 0)
50
- return Promise.resolve();
51
- return getDrizzle(db).delete(watchlist).where(inArray(watchlist.id, ids));
52
- },
53
- updateStatusMany: (db, ids, status) => {
54
- if (ids.length === 0)
55
- return Promise.resolve();
56
- return getDrizzle(db).update(watchlist).set({ status }).where(inArray(watchlist.id, ids));
57
- },
58
- getWatchingShows: (db) => getDrizzle(db).all(sql `SELECT id, name, thumbnail, nativeName, englishName FROM watchlist WHERE status = 'Watching'`),
59
- getMissingThumbnails: (db) => getDrizzle(db).all(sql `SELECT id, name FROM watchlist WHERE thumbnail IS NULL OR TRIM(thumbnail) = ''`),
60
- };
@@ -1,42 +0,0 @@
1
- import path from 'node:path';
2
- import { CONFIG } from '../config.js';
3
- import { DatabaseWrapper } from '../db.js';
4
- import { anilistRequest } from '../lib/anilist.js';
5
- import { dbAll, dbRun } from '../utils/db-utils.js';
6
- const BATCH_SIZE = 50;
7
- async function main() {
8
- const dbName = CONFIG.IS_DEV ? CONFIG.DB_NAME_DEV : CONFIG.DB_NAME_PROD;
9
- const db = await DatabaseWrapper.create(path.join(CONFIG.ROOT, dbName));
10
- db.configure('busyTimeout', 10000);
11
- const cols = dbAll(db, 'PRAGMA table_info(shows_meta)');
12
- if (!cols.some((c) => c.name === 'episodeDuration')) {
13
- db.run('ALTER TABLE shows_meta ADD COLUMN episodeDuration INTEGER');
14
- }
15
- const missing = dbAll(db, 'SELECT id, anilistId FROM shows_meta WHERE anilistId IS NOT NULL AND episodeDuration IS NULL');
16
- if (missing.length === 0) {
17
- console.log('Nothing to backfill.');
18
- db.close();
19
- return;
20
- }
21
- let updated = 0;
22
- for (let i = 0; i < missing.length; i += BATCH_SIZE) {
23
- const batch = missing.slice(i, i + BATCH_SIZE);
24
- const res = await anilistRequest(`query ($ids: [Int]) { Page(perPage: ${BATCH_SIZE}) { media(id_in: $ids, type: ANIME) { id duration } } }`, { ids: batch.map((b) => b.anilistId) });
25
- for (const m of res?.data?.Page?.media ?? []) {
26
- if (!m.duration || m.duration <= 0)
27
- continue;
28
- const row = batch.find((b) => b.anilistId === m.id);
29
- if (!row)
30
- continue;
31
- dbRun(db, 'UPDATE shows_meta SET episodeDuration = ? WHERE id = ?', [m.duration, row.id]);
32
- updated++;
33
- }
34
- console.log(`Progress: ${Math.min(i + BATCH_SIZE, missing.length)}/${missing.length} (updated ${updated})`);
35
- }
36
- console.log(`Done. Updated ${updated} of ${missing.length} shows.`);
37
- db.close();
38
- }
39
- main().catch((err) => {
40
- console.error(err?.message || err);
41
- process.exit(1);
42
- });
@@ -1,91 +0,0 @@
1
- import path from 'path';
2
- import logger from './logger.js';
3
- import { CONFIG } from './config.js';
4
- import { dbAll, dbGet } from './utils/db-utils.js';
5
- import { isTempSyncRow } from './lib/temp-ids.js';
6
- const log = logger.child({ module: 'SyncPayload' });
7
- export const ANIME_SYNC_TABLES = [
8
- 'watchlist',
9
- 'watched_episodes',
10
- 'queue',
11
- 'settings',
12
- 'shows_meta',
13
- 'sync_metadata',
14
- 'dismissed_notifications',
15
- 'discovered_notifications',
16
- ];
17
- export const MANGA_SYNC_TABLES = ['manga_library', 'manga_progress', 'sync_metadata'];
18
- export const TV_SYNC_TABLES = ['tv_library', 'tv_progress', 'sync_metadata'];
19
- export const ASMR_SYNC_TABLES = ['asmr_library', 'asmr_progress', 'sync_metadata'];
20
- export function readPayloadVersion(payload) {
21
- const meta = payload.tables['sync_metadata'];
22
- const row = meta?.find((r) => r.key === 'db_version');
23
- const v = row?.value;
24
- return typeof v === 'number' ? v : Number(v || payload.version || 0);
25
- }
26
- export function normalizePayload(input, tables, label) {
27
- if (!input || typeof input !== 'object' || !('tables' in input)) {
28
- throw new Error(`Invalid ${label} sync payload.`);
29
- }
30
- const payload = input;
31
- for (const table of tables) {
32
- if (!Array.isArray(payload.tables?.[table])) {
33
- throw new Error(`Invalid ${label} sync payload: missing ${String(table)}.`);
34
- }
35
- }
36
- return payload;
37
- }
38
- export function exportTables(db, tables) {
39
- const out = {};
40
- for (const table of tables) {
41
- const rows = dbAll(db, `SELECT * FROM "${String(table).replace(/"/g, '""')}"`);
42
- out[table] = rows.filter((row) => !isTempSyncRow(row));
43
- }
44
- return {
45
- version: readPayloadVersion({ version: 0, exportedAt: '', tables: out }),
46
- exportedAt: new Date().toISOString(),
47
- tables: out,
48
- };
49
- }
50
- export function importTables(db, tables, payload, opts) {
51
- const asRec = payload.tables;
52
- const remoteLibraryRows = opts.libraryTables.reduce((n, t) => n + (asRec[t]?.length ?? 0), 0);
53
- const localLibraryRows = opts.libraryTables.length === 0
54
- ? 1
55
- : (dbGet(db, `SELECT ${opts.libraryTables
56
- .map((t) => `(SELECT COUNT(*) FROM "${String(t).replace(/"/g, '""')}")`)
57
- .join(' + ')} AS n`)?.n ?? 0);
58
- if (remoteLibraryRows === 0 && localLibraryRows > 0) {
59
- throw new Error('Sync down refused: remote library is empty while local library has data');
60
- }
61
- try {
62
- db.backup(path.join(CONFIG.ROOT, opts.backupName));
63
- }
64
- catch (err) {
65
- log.warn({ err }, 'Pre-sync backup failed, continuing without backup');
66
- }
67
- const localColumns = new Map();
68
- for (const table of tables) {
69
- const cols = db.all(`PRAGMA table_info("${String(table)}")`);
70
- localColumns.set(String(table), new Set(cols.map((c) => c.name)));
71
- }
72
- db.serialize(() => {
73
- for (const table of tables) {
74
- db.run(`DELETE FROM "${String(table).replace(/"/g, '""')}"`);
75
- }
76
- for (const table of tables) {
77
- const known = localColumns.get(String(table));
78
- for (const row of asRec[String(table)] || []) {
79
- if (isTempSyncRow(row))
80
- continue;
81
- const columns = Object.keys(row).filter((c) => known?.has(c));
82
- if (columns.length === 0)
83
- continue;
84
- const columnSql = columns.map((c) => `"${c.replace(/"/g, '""')}"`).join(', ');
85
- const placeholders = columns.map(() => '?').join(', ');
86
- const values = columns.map((c) => row[c]);
87
- db.run(`INSERT INTO "${String(table).replace(/"/g, '""')}" (${columnSql}) VALUES (${placeholders})`, values);
88
- }
89
- }
90
- });
91
- }