feat: frontend shadcn
This commit is contained in:
31
backend/.gitignore
vendored
Normal file
31
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Uploads (user content)
|
||||
uploads/*
|
||||
!uploads/.gitkeep
|
||||
|
||||
# Database
|
||||
*.sqlite
|
||||
*.db
|
||||
@@ -6,10 +6,7 @@ import { YouTubeService } from './youtube.service';
|
||||
import { YouTubeCache } from '../../entities/youtube-cache.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([YouTubeCache]),
|
||||
HttpModule,
|
||||
],
|
||||
imports: [TypeOrmModule.forFeature([YouTubeCache]), HttpModule],
|
||||
controllers: [YouTubeController],
|
||||
providers: [YouTubeService],
|
||||
})
|
||||
|
||||
@@ -55,7 +55,10 @@ export class YouTubeService {
|
||||
this.apiKey = this.configService.get<string>('YOUTUBE_API_KEY', '');
|
||||
}
|
||||
|
||||
async search(query: string, maxResults = 10): Promise<YouTubeVideoResponse[]> {
|
||||
async search(
|
||||
query: string,
|
||||
maxResults = 10,
|
||||
): Promise<YouTubeVideoResponse[]> {
|
||||
// Check cache first
|
||||
const cached = await this.getFromCache(query);
|
||||
if (cached) {
|
||||
@@ -76,7 +79,9 @@ export class YouTubeService {
|
||||
return results;
|
||||
}
|
||||
|
||||
private async getFromCache(query: string): Promise<YouTubeVideoResponse[] | null> {
|
||||
private async getFromCache(
|
||||
query: string,
|
||||
): Promise<YouTubeVideoResponse[] | null> {
|
||||
const cached = await this.cacheRepository.findOne({
|
||||
where: {
|
||||
searchQuery: query.toLowerCase(),
|
||||
@@ -87,7 +92,10 @@ export class YouTubeService {
|
||||
return cached ? cached.results : null;
|
||||
}
|
||||
|
||||
private async saveToCache(query: string, results: YouTubeVideoResponse[]): Promise<void> {
|
||||
private async saveToCache(
|
||||
query: string,
|
||||
results: YouTubeVideoResponse[],
|
||||
): Promise<void> {
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setHours(expiresAt.getHours() + this.cacheHours);
|
||||
|
||||
@@ -100,7 +108,10 @@ export class YouTubeService {
|
||||
await this.cacheRepository.save(cache);
|
||||
}
|
||||
|
||||
private async fetchFromYouTube(query: string, maxResults: number): Promise<YouTubeVideoResponse[]> {
|
||||
private async fetchFromYouTube(
|
||||
query: string,
|
||||
maxResults: number,
|
||||
): Promise<YouTubeVideoResponse[]> {
|
||||
const searchUrl = 'https://www.googleapis.com/youtube/v3/search';
|
||||
|
||||
const { data } = await firstValueFrom(
|
||||
@@ -115,7 +126,9 @@ export class YouTubeService {
|
||||
}),
|
||||
);
|
||||
|
||||
const videoIds = data.items.map((item: YouTubeSearchItem) => item.id.videoId).join(',');
|
||||
const videoIds = data.items
|
||||
.map((item: YouTubeSearchItem) => item.id.videoId)
|
||||
.join(',');
|
||||
|
||||
// Get view counts
|
||||
const statsUrl = 'https://www.googleapis.com/youtube/v3/videos';
|
||||
@@ -138,20 +151,26 @@ export class YouTubeService {
|
||||
videoId: item.id.videoId,
|
||||
title: item.snippet.title,
|
||||
channelTitle: item.snippet.channelTitle,
|
||||
thumbnailUrl: item.snippet.thumbnails.high?.url || item.snippet.thumbnails.medium.url,
|
||||
thumbnailUrl:
|
||||
item.snippet.thumbnails.high?.url || item.snippet.thumbnails.medium.url,
|
||||
publishedAt: item.snippet.publishedAt,
|
||||
viewCount: viewCounts.get(item.id.videoId),
|
||||
}));
|
||||
}
|
||||
|
||||
private getMockResults(query: string, maxResults: number): YouTubeVideoResponse[] {
|
||||
private getMockResults(
|
||||
query: string,
|
||||
maxResults: number,
|
||||
): YouTubeVideoResponse[] {
|
||||
const mockVideos: YouTubeVideoResponse[] = [
|
||||
{
|
||||
videoId: 'mock1',
|
||||
title: `${query} - Complete Tutorial for Beginners`,
|
||||
channelTitle: 'Tech Academy',
|
||||
thumbnailUrl: 'https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg',
|
||||
publishedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
publishedAt: new Date(
|
||||
Date.now() - 7 * 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
viewCount: '1250000',
|
||||
},
|
||||
{
|
||||
@@ -159,7 +178,9 @@ export class YouTubeService {
|
||||
title: `Learn ${query} in 30 Minutes`,
|
||||
channelTitle: 'Code Master',
|
||||
thumbnailUrl: 'https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg',
|
||||
publishedAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
publishedAt: new Date(
|
||||
Date.now() - 30 * 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
viewCount: '890000',
|
||||
},
|
||||
{
|
||||
@@ -167,7 +188,9 @@ export class YouTubeService {
|
||||
title: `${query} Crash Course 2025`,
|
||||
channelTitle: 'Dev Tutorial',
|
||||
thumbnailUrl: 'https://i.ytimg.com/vi/kJQP7kiw5Fk/hqdefault.jpg',
|
||||
publishedAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
publishedAt: new Date(
|
||||
Date.now() - 14 * 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
viewCount: '2100000',
|
||||
},
|
||||
{
|
||||
@@ -175,7 +198,9 @@ export class YouTubeService {
|
||||
title: `Why ${query} is Amazing`,
|
||||
channelTitle: 'Tech Reviews',
|
||||
thumbnailUrl: 'https://i.ytimg.com/vi/RgKAFK5djSk/hqdefault.jpg',
|
||||
publishedAt: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
publishedAt: new Date(
|
||||
Date.now() - 60 * 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
viewCount: '450000',
|
||||
},
|
||||
{
|
||||
@@ -183,7 +208,9 @@ export class YouTubeService {
|
||||
title: `${query} Tips and Tricks`,
|
||||
channelTitle: 'Pro Tips',
|
||||
thumbnailUrl: 'https://i.ytimg.com/vi/fJ9rUzIMcZQ/hqdefault.jpg',
|
||||
publishedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
publishedAt: new Date(
|
||||
Date.now() - 3 * 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
viewCount: '320000',
|
||||
},
|
||||
{
|
||||
@@ -191,7 +218,9 @@ export class YouTubeService {
|
||||
title: `${query} for Professionals`,
|
||||
channelTitle: 'Advanced Learning',
|
||||
thumbnailUrl: 'https://i.ytimg.com/vi/09R8_2nJtjg/hqdefault.jpg',
|
||||
publishedAt: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
publishedAt: new Date(
|
||||
Date.now() - 45 * 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
viewCount: '780000',
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user