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,
video_id TEXT NOT NULL,
title TEXT,
thumbnail TEXT,
watched_at TEXT NOT NULL,
progress_seconds INTEGER DEFAULT 0,
duration_seconds INTEGER DEFAULT 0,
@@ -145,5 +146,6 @@ CREATE TABLE IF NOT EXISTS video_tags (
provider TEXT NOT NULL,
video_id TEXT NOT NULL,
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)
);
+11 -4
View File
@@ -279,11 +279,17 @@ function ensureTag(userId, name) {
return id;
}
export function likeVideo({ userId, provider, videoId }) {
export function likeVideo({ userId, provider, videoId, title, thumbnail }) {
const tagId = ensureTag(userId, 'like');
// Upsert-like behavior; ignore if exists
db.prepare(`INSERT OR IGNORE INTO video_tags (user_id, provider, video_id, tag_id) VALUES (?, ?, ?, ?)`)
.run(userId, provider, videoId, tagId);
db.prepare(`INSERT OR IGNORE INTO video_tags (user_id, provider, video_id, tag_id, created_at) VALUES (?, ?, ?, ?, ?)`)
.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 };
}
@@ -350,6 +356,7 @@ export function listLikedVideos({ userId, limit = 100, q }) {
SELECT
vt.provider,
vt.video_id,
vt.created_at,
COALESCE(wh.title, '') AS title,
COALESCE(wh.thumbnail, '') AS thumbnail,
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 = ?
`;
const orderLimit = `
ORDER BY COALESCE(wh.last_watched_at, wh.watched_at) DESC
ORDER BY vt.created_at DESC
LIMIT ?
`;
const query = hasQ
+29 -3
View File
@@ -1058,10 +1058,36 @@ r.get('/user/likes', authMiddleware, (req, res) => {
return res.json(rows);
});
r.post('/user/likes', authMiddleware, (req, res) => {
const { provider, videoId } = req.body || {};
r.post('/user/likes', authMiddleware, async (req, res) => {
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' });
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);
});
+1 -1
View File
@@ -28,7 +28,7 @@
{{ video.duration / 60 | number:'1.0-0' }}:{{ (video.duration % 60) | number:'2.0-0' }}
</div>
<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 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 {
private apiService = inject(YoutubeApiService);
private instances = inject(InstanceService);
instances = inject(InstanceService);
trendingVideos = signal<Video[]>([]);
loading = signal(true);
+3 -3
View File
@@ -55,7 +55,7 @@
{{ formatViews(video.views) }} en direct
</div>
<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 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' }}
</div>
<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 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' }}
</div>
<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 class="p-4 flex-grow flex flex-col">
@@ -82,6 +82,8 @@ export class LikeButtonComponent implements OnInit, OnDestroy, OnChanges {
private auth = inject(AuthService);
@Input() provider = 'youtube';
@Input() title?: string;
@Input() thumbnail?: string;
private _videoId = '';
@@ -193,7 +195,10 @@ export class LikeButtonComponent implements OnInit, OnDestroy, OnChanges {
}
});
} 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(
finalize(() => {
if (!this.destroyRef) {
@@ -80,7 +80,7 @@
<div class="relative">
<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">
<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 class="p-2 text-sm line-clamp-2">{{ v.title }}</div>
@@ -131,7 +131,7 @@
<div class="relative">
<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">
<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 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' }}
</div>
<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 class="p-3">
+3 -2
View File
@@ -8,6 +8,7 @@ export interface LikedVideoItem {
title?: string;
thumbnail?: string;
last_watched_at?: string | null;
created_at?: string;
}
@Injectable({ providedIn: 'root' })
@@ -29,10 +30,10 @@ export class LikesService {
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 }>(
`${this.apiBase()}/user/likes`,
{ provider, videoId },
{ provider, videoId, title, thumbnail },
{ withCredentials: true }
);
}