chore: update Angular TypeScript build info cache
This commit is contained in:
+160
@@ -24,6 +24,36 @@ if (fs.existsSync(schemaFile)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Lightweight schema upgrades for existing databases (SQLite is permissive)
|
||||
(function ensurePlaylistSchemaUpgrades() {
|
||||
try {
|
||||
const colsPlaylists = db.prepare(`PRAGMA table_info(playlists)`).all();
|
||||
const have = new Set(colsPlaylists.map(c => c.name));
|
||||
if (!have.has('description')) db.exec(`ALTER TABLE playlists ADD COLUMN description TEXT`);
|
||||
if (!have.has('thumbnail')) db.exec(`ALTER TABLE playlists ADD COLUMN thumbnail TEXT`);
|
||||
if (!have.has('is_private')) db.exec(`ALTER TABLE playlists ADD COLUMN is_private INTEGER NOT NULL DEFAULT 1`);
|
||||
} catch {}
|
||||
try {
|
||||
const colsItems = db.prepare(`PRAGMA table_info(playlist_items)`).all();
|
||||
const have2 = new Set(colsItems.map(c => c.name));
|
||||
if (!have2.has('thumbnail')) db.exec(`ALTER TABLE playlist_items ADD COLUMN thumbnail TEXT`);
|
||||
} catch {}
|
||||
try {
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS playlist_metrics (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
playlist_id TEXT NOT NULL REFERENCES playlists(id) ON DELETE CASCADE,
|
||||
action TEXT NOT NULL,
|
||||
meta_json TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_playlist_metrics_user_time ON playlist_metrics(user_id, created_at DESC);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_playlist_metrics_playlist_time ON playlist_metrics(playlist_id, created_at DESC);`);
|
||||
} catch {}
|
||||
})();
|
||||
|
||||
// (duplicate schema exec removed)
|
||||
|
||||
// Helpers
|
||||
export function nowIso() {
|
||||
return new Date().toISOString();
|
||||
@@ -390,3 +420,133 @@ export function listLikedVideos({ userId, limit = 100, q }) {
|
||||
throw error; // Renvoyer l'erreur pour qu'elle soit gérée par le routeur
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Playlists --------------------
|
||||
export function recordPlaylistMetric({ userId, playlistId, action, meta }) {
|
||||
const id = cryptoRandomId();
|
||||
const created_at = nowIso();
|
||||
const meta_json = meta ? JSON.stringify(meta) : null;
|
||||
db.prepare(`INSERT INTO playlist_metrics (id, user_id, playlist_id, action, meta_json, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`)
|
||||
.run(id, userId, playlistId, action, meta_json, created_at);
|
||||
return { id, created_at };
|
||||
}
|
||||
|
||||
export function createPlaylist({ userId, title, description, thumbnail, isPrivate = true }) {
|
||||
const id = cryptoRandomUUID();
|
||||
const now = nowIso();
|
||||
const name = String(title || '').trim();
|
||||
if (!name) throw new Error('title_required');
|
||||
db.prepare(`INSERT INTO playlists (id, user_id, name, description, thumbnail, is_private, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
.run(id, userId, name, description || null, thumbnail || null, isPrivate ? 1 : 0, now, now);
|
||||
recordPlaylistMetric({ userId, playlistId: id, action: 'create', meta: { title: name } });
|
||||
return db.prepare(`SELECT id, user_id AS userId, name AS title, description, thumbnail, is_private AS isPrivate, created_at AS createdAt, updated_at AS updatedAt,
|
||||
(SELECT COUNT(1) FROM playlist_items WHERE playlist_id = playlists.id) AS itemsCount
|
||||
FROM playlists WHERE id = ?`).get(id);
|
||||
}
|
||||
|
||||
export function listPlaylists({ userId, limit = 50, offset = 0, q }) {
|
||||
const like = `%${(q || '').trim()}%`;
|
||||
const hasQ = typeof q === 'string' && q.trim().length > 0;
|
||||
const base = `SELECT id, user_id AS userId, name AS title, description, thumbnail, is_private AS isPrivate, created_at AS createdAt, updated_at AS updatedAt,
|
||||
(SELECT COUNT(1) FROM playlist_items WHERE playlist_id = playlists.id) AS itemsCount
|
||||
FROM playlists WHERE user_id = ?`;
|
||||
const sql = hasQ ? `${base} AND (name LIKE ? OR COALESCE(description,'') LIKE ?)
|
||||
ORDER BY updated_at DESC LIMIT ? OFFSET ?` :
|
||||
`${base} ORDER BY updated_at DESC LIMIT ? OFFSET ?`;
|
||||
return hasQ
|
||||
? db.prepare(sql).all(userId, like, like, limit, offset)
|
||||
: db.prepare(sql).all(userId, limit, offset);
|
||||
}
|
||||
|
||||
export function getPlaylistRaw(id) {
|
||||
return db.prepare(`SELECT id, user_id AS userId, name AS title, description, thumbnail, is_private AS isPrivate, created_at AS createdAt, updated_at AS updatedAt
|
||||
FROM playlists WHERE id = ?`).get(id);
|
||||
}
|
||||
|
||||
export function updatePlaylist({ userId, id, patch }) {
|
||||
const cur = db.prepare(`SELECT * FROM playlists WHERE id = ?`).get(id);
|
||||
if (!cur) return null;
|
||||
if (cur.user_id !== userId) return 'forbidden';
|
||||
const next = {
|
||||
name: patch.title != null ? String(patch.title).trim() : cur.name,
|
||||
description: patch.description != null ? String(patch.description).trim() : cur.description,
|
||||
thumbnail: patch.thumbnail != null ? String(patch.thumbnail).trim() : cur.thumbnail,
|
||||
is_private: typeof patch.isPrivate === 'boolean' ? (patch.isPrivate ? 1 : 0) : cur.is_private,
|
||||
updated_at: nowIso(),
|
||||
};
|
||||
db.prepare(`UPDATE playlists SET name=@name, description=@description, thumbnail=@thumbnail, is_private=@is_private, updated_at=@updated_at WHERE id = ?`)
|
||||
.run(id, next);
|
||||
recordPlaylistMetric({ userId, playlistId: id, action: 'update', meta: { title: next.name } });
|
||||
return db.prepare(`SELECT id, user_id AS userId, name AS title, description, thumbnail, is_private AS isPrivate, created_at AS createdAt, updated_at AS updatedAt
|
||||
FROM playlists WHERE id = ?`).get(id);
|
||||
}
|
||||
|
||||
export function deletePlaylist({ userId, id }) {
|
||||
const cur = db.prepare(`SELECT * FROM playlists WHERE id = ?`).get(id);
|
||||
if (!cur) return { removed: false };
|
||||
if (cur.user_id !== userId) return 'forbidden';
|
||||
const info = db.prepare(`DELETE FROM playlists WHERE id = ?`).run(id);
|
||||
recordPlaylistMetric({ userId, playlistId: id, action: 'delete' });
|
||||
return { removed: (info.changes || 0) > 0 };
|
||||
}
|
||||
|
||||
export function listPlaylistItems({ playlistId, limit = 200, offset = 0 }) {
|
||||
return db.prepare(`SELECT id, playlist_id AS playlistId, provider, video_id AS videoId, title, thumbnail, added_at AS addedAt, position
|
||||
FROM playlist_items WHERE playlist_id = ?
|
||||
ORDER BY position ASC LIMIT ? OFFSET ?`).all(playlistId, limit, offset);
|
||||
}
|
||||
|
||||
export function addPlaylistVideo({ userId, playlistId, provider, videoId, title, thumbnail }) {
|
||||
const pl = db.prepare(`SELECT * FROM playlists WHERE id = ?`).get(playlistId);
|
||||
if (!pl) return 'not_found';
|
||||
if (pl.user_id !== userId) return 'forbidden';
|
||||
const now = nowIso();
|
||||
const posRow = db.prepare(`SELECT COALESCE(MAX(position), 0) + 1 AS nextPos FROM playlist_items WHERE playlist_id = ?`).get(playlistId);
|
||||
const position = Number(posRow?.nextPos || 1);
|
||||
const id = cryptoRandomId();
|
||||
db.prepare(`INSERT OR IGNORE INTO playlist_items (id, playlist_id, provider, video_id, title, thumbnail, added_at, position)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, playlistId, provider, videoId, title || null, thumbnail || null, now, position);
|
||||
recordPlaylistMetric({ userId, playlistId, action: 'add_video', meta: { provider, videoId } });
|
||||
// Return the row (if it existed we need to fetch by key)
|
||||
const row = db.prepare(`SELECT id, playlist_id AS playlistId, provider, video_id AS videoId, title, thumbnail, added_at AS addedAt, position
|
||||
FROM playlist_items WHERE playlist_id = ? AND provider = ? AND video_id = ?`).get(playlistId, provider, videoId);
|
||||
return row;
|
||||
}
|
||||
|
||||
export function removePlaylistVideo({ userId, playlistId, provider, videoId }) {
|
||||
const pl = db.prepare(`SELECT * FROM playlists WHERE id = ?`).get(playlistId);
|
||||
if (!pl) return 'not_found';
|
||||
if (pl.user_id !== userId) return 'forbidden';
|
||||
const info = db.prepare(`DELETE FROM playlist_items WHERE playlist_id = ? AND provider = ? AND video_id = ?`).run(playlistId, provider, videoId);
|
||||
recordPlaylistMetric({ userId, playlistId, action: 'remove_video', meta: { provider, videoId } });
|
||||
return { removed: (info.changes || 0) > 0 };
|
||||
}
|
||||
|
||||
export function reorderPlaylistVideos({ userId, playlistId, order }) {
|
||||
const pl = db.prepare(`SELECT * FROM playlists WHERE id = ?`).get(playlistId);
|
||||
if (!pl) return 'not_found';
|
||||
if (pl.user_id !== userId) return 'forbidden';
|
||||
if (!Array.isArray(order) || order.length === 0) return { changed: 0 };
|
||||
const tx = db.transaction((rows) => {
|
||||
let i = 1, changed = 0;
|
||||
for (const it of rows) {
|
||||
// Support either item ids or provider+videoId pairs
|
||||
if (it && typeof it === 'string') {
|
||||
const info = db.prepare(`UPDATE playlist_items SET position = ? WHERE id = ? AND playlist_id = ?`).run(i, it, playlistId);
|
||||
changed += info.changes || 0;
|
||||
} else if (it && (it.id || (it.provider && it.videoId))) {
|
||||
const info = it.id
|
||||
? db.prepare(`UPDATE playlist_items SET position = ? WHERE id = ? AND playlist_id = ?`).run(i, it.id, playlistId)
|
||||
: db.prepare(`UPDATE playlist_items SET position = ? WHERE provider = ? AND video_id = ? AND playlist_id = ?`).run(i, it.provider, it.videoId, playlistId);
|
||||
changed += info.changes || 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return changed;
|
||||
});
|
||||
const changed = tx(order);
|
||||
recordPlaylistMetric({ userId, playlistId, action: 'reorder', meta: { count: order.length } });
|
||||
return { changed };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user