/** @typedef {import("fastify").FastifyInstance} FastifyInstance */ import { desc, eq, getTableColumns, sql } from "drizzle-orm"; import { db } from "../db/index.js"; import { articles, articles as articlesTable, signups as signupsTable, sites, users } from "../db/schemas.js"; import { authMiddleware, authMiddlewareFn } from "../modules/middleware.js"; import { jsonToCsv, getAccessToken, getVideoCaptions, getCaptionText, parseTextFromCaptions, createBlogFromCaptions, createArticleSlug, getVideoById, env } from "../utils/index.js"; /** * * @param {FastifyInstance} fastify * @param {unknown} _ * @param {() => void} done */ export const dashboardRoutes = (fastify, _, done) => { fastify.register(authMiddleware); fastify.get("/", async (req, response) => { try { const [{ site_id, user_id }] = await db.select({ site_id: sites.id, user_id: sites.user_id }).from(sites).where(eq(sites.user_id, req.session.user_id)); const recentArticles = await db.select({ title: articlesTable.title, views: articlesTable.views, created_at: articlesTable.created_at }).from(articlesTable) .where(eq(articlesTable.site_id, site_id)) .orderBy(articlesTable.created_at).limit(5); const recentSignups = await db.select({ email: signupsTable.email, created_at: signupsTable.created_at }).from(signupsTable) .where(eq(signupsTable.site_id, site_id)) .orderBy(desc(signupsTable.created_at)).limit(8); const [{ totalArticles }] = await db.select({ totalArticles: sql`count(*)` }).from(articlesTable).where(eq(articlesTable.site_id, site_id)); const [{ totalViews }] = await db.select({ totalViews: sql`COALESCE(sum(${articlesTable.views}), 0)` }).from(articlesTable).where(eq(articlesTable.site_id, site_id)); const [{ totalEmails }] = await db.select({ totalEmails: sql`count(*)` }).from(signupsTable).where(eq(signupsTable.site_id, site_id)); response.send({ success: true, recentArticles, recentSignups, totalArticles, totalEmails, totalViews }); return; } catch (e) { console.log(e); response.status(500).send({ success: false, message: "Failed to get dashboard data", log: e.message }); return; } }); fastify.get("/email-export", async (req, response) => { }); fastify.get("/signups", async (req, reply) => { const site = await db.select().from(sites).where(eq(sites.user_id, req.session.user_id)); if (site[0]?.user_id != req.session.user_id) { reply.status(403).send({ success: false }); return; } const signups = await db.select().from(signupsTable).where(eq(signupsTable.site_id, site[0].id)).orderBy(desc(signupsTable.created_at)); reply.send({ success: true, signups }); }); fastify.get("/signups/export", async (req, reply) => { try { const [{ site_id }] = await db.select({ site_id: sites.id }).from(sites).where(eq(sites.user_id, req.session.user_id)); const signups = await db.select({ email: signupsTable.email, source: sql`COALESCE(source, 'Home')`, created_at: signupsTable.created_at }).from(signupsTable).where(eq(signupsTable.site_id, site_id)).orderBy(desc(signupsTable.created_at)); let result = jsonToCsv(signups); reply.send({ success: true, data: result }); return; } catch (e) { console.log(e); } }); fastify.put("/article", { schema: { body: { type: "object", properties: { id: { type: "string" }, is_public: { type: "boolean" }, content: { type: "string" }, title: { type: "string" }, }, required: ["id"] } } }, async (req, reply) => { const [article] = await db.select(getTableColumns(articles)).from(articles).leftJoin(sites, eq(sites.id, articles.site_id)).where(eq(sites.user_id, req.session.user_id)); if (!article) { reply.status(404).send({ success: false, message: "This article does not exist." }); return; } if (Object.keys(req.body).length > 1) { await db.update(articles).set(JSON.parse(JSON.stringify({ ...req.body, id: undefined }))).where(eq(articles.id, req.body.id)); } reply.send({ success: true }); return; }); fastify.post("/create", { schema: { body: { type: "object", required: ["video_id"], properties: { video_id: { type: "string", }, length: { type: "number", }, format: { type: "string" }, faq: { type: "boolean" }, } }, } }, async (req, reply) => { try { const [{ tokens }] = await db.select({ tokens: users.tokens }).from(users).where(eq(users.id, req.session.user_id)); if (tokens < 1) { reply.send({ success: false, code: "insufficient_tokens", message: "Insufficient tokens" }); return; } const access_token = await getAccessToken(fastify, req); const captions = await getVideoCaptions(access_token, req.body.video_id); const preferred_caption_id = captions.find(x => x.snippet.language == 'en').id; reply.send({ success: true }); const caption_body = await getCaptionText(access_token, preferred_caption_id); const caption_text = parseTextFromCaptions(caption_body).substring(28); const video_data = await getVideoById(access_token, req.body.video_id); const blog_content_json = await createBlogFromCaptions(caption_text, { title: video_data.title, description: video_data.description }, req.body); // TODO: once I add multiple sites per user, this should come from the client const site = await db.select().from(sites).where(eq(sites.user_id, req.session.user_id)); await db.insert(articlesTable).values({ site_id: site[0].id, title: blog_content_json.title, content: blog_content_json.content, meta_title: blog_content_json.meta_title, meta_desc: blog_content_json.meta_desc, excerp: blog_content_json.excerp, source_video_id: req.body.video_id, seo_slug: createArticleSlug(blog_content_json.title), is_public: false }).returning({ id: articlesTable.id }); await db.update(users).set({ tokens: tokens - 1 }).where(eq(users.id, req.session.user_id)); } catch (e) { console.log(e); } }); fastify.post("/recreate", { schema: { body: { type: "object", required: ["video_id"], properties: { video_id: { type: "string", }, length: { type: "number", }, format: { type: "string" }, faq: { type: "boolean" }, } }, } }, async (req, reply) => { }) fastify.put("/website", { schema: { body: { type: "object", properties: { id: { type: "string" }, name: { type: "string" }, primary_color_hex: { type: "string", maxLength: 6, minLength: 6 }, secondary_color_hex: { type: "string", maxLength: 6, minLength: 6 }, text_color_hex: { type: "string", maxLength: 6, minLength: 6 }, use_freebie: { type: "boolean" }, freebie_name: { type: "string" }, freebie_url: { type: "string", format: "uri" }, freebie_image_url: { type: "string", format: "uri" }, freebie_text: { type: "string", }, title: { type: "string", }, subtitle: { type: "string", }, domain: { type: "string" } }, required: ["id"] } }, preValidation: authMiddlewareFn }, async (req, reply) => { try { const [site] = await db.select().from(sites).where(eq(sites.id, req.body.id)); if (site.user_id !== req.session.user_id) { reply.status(403).send({ success: false }); return; } if (env.CERTS_URL && site.domain !== req.body.domain) { await fetch(`http://${env.CERTS_URL}/provision`, { method: "POST", body: JSON.stringify({ domain: req.body.domain }) }); } await db.update(sites).set(req.body); return { success: true } } catch (e) { console.log(e); } }); done(); };