|
1 | 1 | import { Document, MongoClient } from 'mongodb'
|
2 | 2 | import { unstable_cache, unstable_expireTag } from 'next/cache'
|
| 3 | +import { cache } from 'react' |
3 | 4 | import { z } from 'zod'
|
4 | 5 | import { Channel } from '../../models/channel'
|
5 | 6 | import { Video } from '../../models/video'
|
6 | 7 | import { DbClient, dbClientSchema } from '../../schemas/db-client'
|
7 | 8 |
|
8 |
| -export const createMongoDbClient = z |
9 |
| - .function() |
10 |
| - .args(z.object({ connectionString: z.string().min(1) })) |
11 |
| - .returns(z.promise(dbClientSchema as z.ZodType<DbClient>)) |
12 |
| - .implement(async function createMongoDbClient({ connectionString }): Promise<DbClient> { |
13 |
| - const databaseName = 'youtube-serverless' |
| 9 | +export const createMongoDbClient = cache( |
| 10 | + z |
| 11 | + .function() |
| 12 | + .args(z.object({ connectionString: z.string().min(1) })) |
| 13 | + .returns(z.promise(dbClientSchema as z.ZodType<DbClient>)) |
| 14 | + .implement(async function createMongoDbClient({ connectionString }): Promise<DbClient> { |
| 15 | + const databaseName = 'youtube-serverless' |
14 | 16 |
|
15 |
| - // Maintain the same client across factory calls, as long as the connection string is the same. |
16 |
| - const client = new MongoClient(connectionString, { appName: 'youtube-serverless' }) |
| 17 | + // Maintain the same client across factory calls, as long as the connection string is the same. |
| 18 | + const client = new MongoClient(connectionString, { appName: 'youtube-serverless' }) |
17 | 19 |
|
18 |
| - type Collection = 'channels' | 'videos' |
| 20 | + type Collection = 'channels' | 'videos' |
19 | 21 |
|
20 |
| - const getCollection = <C extends Document = never>(collectionName: Collection) => { |
21 |
| - const collection = client.db(databaseName).collection<C>(collectionName) |
| 22 | + const getCollection = <C extends Document = never>(collectionName: Collection) => { |
| 23 | + const collection = client.db(databaseName).collection<C>(collectionName) |
22 | 24 |
|
23 |
| - return Promise.resolve({ |
24 |
| - collection, |
25 |
| - }) |
26 |
| - } |
27 |
| - |
28 |
| - const getChannels = dbClientSchema.shape.getChannels.implement( |
29 |
| - async function getChannels(): Promise<Channel[]> { |
30 |
| - const { collection } = await getCollection<Channel>('channels') |
31 |
| - const channelsWithId = await collection.find().toArray() |
32 |
| - return channelsWithId.map<Channel>( |
33 |
| - (channel) => |
34 |
| - ({ |
35 |
| - channelId: channel.channelId, |
36 |
| - channelTitle: channel.channelTitle, |
37 |
| - playlist: channel.playlist, |
38 |
| - thumbnail: channel.thumbnail, |
39 |
| - channelThumbnail: channel.channelThumbnail, |
40 |
| - channelLink: channel.channelLink, |
41 |
| - }) satisfies Channel |
42 |
| - ) |
| 25 | + return Promise.resolve({ |
| 26 | + collection, |
| 27 | + }) |
43 | 28 | }
|
44 |
| - ) |
45 | 29 |
|
46 |
| - const updateChannel = dbClientSchema.shape.updateChannel.implement( |
47 |
| - async function updateChannel({ channel }) { |
48 |
| - const { collection } = await getCollection<Channel>('channels') |
49 |
| - await collection.replaceOne({ channelId: channel.channelId }, channel, { upsert: true }) |
50 |
| - } |
51 |
| - ) |
| 30 | + const getChannels = dbClientSchema.shape.getChannels.implement( |
| 31 | + async function getChannels(): Promise<Channel[]> { |
| 32 | + const { collection } = await getCollection<Channel>('channels') |
| 33 | + const channelsWithId = await collection.find().toArray() |
| 34 | + return channelsWithId.map<Channel>( |
| 35 | + (channel) => |
| 36 | + ({ |
| 37 | + channelId: channel.channelId, |
| 38 | + channelTitle: channel.channelTitle, |
| 39 | + playlist: channel.playlist, |
| 40 | + thumbnail: channel.thumbnail, |
| 41 | + channelThumbnail: channel.channelThumbnail, |
| 42 | + channelLink: channel.channelLink, |
| 43 | + }) satisfies Channel |
| 44 | + ) |
| 45 | + } |
| 46 | + ) |
52 | 47 |
|
53 |
| - const latestVideosTag = 'latest-videos' |
| 48 | + const updateChannel = dbClientSchema.shape.updateChannel.implement( |
| 49 | + async function updateChannel({ channel }) { |
| 50 | + const { collection } = await getCollection<Channel>('channels') |
| 51 | + await collection.replaceOne({ channelId: channel.channelId }, channel, { upsert: true }) |
| 52 | + } |
| 53 | + ) |
54 | 54 |
|
55 |
| - const putLatestVideos = dbClientSchema.shape.putLatestVideos.implement(async function putVideo({ |
56 |
| - videos, |
57 |
| - }) { |
58 |
| - const { collection } = await getCollection<{ videos: Video[] }>('videos') |
59 |
| - await collection.updateOne({}, { $set: { videos } }, { upsert: true }) |
60 |
| - unstable_expireTag(latestVideosTag) |
61 |
| - }) |
| 55 | + const latestVideosTag = 'latest-videos' |
62 | 56 |
|
63 |
| - const getLatestVideos = dbClientSchema.shape.getLatestVideos.implement( |
64 |
| - unstable_cache( |
65 |
| - async function getLatestVideos({ limit }): Promise<Video[]> { |
| 57 | + const putLatestVideos = dbClientSchema.shape.putLatestVideos.implement( |
| 58 | + async function putVideo({ videos }) { |
66 | 59 | const { collection } = await getCollection<{ videos: Video[] }>('videos')
|
67 |
| - const { videos } = (await collection.findOne()) ?? {} |
68 |
| - return videos == null |
69 |
| - ? [] |
70 |
| - : videos.slice(0, limit).map<Video>( |
71 |
| - (video) => |
72 |
| - ({ |
73 |
| - channelId: video.channelId, |
74 |
| - videoId: video.videoId, |
75 |
| - videoPublishedAt: video.videoPublishedAt, |
76 |
| - thumbnail: video.thumbnail, |
77 |
| - channelTitle: video.channelTitle, |
78 |
| - channelThumbnail: video.channelThumbnail, |
79 |
| - channelLink: video.channelLink, |
80 |
| - title: video.title, |
81 |
| - durationInSeconds: video.durationInSeconds, |
82 |
| - }) satisfies Video |
83 |
| - ) |
84 |
| - }, |
85 |
| - ['latest-videos'], |
86 |
| - { revalidate: 60 * 60, tags: [latestVideosTag] } |
| 60 | + await collection.updateOne({}, { $set: { videos } }, { upsert: true }) |
| 61 | + unstable_expireTag(latestVideosTag) |
| 62 | + } |
87 | 63 | )
|
88 |
| - ) |
89 | 64 |
|
90 |
| - const deleteOldVideos = dbClientSchema.shape.deleteOldVideos.implement( |
91 |
| - async function deleteOldVideos(): Promise<number> { |
92 |
| - // NOTE: Nothing to cleanup for this backend. |
93 |
| - return Promise.resolve(0) |
94 |
| - } |
95 |
| - ) |
| 65 | + const getLatestVideos = dbClientSchema.shape.getLatestVideos.implement( |
| 66 | + unstable_cache( |
| 67 | + async function getLatestVideos({ limit }): Promise<Video[]> { |
| 68 | + const { collection } = await getCollection<{ videos: Video[] }>('videos') |
| 69 | + const { videos } = (await collection.findOne()) ?? {} |
| 70 | + return videos == null |
| 71 | + ? [] |
| 72 | + : videos.slice(0, limit).map<Video>( |
| 73 | + (video) => |
| 74 | + ({ |
| 75 | + channelId: video.channelId, |
| 76 | + videoId: video.videoId, |
| 77 | + videoPublishedAt: video.videoPublishedAt, |
| 78 | + thumbnail: video.thumbnail, |
| 79 | + channelTitle: video.channelTitle, |
| 80 | + channelThumbnail: video.channelThumbnail, |
| 81 | + channelLink: video.channelLink, |
| 82 | + title: video.title, |
| 83 | + durationInSeconds: video.durationInSeconds, |
| 84 | + }) satisfies Video |
| 85 | + ) |
| 86 | + }, |
| 87 | + ['latest-videos'], |
| 88 | + { revalidate: 60 * 60, tags: [latestVideosTag] } |
| 89 | + ) |
| 90 | + ) |
96 | 91 |
|
97 |
| - const close: DbClient['close'] = () => client.close() |
| 92 | + const deleteOldVideos = dbClientSchema.shape.deleteOldVideos.implement( |
| 93 | + async function deleteOldVideos(): Promise<number> { |
| 94 | + // NOTE: Nothing to cleanup for this backend. |
| 95 | + return Promise.resolve(0) |
| 96 | + } |
| 97 | + ) |
| 98 | + |
| 99 | + const close: DbClient['close'] = () => client.close() |
98 | 100 |
|
99 |
| - await client.connect() |
| 101 | + await client.connect() |
100 | 102 |
|
101 |
| - return { |
102 |
| - getChannels, |
103 |
| - updateChannel, |
104 |
| - putLatestVideos, |
105 |
| - getLatestVideos, |
106 |
| - deleteOldVideos, |
107 |
| - close, |
108 |
| - } satisfies DbClient |
109 |
| - }) |
| 103 | + return { |
| 104 | + getChannels, |
| 105 | + updateChannel, |
| 106 | + putLatestVideos, |
| 107 | + getLatestVideos, |
| 108 | + deleteOldVideos, |
| 109 | + close, |
| 110 | + } satisfies DbClient |
| 111 | + }) |
| 112 | +) |
0 commit comments