chore: update Angular cache files and TypeScript build info

This commit is contained in:
2025-09-17 21:27:20 -04:00
parent 3dbfb04b15
commit f3a78b7d7e
12 changed files with 60 additions and 19 deletions
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
+2
View File
@@ -73,6 +73,7 @@ CREATE TABLE IF NOT EXISTS watch_history (
provider TEXT NOT NULL, provider TEXT NOT NULL,
video_id TEXT NOT NULL, video_id TEXT NOT NULL,
title TEXT, title TEXT,
thumbnail TEXT,
watched_at TEXT NOT NULL, watched_at TEXT NOT NULL,
progress_seconds INTEGER DEFAULT 0, progress_seconds INTEGER DEFAULT 0,
duration_seconds INTEGER DEFAULT 0, duration_seconds INTEGER DEFAULT 0,
@@ -145,5 +146,6 @@ CREATE TABLE IF NOT EXISTS video_tags (
provider TEXT NOT NULL, provider TEXT NOT NULL,
video_id TEXT NOT NULL, video_id TEXT NOT NULL,
tag_id TEXT NOT NULL REFERENCES tags(id) ON DELETE CASCADE, tag_id TEXT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
created_at TEXT NOT NULL,
PRIMARY KEY (user_id, provider, video_id, tag_id) PRIMARY KEY (user_id, provider, video_id, tag_id)
); );
+11 -4
View File
@@ -279,11 +279,17 @@ function ensureTag(userId, name) {
return id; return id;
} }
export function likeVideo({ userId, provider, videoId }) { export function likeVideo({ userId, provider, videoId, title, thumbnail }) {
const tagId = ensureTag(userId, 'like'); const tagId = ensureTag(userId, 'like');
// Upsert-like behavior; ignore if exists // Upsert-like behavior; ignore if exists
db.prepare(`INSERT OR IGNORE INTO video_tags (user_id, provider, video_id, tag_id) VALUES (?, ?, ?, ?)`) db.prepare(`INSERT OR IGNORE INTO video_tags (user_id, provider, video_id, tag_id, created_at) VALUES (?, ?, ?, ?, ?)`)
.run(userId, provider, videoId, tagId); .run(userId, provider, videoId, tagId, nowIso());
// Also update the watch_history table with the title and thumbnail
if (title || thumbnail) {
upsertWatchHistory({ userId, provider, videoId, title, thumbnail });
}
return { provider, video_id: videoId }; return { provider, video_id: videoId };
} }
@@ -350,6 +356,7 @@ export function listLikedVideos({ userId, limit = 100, q }) {
SELECT SELECT
vt.provider, vt.provider,
vt.video_id, vt.video_id,
vt.created_at,
COALESCE(wh.title, '') AS title, COALESCE(wh.title, '') AS title,
COALESCE(wh.thumbnail, '') AS thumbnail, COALESCE(wh.thumbnail, '') AS thumbnail,
wh.last_watched_at AS last_watched_at wh.last_watched_at AS last_watched_at
@@ -361,7 +368,7 @@ export function listLikedVideos({ userId, limit = 100, q }) {
WHERE vt.user_id = ? AND vt.tag_id = ? WHERE vt.user_id = ? AND vt.tag_id = ?
`; `;
const orderLimit = ` const orderLimit = `
ORDER BY COALESCE(wh.last_watched_at, wh.watched_at) DESC ORDER BY vt.created_at DESC
LIMIT ? LIMIT ?
`; `;
const query = hasQ const query = hasQ
+29 -3
View File
@@ -1058,10 +1058,36 @@ r.get('/user/likes', authMiddleware, (req, res) => {
return res.json(rows); return res.json(rows);
}); });
r.post('/user/likes', authMiddleware, (req, res) => { r.post('/user/likes', authMiddleware, async (req, res) => {
const { provider, videoId } = req.body || {}; let { provider, videoId, title, thumbnail } = req.body || {};
try {
console.log('[POST /user/likes] payload:', {
provider,
videoId,
titlePreview: typeof title === 'string' ? title.slice(0, 80) : title,
hasThumbnail: Boolean(thumbnail)
});
} catch {}
if (!provider || !videoId) return res.status(400).json({ error: 'provider and videoId are required' }); if (!provider || !videoId) return res.status(400).json({ error: 'provider and videoId are required' });
const row = likeVideo({ userId: req.user.id, provider, videoId });
// Server-side enrichment: if title or thumbnail is missing, fetch minimal details via yt-dlp
try {
const needTitle = !(typeof title === 'string' && title.trim().length > 0);
const needThumb = !(typeof thumbnail === 'string' && thumbnail.trim().length > 0);
if (needTitle || needThumb) {
const url = providerUrlFrom(provider, videoId, { instance: req.query.instance, slug: req.query.slug, sourceUrl: req.query.sourceUrl });
try {
const raw = await youtubedl(url, { dumpSingleJson: true, noWarnings: true, noCheckCertificates: true, skipDownload: true });
const meta = (typeof raw === 'string') ? JSON.parse(raw || '{}') : (raw || {});
if (needTitle) title = meta?.title || title || '';
if (needThumb) thumbnail = meta?.thumbnail || (Array.isArray(meta?.thumbnails) && meta.thumbnails.length ? meta.thumbnails[0].url : thumbnail || '');
} catch (e) {
console.warn('[POST /user/likes] details fetch failed, continuing without enrichment:', e?.message || e);
}
}
} catch {}
const row = likeVideo({ userId: req.user.id, provider, videoId, title, thumbnail });
return res.status(201).json(row); return res.status(201).json(row);
}); });
+1 -1
View File
@@ -28,7 +28,7 @@
{{ video.duration / 60 | number:'1.0-0' }}:{{ (video.duration % 60) | number:'2.0-0' }} {{ video.duration / 60 | number:'1.0-0' }}:{{ (video.duration % 60) | number:'2.0-0' }}
</div> </div>
<div class="absolute bottom-2 left-2"> <div class="absolute bottom-2 left-2">
<app-like-button [videoId]="video.videoId" [provider]="'youtube'"></app-like-button> <app-like-button [videoId]="video.videoId" [provider]="instances.selectedProvider()" [title]="video.title" [thumbnail]="video.thumbnail"></app-like-button>
</div> </div>
</div> </div>
<div class="p-4 flex-grow flex flex-col"> <div class="p-4 flex-grow flex flex-col">
+1 -1
View File
@@ -25,7 +25,7 @@ import { LikeButtonComponent } from '../shared/components/like-button/like-butto
}) })
export class HomeComponent { export class HomeComponent {
private apiService = inject(YoutubeApiService); private apiService = inject(YoutubeApiService);
private instances = inject(InstanceService); instances = inject(InstanceService);
trendingVideos = signal<Video[]>([]); trendingVideos = signal<Video[]>([]);
loading = signal(true); loading = signal(true);
+3 -3
View File
@@ -55,7 +55,7 @@
{{ formatViews(video.views) }} en direct {{ formatViews(video.views) }} en direct
</div> </div>
<div class="absolute bottom-2 left-2"> <div class="absolute bottom-2 left-2">
<app-like-button [videoId]="video.videoId" [provider]="'twitch'"></app-like-button> <app-like-button [videoId]="video.videoId" [provider]="'twitch'" [title]="video.title" [thumbnail]="video.thumbnail"></app-like-button>
</div> </div>
</div> </div>
<div class="p-4 flex-grow flex flex-col"> <div class="p-4 flex-grow flex flex-col">
@@ -91,7 +91,7 @@
{{ video.duration / 60 | number:'1.0-0' }}:{{ (video.duration % 60) | number:'2.0-0' }} {{ video.duration / 60 | number:'1.0-0' }}:{{ (video.duration % 60) | number:'2.0-0' }}
</div> </div>
<div class="absolute bottom-2 left-2"> <div class="absolute bottom-2 left-2">
<app-like-button [videoId]="video.videoId" [provider]="'twitch'"></app-like-button> <app-like-button [videoId]="video.videoId" [provider]="'twitch'" [title]="video.title" [thumbnail]="video.thumbnail"></app-like-button>
</div> </div>
</div> </div>
<div class="p-4 flex-grow flex flex-col"> <div class="p-4 flex-grow flex flex-col">
@@ -127,7 +127,7 @@
{{ video.duration / 60 | number:'1.0-0' }}:{{ (video.duration % 60) | number:'2.0-0' }} {{ video.duration / 60 | number:'1.0-0' }}:{{ (video.duration % 60) | number:'2.0-0' }}
</div> </div>
<div class="absolute bottom-2 left-2"> <div class="absolute bottom-2 left-2">
<app-like-button [videoId]="video.videoId" [provider]="selectedProviderForView()"></app-like-button> <app-like-button [videoId]="video.videoId" [provider]="selectedProviderForView()" [title]="video.title" [thumbnail]="video.thumbnail"></app-like-button>
</div> </div>
</div> </div>
<div class="p-4 flex-grow flex flex-col"> <div class="p-4 flex-grow flex flex-col">
@@ -82,6 +82,8 @@ export class LikeButtonComponent implements OnInit, OnDestroy, OnChanges {
private auth = inject(AuthService); private auth = inject(AuthService);
@Input() provider = 'youtube'; @Input() provider = 'youtube';
@Input() title?: string;
@Input() thumbnail?: string;
private _videoId = ''; private _videoId = '';
@@ -193,7 +195,10 @@ export class LikeButtonComponent implements OnInit, OnDestroy, OnChanges {
} }
}); });
} else { } else {
this.likesService.like(this.provider, this.videoId) const cleanTitle = (this.title || '').trim();
const cleanThumb = (this.thumbnail || '').trim();
try { console.debug('[LikeButton] like payload', { provider: this.provider, videoId: this.videoId, title: cleanTitle, hasThumb: !!cleanThumb }); } catch {}
this.likesService.like(this.provider, this.videoId, cleanTitle || undefined, cleanThumb || undefined)
.pipe( .pipe(
finalize(() => { finalize(() => {
if (!this.destroyRef) { if (!this.destroyRef) {
@@ -80,7 +80,7 @@
<div class="relative"> <div class="relative">
<img [src]="v.thumbnail" [alt]="v.title" loading="lazy" decoding="async" class="w-full h-32 object-cover"> <img [src]="v.thumbnail" [alt]="v.title" loading="lazy" decoding="async" class="w-full h-32 object-cover">
<div class="absolute bottom-2 left-2"> <div class="absolute bottom-2 left-2">
<app-like-button [videoId]="v.videoId" [provider]="provider()"></app-like-button> <app-like-button [videoId]="v.videoId" [provider]="provider()" [title]="v.title" [thumbnail]="v.thumbnail"></app-like-button>
</div> </div>
</div> </div>
<div class="p-2 text-sm line-clamp-2">{{ v.title }}</div> <div class="p-2 text-sm line-clamp-2">{{ v.title }}</div>
@@ -131,7 +131,7 @@
<div class="relative"> <div class="relative">
<img [src]="v.thumbnail" [alt]="v.title" loading="lazy" decoding="async" class="w-full h-32 object-cover"> <img [src]="v.thumbnail" [alt]="v.title" loading="lazy" decoding="async" class="w-full h-32 object-cover">
<div class="absolute bottom-2 left-2"> <div class="absolute bottom-2 left-2">
<app-like-button [videoId]="v.videoId" [provider]="provider()"></app-like-button> <app-like-button [videoId]="v.videoId" [provider]="provider()" [title]="v.title" [thumbnail]="v.thumbnail"></app-like-button>
</div> </div>
</div> </div>
<div class="p-2 text-sm line-clamp-2">{{ v.title }}</div> <div class="p-2 text-sm line-clamp-2">{{ v.title }}</div>
@@ -33,7 +33,7 @@
{{ v.duration / 60 | number:'1.0-0' }}:{{ (v.duration % 60) | number:'2.0-0' }} {{ v.duration / 60 | number:'1.0-0' }}:{{ (v.duration % 60) | number:'2.0-0' }}
</div> </div>
<div class="absolute bottom-2 left-2"> <div class="absolute bottom-2 left-2">
<app-like-button [videoId]="v.videoId" [provider]="b.provider"></app-like-button> <app-like-button [videoId]="v.videoId" [provider]="b.provider" [title]="v.title" [thumbnail]="v.thumbnail"></app-like-button>
</div> </div>
</div> </div>
<div class="p-3"> <div class="p-3">
+3 -2
View File
@@ -8,6 +8,7 @@ export interface LikedVideoItem {
title?: string; title?: string;
thumbnail?: string; thumbnail?: string;
last_watched_at?: string | null; last_watched_at?: string | null;
created_at?: string;
} }
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
@@ -29,10 +30,10 @@ export class LikesService {
return this.http.get<LikedVideoItem[]>(`${this.apiBase()}/user/likes`, { params, withCredentials: true }); return this.http.get<LikedVideoItem[]>(`${this.apiBase()}/user/likes`, { params, withCredentials: true });
} }
like(provider: string, videoId: string): Observable<{ provider: string; video_id: string }> { like(provider: string, videoId: string, title?: string, thumbnail?: string): Observable<{ provider: string; video_id: string }> {
return this.http.post<{ provider: string; video_id: string }>( return this.http.post<{ provider: string; video_id: string }>(
`${this.apiBase()}/user/likes`, `${this.apiBase()}/user/likes`,
{ provider, videoId }, { provider, videoId, title, thumbnail },
{ withCredentials: true } { withCredentials: true }
); );
} }