youtuber-blog/src/routes/dashboard.js

402 lines
13 KiB
JavaScript
Raw Normal View History

2024-05-12 20:05:06 +00:00
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
2024-06-07 10:39:11 +00:00
import { and, desc, eq, getTableColumns, sql } from "drizzle-orm";
2024-05-12 20:05:06 +00:00
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";
2024-06-10 19:11:17 +00:00
import { jsonToCsv, createBlogFromCaptions, createArticleSlug, getVideoById, env, getWhisperCaptions, getVideoWithCaptions } from "../utils/index.js";
2024-05-12 20:05:06 +00:00
/**
*
* @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);
2024-05-29 19:35:18 +00:00
const [{ totalArticles }] = await db.select({ totalArticles: sql`count(*)` }).from(articlesTable).where(eq(articlesTable.site_id, site_id));
2024-05-30 10:24:10 +00:00
const [{ totalViews }] = await db.select({ totalViews: sql`COALESCE(sum(${articlesTable.views}), 0)` }).from(articlesTable).where(eq(articlesTable.site_id, site_id));
2024-05-29 19:35:18 +00:00
const [{ totalEmails }] = await db.select({ totalEmails: sql`count(*)` }).from(signupsTable).where(eq(signupsTable.site_id, site_id));
2024-05-12 20:05:06 +00:00
response.send({
success: true,
recentArticles,
2024-05-29 19:35:18 +00:00
recentSignups,
totalArticles,
totalEmails,
totalViews
2024-05-12 20:05:06 +00:00
});
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;
});
2024-06-07 10:39:11 +00:00
fastify.delete("/article", {
schema: {
querystring: {
type: "object",
properties: {
id: {
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(and(eq(sites.user_id, req.session.user_id), eq(articles.id, req.query.id)));
if (!article) {
reply.status(404).send({
success: false,
message: "This article does not exist."
});
return;
}
await db.delete(articles).where(eq(articles.id, article.id));
reply.send({
success: true
});
return;
});
2024-05-12 20:05:06 +00:00
fastify.post("/create", {
schema: {
body: {
type: "object",
required: ["video_id"],
properties: {
video_id: {
type: "string",
},
length: {
type: "number",
},
format: {
type: "string"
},
2024-05-29 19:35:18 +00:00
faq: {
type: "boolean"
},
2024-05-12 20:05:06 +00:00
}
},
}
}, 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;
}
2024-06-10 19:11:17 +00:00
// const access_token = await getAccessToken(fastify, req);
2024-06-09 22:14:41 +00:00
let urlRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|\?v=)([^#\&\?]*).*/;
let match = req.body.video_id.match(urlRegex);
if (!match) {
reply.status(400).send({
success: false,
message: "Invalid Youtube URL"
});
return;
}
// youtube-dl --write-sub --sub-lang en --skip-download URL
2024-06-10 19:11:17 +00:00
const video_data = await getVideoWithCaptions(req.body.video_id);
2024-05-12 20:05:06 +00:00
reply.send({
success: true
});
2024-06-10 19:11:17 +00:00
// const video_data = await getVideoById(access_token, req.body.video_id);
2024-05-29 19:35:18 +00:00
2024-06-10 19:11:17 +00:00
const blog_content_json = await createBlogFromCaptions(video_data.captions, { title: video_data.title, description: video_data.description }, req.body);
2024-05-12 20:05:06 +00:00
// 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,
2024-05-30 13:21:55 +00:00
content: blog_content_json.content,
meta_title: blog_content_json.meta_title,
meta_desc: blog_content_json.meta_desc,
excerp: blog_content_json.excerp,
2024-05-12 20:05:06 +00:00
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
2024-05-29 19:35:18 +00:00
}).where(eq(users.id, req.session.user_id));
2024-05-12 20:05:06 +00:00
} catch (e) {
console.log(e);
}
});
2024-06-02 00:36:39 +00:00
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) => {
})
2024-05-12 20:05:06 +00:00
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
2024-05-29 19:35:18 +00:00
},
use_freebie: {
type: "boolean"
},
freebie_name: {
2024-06-07 19:34:52 +00:00
type: ["string", "null"]
2024-05-29 19:35:18 +00:00
},
freebie_url: {
2024-06-07 19:34:52 +00:00
type: ["string", "null"],
2024-05-29 19:35:18 +00:00
format: "uri"
},
freebie_image_url: {
2024-06-07 19:34:52 +00:00
type: ["string", "null"],
2024-05-29 19:35:18 +00:00
format: "uri"
},
freebie_text: {
2024-06-07 19:34:52 +00:00
type: ["string", "null"]
2024-05-29 19:35:18 +00:00
},
title: {
type: "string",
},
subtitle: {
type: "string",
2024-06-02 00:36:39 +00:00
},
domain: {
2024-06-07 19:34:52 +00:00
type: ["string", "null"]
2024-05-12 20:05:06 +00:00
}
},
required: ["id"]
}
},
preValidation: authMiddlewareFn
}, async (req, reply) => {
2024-05-29 19:35:18 +00:00
try {
const [site] = await db.select().from(sites).where(eq(sites.id, req.body.id));
2024-05-12 20:05:06 +00:00
2024-05-29 19:35:18 +00:00
if (site.user_id !== req.session.user_id) {
reply.status(403).send({
success: false
});
return;
}
2024-05-12 20:05:06 +00:00
if (env.CERTS_URL && site.domain !== req.body.domain) {
2024-06-02 08:32:48 +00:00
await fetch(`http://${env.CERTS_URL}/provision`, {
2024-06-02 00:36:39 +00:00
method: "POST",
2024-06-02 08:30:56 +00:00
body: JSON.stringify({
2024-06-02 00:36:39 +00:00
domain: req.body.domain
2024-06-02 08:30:56 +00:00
})
2024-06-02 00:36:39 +00:00
});
}
2024-06-07 20:00:08 +00:00
const data = structuredClone(req.body.id);
delete data.id;
await db.update(sites).set(data).where(eq(sites.id, req.body.id));
2024-05-12 20:05:06 +00:00
2024-05-29 19:35:18 +00:00
return {
success: true
}
} catch (e) {
console.log(e);
2024-05-12 20:05:06 +00:00
}
});
done();
};
2024-06-09 22:14:41 +00:00
function convertSRV1ToPlainText(transcript) {
// Define regular expression to extract text content
const textRegex = /<text[^>]+>(.*?)<\/text>/g;
// Initialize an empty array to hold the plain text lines
const plainTextLines = [];
// Match text segments using regular expression
let match;
while ((match = textRegex.exec(transcript)) !== null) {
// Extract text content and remove any HTML tags
const textContent = match[1].replace(/<[^>]+>/g, '');
// Add text content to the plain text lines array
plainTextLines.push(textContent);
}
// Join the plain text lines into a single string and return it
return plainTextLines.join('\n');
}