2024-05-12 20:05:06 +00:00
|
|
|
/** @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";
|
2024-06-02 00:36:39 +00:00
|
|
|
import { jsonToCsv, getAccessToken, getVideoCaptions, getCaptionText, parseTextFromCaptions, createBlogFromCaptions, createArticleSlug, getVideoById, env } 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;
|
|
|
|
});
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2024-05-29 19:35:18 +00:00
|
|
|
const video_data = await getVideoById(access_token, req.body.video_id);
|
|
|
|
|
2024-06-02 00:36:39 +00:00
|
|
|
const blog_content_json = await createBlogFromCaptions(caption_text, { 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: {
|
|
|
|
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",
|
2024-06-02 00:36:39 +00:00
|
|
|
},
|
|
|
|
domain: {
|
|
|
|
type: "string"
|
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
|
|
|
|
2024-06-02 00:36:39 +00:00
|
|
|
if (env.CERTS_URL) {
|
2024-06-02 00:38:40 +00:00
|
|
|
const resp = await fetch(`http://${env.CERTS_URL}/provision`, {
|
2024-06-02 00:36:39 +00:00
|
|
|
method: "POST",
|
|
|
|
body: {
|
|
|
|
domain: req.body.domain
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log(await resp.text());
|
|
|
|
}
|
|
|
|
|
2024-05-29 19:35:18 +00:00
|
|
|
await db.update(sites).set(req.body);
|
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();
|
|
|
|
};
|