From 9e8a5a28e0622b88315b967f844927d3ce5cf49b Mon Sep 17 00:00:00 2001 From: Joel Hooks Date: Fri, 22 Nov 2024 13:34:01 -0800 Subject: [PATCH] chore: tighten up sync (#330) --- apps/egghead/src/lib/egghead.ts | 70 ++++++----- apps/egghead/src/lib/posts-query.ts | 23 +++- apps/egghead/src/lib/posts.ts | 1 + apps/egghead/src/lib/sanity-content-query.ts | 118 ++++++++++++++++++- 4 files changed, 174 insertions(+), 38 deletions(-) diff --git a/apps/egghead/src/lib/egghead.ts b/apps/egghead/src/lib/egghead.ts index 35747af9b..261fdca5d 100644 --- a/apps/egghead/src/lib/egghead.ts +++ b/apps/egghead/src/lib/egghead.ts @@ -81,32 +81,41 @@ export async function createEggheadLesson(input: { }) { const { title, slug, guid, instructorId, hlsUrl = null } = input - const eggheadLessonResult = await eggheadPgQuery( - `INSERT INTO lessons (title, instructor_id, slug, resource_type, state, - created_at, updated_at, visibility_state, guid${hlsUrl ? ', current_video_hls_url' : ''}) - VALUES ($1, $2, $3, $4, $5, NOW(), NOW(), $6, $7${hlsUrl ? ', $8' : ''}) - RETURNING id`, - hlsUrl - ? [ - title, - instructorId, - slug, - EGGHEAD_LESSON_TYPE, - EGGHEAD_INITIAL_LESSON_STATE, - 'hidden', - guid, - hlsUrl, - ] - : [ - title, - instructorId, - slug, - EGGHEAD_LESSON_TYPE, - EGGHEAD_INITIAL_LESSON_STATE, - 'hidden', - guid, - ], - ) + const columns = [ + 'title', + 'instructor_id', + 'slug', + 'resource_type', + 'state', + 'created_at', + 'updated_at', + 'visibility_state', + 'guid', + ...(hlsUrl ? ['current_video_hls_url'] : []), + ] + + const values = [ + title, + instructorId, + slug, + EGGHEAD_LESSON_TYPE, + EGGHEAD_INITIAL_LESSON_STATE, + new Date(), // created_at + new Date(), // updated_at + 'hidden', + guid, + ...(hlsUrl ? [hlsUrl] : []), + ] + + const placeholders = columns.map((_, index) => `$${index + 1}`).join(', ') + + const query = ` + INSERT INTO lessons (${columns.join(', ')}) + VALUES (${placeholders}) + RETURNING id + ` + + const eggheadLessonResult = await eggheadPgQuery(query, values) const eggheadLessonId = eggheadLessonResult.rows[0].id @@ -129,6 +138,7 @@ export async function updateEggheadLesson(input: { visibilityState: string duration: number hlsUrl?: string + body?: string }) { const { eggheadLessonId, @@ -139,6 +149,7 @@ export async function updateEggheadLesson(input: { title, slug, guid, + body = '', } = input await eggheadPgQuery( `UPDATE lessons SET @@ -149,8 +160,9 @@ export async function updateEggheadLesson(input: { current_video_hls_url = $4, title = $5, slug = $6, - guid = $7 - WHERE id = $8`, + guid = $7, + summary = $8 + WHERE id = $9`, [ state, Math.floor(duration), @@ -159,6 +171,7 @@ export async function updateEggheadLesson(input: { title, slug, guid, + body, eggheadLessonId, ], ) @@ -233,6 +246,7 @@ export const eggheadLessonSchema = z.object({ summary: z.string().nullish(), topic_list: z.array(z.string()), free_forever: z.boolean(), + is_pro: z.boolean(), body: z.string().nullish(), state: z.string(), instructor: z.object({ diff --git a/apps/egghead/src/lib/posts-query.ts b/apps/egghead/src/lib/posts-query.ts index 1dc9fed67..a238c4e67 100644 --- a/apps/egghead/src/lib/posts-query.ts +++ b/apps/egghead/src/lib/posts-query.ts @@ -11,7 +11,6 @@ import { contentResourceTag as contentResourceTagTable, contentResourceVersion as contentResourceVersionTable, contributionTypes, - users, } from '@/db/schema' import { generateContentHash, @@ -28,7 +27,6 @@ import { subject } from '@casl/ability' import slugify from '@sindresorhus/slugify' import { and, asc, desc, eq, like, or, sql } from 'drizzle-orm' import readingTime from 'reading-time' -import Typesense from 'typesense' import { z } from 'zod' import 'server-only' @@ -51,6 +49,10 @@ import { updateEggheadLesson, writeLegacyTaggingsToEgghead, } from './egghead' +import { + replaceSanityLessonResources, + updateSanityLesson, +} from './sanity-content-query' import { EggheadTag, EggheadTagSchema } from './tags' import { upsertPostToTypeSense } from './typesense' @@ -385,6 +387,14 @@ export async function writeNewPostToDatabase(input: { const post = await getPost(newPostId) + if (post && post.fields.eggheadLessonId) { + await updateSanityLesson(post.fields.eggheadLessonId, post) + await replaceSanityLessonResources({ + eggheadLessonId: post.fields.eggheadLessonId, + videoResourceId: videoResourceId, + }) + } + await createNewPostVersion(post) if (post) { @@ -466,13 +476,12 @@ export async function writePostUpdateToDatabase(input: { ? await courseBuilderAdapter.getVideoResource(videoResourceId) : null - // probably update sanity here - if (currentPost.fields.eggheadLessonId) { await updateEggheadLesson({ title: postUpdate.fields.title, slug: postSlug, // probably bypassing friendly id here, does it matter? guid: postGuid, + body: postUpdate.fields.body ?? '', eggheadLessonId: currentPost.fields.eggheadLessonId, state: lessonState, visibilityState: lessonVisibilityState, @@ -498,6 +507,12 @@ export async function writePostUpdateToDatabase(input: { throw new Error(`Post with id ${currentPost.id} not found.`) } + await updateSanityLesson(currentPost.fields.eggheadLessonId, updatedPost) + await replaceSanityLessonResources({ + eggheadLessonId: currentPost.fields.eggheadLessonId, + videoResourceId: videoResourceId, + }) + const newContentHash = generateContentHash(updatedPost) const currentContentHash = currentPost.currentVersionId?.split('~')[1] diff --git a/apps/egghead/src/lib/posts.ts b/apps/egghead/src/lib/posts.ts index 6a9180106..edb5ec004 100644 --- a/apps/egghead/src/lib/posts.ts +++ b/apps/egghead/src/lib/posts.ts @@ -79,6 +79,7 @@ export const PostUpdateSchema = z.object({ title: z.string().min(2).max(90), postType: PostTypeSchema.optional().default('lesson'), body: z.string().optional().nullable(), + description: z.string().optional().nullable(), visibility: PostVisibilitySchema.optional().default('public'), state: PostStateSchema.optional().default('draft'), }), diff --git a/apps/egghead/src/lib/sanity-content-query.ts b/apps/egghead/src/lib/sanity-content-query.ts index afc77ed00..342e63e7f 100644 --- a/apps/egghead/src/lib/sanity-content-query.ts +++ b/apps/egghead/src/lib/sanity-content-query.ts @@ -1,11 +1,15 @@ 'use server' -import type { EggheadLesson } from '@/lib/egghead' +import { courseBuilderAdapter, db } from '@/db' +import { contentResource } from '@/db/schema' +import { getEggheadLesson, type EggheadLesson } from '@/lib/egghead' import { keyGenerator, sanityCollaboratorDocumentSchema, + sanityCollaboratorReferenceObjectSchema, sanityLessonDocumentSchema, sanitySoftwareLibraryDocumentSchema, + sanityVersionedSoftwareLibraryObjectSchema, sanityVideoResourceDocumentSchema, } from '@/lib/sanity-content' import type { @@ -15,9 +19,12 @@ import type { SanityVersionedSoftwareLibraryObject, } from '@/lib/sanity-content' import { sanityWriteClient } from '@/server/sanity-write-client' +import { eq, sql } from 'drizzle-orm' import type { VideoResource } from '@coursebuilder/core/schemas' +import { Post } from './posts' + export async function createSanityVideoResource(videoResource: VideoResource) { const { muxPlaybackId, muxAssetId, transcript, srt, id } = videoResource @@ -49,11 +56,49 @@ export async function createSanityVideoResource(videoResource: VideoResource) { return sanityVideoResourceDocument } +export async function replaceSanityLessonResources({ + eggheadLessonId, + videoResourceId, +}: { + eggheadLessonId: number | null | undefined + videoResourceId: string | null | undefined +}) { + if (!eggheadLessonId || !videoResourceId) return + + const videoResource = + await courseBuilderAdapter.getVideoResource(videoResourceId) + + if (!videoResource) { + throw new Error(`Video resource with id ${videoResourceId} not found.`) + } + + const sanityLessonDocument = + await getSanityLessonForEggheadLessonId(eggheadLessonId) + + const sanityVideoResourceDocument = + await createSanityVideoResource(videoResource) + + return await sanityWriteClient + .patch(sanityLessonDocument._id) + .set({ + resources: [ + { + _key: keyGenerator(), + _type: 'reference', + _ref: sanityVideoResourceDocument._id, + }, + ], + }) + .commit() +} + export async function patchSanityLessonWithVideoResourceReference( - eggheadLessonId: number, + eggheadLessonId: number | null | undefined, videoResourceDocumentId: string, ) { - const sanityLessonDocument = await getSanityLesson(eggheadLessonId) + if (!eggheadLessonId) return + const sanityLessonDocument = + await getSanityLessonForEggheadLessonId(eggheadLessonId) if (!sanityLessonDocument) return @@ -72,7 +117,7 @@ export async function patchSanityLessonWithVideoResourceReference( .commit() } -export async function getSanityLesson( +export async function getSanityLessonForEggheadLessonId( eggheadLessonId: number | null | undefined, ) { if (!eggheadLessonId) return @@ -82,22 +127,83 @@ export async function getSanityLesson( ) } +export async function updateSanityLesson( + eggheadLessonId: number | null | undefined, + lesson?: Post | null, +) { + if (!eggheadLessonId || !lesson) return + + const sanityLessonDocument = + await getSanityLessonForEggheadLessonId(eggheadLessonId) + const eggheadLesson = await getEggheadLesson(eggheadLessonId) + + if (!sanityLessonDocument || !eggheadLesson) return + + const softwareLibraries = await Promise.all( + eggheadLesson.topic_list.map(async (library: string) => { + return sanityVersionedSoftwareLibraryObjectSchema.parse( + await getSanitySoftwareLibrary(library), + ) + }), + ) + + const collaborator = sanityCollaboratorReferenceObjectSchema.parse( + await getSanityCollaborator(eggheadLesson.instructor.id), + ) + + await sanityWriteClient + .patch(sanityLessonDocument._id) + .set({ + description: lesson.fields.body, + status: eggheadLesson.state, + accessLevel: eggheadLesson.is_pro + ? 'pro' + : eggheadLesson.free_forever + ? 'free' + : 'pro', + title: lesson.fields.title, + slug: { + _type: 'slug', + current: lesson.fields.slug, + }, + collaborators: [collaborator], + softwareLibraries, + }) + .commit() +} + export async function createSanityLesson( eggheadLesson: EggheadLesson, collaborator: SanityCollaboratorReferenceObject, softwareLibraries: SanityVersionedSoftwareLibraryObject[], ) { + const post = await db.query.contentResource.findFirst({ + where: eq( + sql`JSON_EXTRACT (${contentResource.fields}, "$.eggheadLessonId")`, + eggheadLesson.id, + ), + }) + + if (!post) { + throw new Error(`Post with id ${eggheadLesson.id} not found.`) + } + const lesson = sanityLessonDocumentSchema.parse({ + _id: `lesson-${eggheadLesson.id}`, _type: 'lesson', title: eggheadLesson.title, slug: { _type: 'slug', current: eggheadLesson.slug, }, - description: eggheadLesson.body, + description: post?.fields?.body, railsLessonId: eggheadLesson.id, status: eggheadLesson.state, - accessLevel: eggheadLesson.free_forever ? 'free' : 'pro', + accessLevel: eggheadLesson.is_pro + ? 'pro' + : eggheadLesson.free_forever + ? 'free' + : 'pro', collaborators: [collaborator], softwareLibraries, })