UNPKG

realworld-hono-drizzle

Version:
853 lines (832 loc) 35.9 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc2) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/cli.ts var import_config = require("dotenv/config"); var import_node_crypto = require("crypto"); var import_node_path = require("path"); var import_node_server = require("@hono/node-server"); var import_libsql2 = require("drizzle-orm/libsql"); var import_migrator = require("drizzle-orm/libsql/migrator"); var import_valibot12 = require("valibot"); // src/factory.ts var import_libsql = require("drizzle-orm/libsql"); var import_factory2 = require("hono/factory"); // src/db/schema.ts var schema_exports = {}; __export(schema_exports, { articleFavoriteTable: () => articleFavoriteTable, articleRelations: () => articleRelations, articleTagRelations: () => articleTagRelations, articleTagTable: () => articleTagTable, articlesTable: () => articlesTable, commentRelations: () => commentRelations, commentsTable: () => commentsTable, tagRelations: () => tagRelations, tagsTable: () => tagsTable, userFollowRelations: () => userFollowRelations, userFollowTable: () => userFollowTable, usersTable: () => usersTable }); var import_drizzle_orm = require("drizzle-orm"); var import_sqlite_core = require("drizzle-orm/sqlite-core"); var date = (0, import_sqlite_core.customType)({ dataType() { return "text"; }, fromDriver(value) { const [date2, time] = value.split(" "); return `${date2}T${time}.000Z`; } }); var usersTable = (0, import_sqlite_core.sqliteTable)("users", { id: (0, import_sqlite_core.int)().primaryKey({ autoIncrement: true }), email: (0, import_sqlite_core.text)().notNull().unique(), username: (0, import_sqlite_core.text)().notNull().unique(), bio: (0, import_sqlite_core.text)(), image: (0, import_sqlite_core.text)(), passwordHash: (0, import_sqlite_core.text)().notNull() }); var userFollowTable = (0, import_sqlite_core.sqliteTable)( "user_follow", { followerId: (0, import_sqlite_core.int)().notNull().references(() => usersTable.id, { onDelete: "cascade" }), followedId: (0, import_sqlite_core.int)().notNull().references(() => usersTable.id, { onDelete: "cascade" }) }, (t) => [(0, import_sqlite_core.primaryKey)({ columns: [t.followerId, t.followedId] })] ); var userFollowRelations = (0, import_drizzle_orm.relations)(userFollowTable, ({ one }) => ({ follower: one(usersTable, { fields: [userFollowTable.followerId], references: [usersTable.id] }), followed: one(usersTable, { fields: [userFollowTable.followedId], references: [usersTable.id] }) })); var tagsTable = (0, import_sqlite_core.sqliteTable)("tags", { tag: (0, import_sqlite_core.text)().notNull().unique() }); var articlesTable = (0, import_sqlite_core.sqliteTable)("articles", { slug: (0, import_sqlite_core.text)().primaryKey(), title: (0, import_sqlite_core.text)().notNull(), description: (0, import_sqlite_core.text)().notNull(), body: (0, import_sqlite_core.text)().notNull(), createdAt: date().notNull().default(import_drizzle_orm.sql`(CURRENT_TIMESTAMP)`), updatedAt: date().notNull().default(import_drizzle_orm.sql`(CURRENT_TIMESTAMP)`), authorId: (0, import_sqlite_core.int)().notNull().references(() => usersTable.id, { onDelete: "cascade" }) }); var commentsTable = (0, import_sqlite_core.sqliteTable)("comments", { id: (0, import_sqlite_core.int)().primaryKey({ autoIncrement: true }), createdAt: date().notNull().default(import_drizzle_orm.sql`(CURRENT_TIMESTAMP)`), updatedAt: date().notNull().default(import_drizzle_orm.sql`(CURRENT_TIMESTAMP)`), body: (0, import_sqlite_core.text)().notNull(), articleSlug: (0, import_sqlite_core.text)().notNull().references(() => articlesTable.slug, { onDelete: "cascade", onUpdate: "cascade" }), authorId: (0, import_sqlite_core.int)().notNull().references(() => usersTable.id, { onDelete: "cascade" }) }); var articleTagTable = (0, import_sqlite_core.sqliteTable)( "article_tag", { articleSlug: (0, import_sqlite_core.text)().notNull().references(() => articlesTable.slug, { onDelete: "cascade", onUpdate: "cascade" }), tag: (0, import_sqlite_core.text)().notNull().references(() => tagsTable.tag, { onDelete: "cascade" }) }, (t) => [(0, import_sqlite_core.primaryKey)({ columns: [t.articleSlug, t.tag] })] ); var articleFavoriteTable = (0, import_sqlite_core.sqliteTable)( "article_favorite", { articleSlug: (0, import_sqlite_core.text)().notNull().references(() => articlesTable.slug, { onDelete: "cascade", onUpdate: "cascade" }), userId: (0, import_sqlite_core.int)().notNull().references(() => usersTable.id, { onDelete: "cascade" }) }, (t) => [(0, import_sqlite_core.primaryKey)({ columns: [t.articleSlug, t.userId] })] ); var articleRelations = (0, import_drizzle_orm.relations)(articlesTable, ({ one, many }) => ({ author: one(usersTable, { fields: [articlesTable.authorId], references: [usersTable.id] }), tagList: many(articleTagTable), commentRelations: many(commentsTable) })); var tagRelations = (0, import_drizzle_orm.relations)(tagsTable, ({ many }) => ({ articles: many(articleTagTable) })); var articleTagRelations = (0, import_drizzle_orm.relations)(articleTagTable, ({ one }) => ({ article: one(articlesTable, { fields: [articleTagTable.articleSlug], references: [articlesTable.slug] }), tag: one(tagsTable, { fields: [articleTagTable.tag], references: [tagsTable.tag] }) })); var commentRelations = (0, import_drizzle_orm.relations)(commentsTable, ({ one }) => ({ article: one(articlesTable, { fields: [commentsTable.articleSlug], references: [articlesTable.slug] }) })); // src/modules/articles/articles.ts var import_valibot_validator = require("@hono/valibot-validator"); var import_slugify = __toESM(require("@sindresorhus/slugify"), 1); var import_drizzle_orm3 = require("drizzle-orm"); var import_hono = require("hono"); var import_jwt2 = require("hono/jwt"); var import_valibot4 = require("valibot"); // src/auth.ts var import_factory = require("hono/factory"); var import_jwt = require("hono/jwt"); var import_valibot = require("valibot"); var JwtClaims = (0, import_valibot.object)({ id: (0, import_valibot.number)() }); var jwtAuth = (0, import_factory.createMiddleware)((c, next) => { const jwtMiddleware = (0, import_jwt.jwt)({ secret: c.env.JWT_SECRET }); return jwtMiddleware(c, next); }); var exposeToken = (0, import_factory.createMiddleware)(async (c, next) => { const token = c.req.header("Authorization")?.split(" ")[1]; c.set("token", token); await next(); }); // src/modules/articles/am-i-following.ts var import_drizzle_orm2 = require("drizzle-orm"); function amIFollowing({ db, me, them }) { return (0, import_drizzle_orm2.exists)( db.select({ exists: import_drizzle_orm2.sql`1` }).from(userFollowTable).where( (0, import_drizzle_orm2.and)( (0, import_drizzle_orm2.eq)(userFollowTable.followerId, me), (0, import_drizzle_orm2.eq)(userFollowTable.followedId, them) ) ) ); } // src/modules/articles/schema.ts var import_valibot3 = require("valibot"); // src/modules/profiles/schema.ts var import_valibot2 = require("valibot"); var Profile = (0, import_valibot2.object)({ username: (0, import_valibot2.string)(), bio: (0, import_valibot2.nullable)((0, import_valibot2.string)()), image: (0, import_valibot2.nullable)((0, import_valibot2.string)()), following: (0, import_valibot2.boolean)() }); var ProfileResponse = (0, import_valibot2.object)({ profile: Profile }); // src/modules/articles/schema.ts var Article = (0, import_valibot3.object)({ slug: (0, import_valibot3.string)(), title: (0, import_valibot3.string)(), description: (0, import_valibot3.string)(), body: (0, import_valibot3.string)(), tagList: (0, import_valibot3.array)((0, import_valibot3.string)()), createdAt: (0, import_valibot3.string)(), updatedAt: (0, import_valibot3.string)(), favorited: (0, import_valibot3.boolean)(), favoritesCount: (0, import_valibot3.number)(), author: Profile }); var Comment = (0, import_valibot3.object)({ id: (0, import_valibot3.number)(), createdAt: (0, import_valibot3.string)(), updatedAt: (0, import_valibot3.string)(), body: (0, import_valibot3.string)(), author: Profile }); var SingleArticleResponse = (0, import_valibot3.object)({ article: Article }); var MultipleArticlesResponse = (0, import_valibot3.object)({ articles: (0, import_valibot3.array)((0, import_valibot3.omit)(Article, ["body"])), articlesCount: (0, import_valibot3.number)() }); var ArticleToCreate = (0, import_valibot3.object)({ article: (0, import_valibot3.object)({ title: (0, import_valibot3.string)(), description: (0, import_valibot3.string)(), body: (0, import_valibot3.string)(), tagList: (0, import_valibot3.optional)((0, import_valibot3.array)((0, import_valibot3.string)())) }) }); var UpdatedArticle = (0, import_valibot3.object)({ article: (0, import_valibot3.partial)( (0, import_valibot3.object)({ title: (0, import_valibot3.string)(), description: (0, import_valibot3.string)(), body: (0, import_valibot3.string)() }) ) }); var SingleCommentResponse = (0, import_valibot3.object)({ comment: Comment }); var MultipleCommentsResponse = (0, import_valibot3.object)({ comments: (0, import_valibot3.array)(Comment) }); var CommentToCreate = (0, import_valibot3.object)({ comment: (0, import_valibot3.object)({ body: (0, import_valibot3.string)() }) }); // src/modules/articles/articles.ts var articlesModule = new import_hono.Hono(); var TagList = (0, import_valibot4.array)((0, import_valibot4.string)()); function isFavorited({ db, articleSlug, me }) { return (0, import_drizzle_orm3.exists)( db.select({ exists: import_drizzle_orm3.sql`1` }).from(articleFavoriteTable).where( (0, import_drizzle_orm3.and)( (0, import_drizzle_orm3.eq)(articleFavoriteTable.articleSlug, articleSlug), (0, import_drizzle_orm3.eq)(articleFavoriteTable.userId, me) ) ) ); } async function findArticle(db, slug, self) { const [article] = await db.select({ ...(0, import_drizzle_orm3.getTableColumns)(articlesTable), favorited: (self === null ? import_drizzle_orm3.sql`0` : isFavorited({ db, articleSlug: articlesTable.slug, me: self.id })).mapWith(Boolean), favoritesCount: (0, import_drizzle_orm3.countDistinct)(articleFavoriteTable.userId).as( "favoritesCount" ), tagList: import_drizzle_orm3.sql`json_group_array(DISTINCT ${articleTagTable.tag}) filter (where ${articleTagTable.tag} is not null)`.mapWith( (tagList) => (0, import_valibot4.parse)(TagList, JSON.parse(tagList)) ), author: { username: usersTable.username, bio: usersTable.bio, image: usersTable.image, following: (self === null ? import_drizzle_orm3.sql`0` : amIFollowing({ db, them: articlesTable.authorId, me: self.id })).mapWith(Boolean) } }).from(articlesTable).leftJoin( articleFavoriteTable, (0, import_drizzle_orm3.eq)(articlesTable.slug, articleFavoriteTable.articleSlug) ).leftJoin( articleTagTable, (0, import_drizzle_orm3.eq)(articlesTable.slug, articleTagTable.articleSlug) ).innerJoin(usersTable, (0, import_drizzle_orm3.eq)(articlesTable.authorId, usersTable.id)).where((0, import_drizzle_orm3.eq)(articlesTable.slug, slug)).groupBy(articlesTable.slug); return article; } articlesModule.get("/", exposeToken, async (c) => { const db = c.get("db"); const token = c.get("token"); const self = token !== void 0 ? (0, import_valibot4.parse)(JwtClaims, (0, import_jwt2.decode)(token).payload) : null; const tagFilter = c.req.query("tag"); const authorFilter = c.req.query("author"); const favoritedFilter = c.req.query("favorited"); const limit = Number(c.req.query("limit") ?? 20); const offset = Number(c.req.query("offset") ?? 0); const { body: _body, authorId: _authorId, ...desiredColumns } = (0, import_drizzle_orm3.getTableColumns)(articlesTable); const slugsWithTagFilter = tagFilter ? db.select({ slug: articleTagTable.articleSlug }).from(articleTagTable).where((0, import_drizzle_orm3.eq)(articleTagTable.tag, tagFilter)).as("slugsWithTagFilter") : null; const slugsWithFavoritedFilter = favoritedFilter ? db.select({ slug: articleFavoriteTable.articleSlug }).from(articleFavoriteTable).innerJoin(usersTable, (0, import_drizzle_orm3.eq)(articleFavoriteTable.userId, usersTable.id)).where((0, import_drizzle_orm3.eq)(usersTable.username, favoritedFilter)).as("slugsWithFavoritedFilter") : null; const articles = await db.select({ ...desiredColumns, favorited: (self === null ? import_drizzle_orm3.sql`0` : isFavorited({ db, articleSlug: articlesTable.slug, me: self.id })).mapWith(Boolean), favoritesCount: (0, import_drizzle_orm3.countDistinct)(articleFavoriteTable.userId).as( "favoritesCount" ), tagList: import_drizzle_orm3.sql`json_group_array(DISTINCT ${articleTagTable.tag}) filter (where ${articleTagTable.tag} is not null)`.mapWith( (tagList) => (0, import_valibot4.parse)(TagList, JSON.parse(tagList)) ), author: { username: usersTable.username, bio: usersTable.bio, image: usersTable.image, following: (self === null ? import_drizzle_orm3.sql`0` : amIFollowing({ db, them: articlesTable.authorId, me: self.id })).mapWith(Boolean) } }).from(articlesTable).leftJoin( articleFavoriteTable, (0, import_drizzle_orm3.eq)(articlesTable.slug, articleFavoriteTable.articleSlug) ).leftJoin( articleTagTable, (0, import_drizzle_orm3.eq)(articlesTable.slug, articleTagTable.articleSlug) ).innerJoin(usersTable, (0, import_drizzle_orm3.eq)(articlesTable.authorId, usersTable.id)).where( (0, import_drizzle_orm3.and)( slugsWithTagFilter ? (0, import_drizzle_orm3.exists)( db.select().from(slugsWithTagFilter).where((0, import_drizzle_orm3.eq)(slugsWithTagFilter.slug, articlesTable.slug)) ) : void 0, authorFilter ? (0, import_drizzle_orm3.eq)(usersTable.username, authorFilter) : void 0, slugsWithFavoritedFilter ? (0, import_drizzle_orm3.exists)( db.select().from(slugsWithFavoritedFilter).where((0, import_drizzle_orm3.eq)(slugsWithFavoritedFilter.slug, articlesTable.slug)) ) : void 0 ) ).limit(limit).offset(offset).groupBy(articlesTable.slug).orderBy((0, import_drizzle_orm3.desc)(articlesTable.createdAt)); return c.json( (0, import_valibot4.parse)(MultipleArticlesResponse, { articles, articlesCount: articles.length }) ); }); articlesModule.get("/feed", jwtAuth, async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const limit = Number(c.req.query("limit") ?? 20); const offset = Number(c.req.query("offset") ?? 0); const { body: _body, authorId: _authorId, ...desiredColumns } = (0, import_drizzle_orm3.getTableColumns)(articlesTable); const articles = await db.select({ ...desiredColumns, favorited: (self === null ? import_drizzle_orm3.sql`0` : isFavorited({ db, articleSlug: articlesTable.slug, me: self.id })).mapWith(Boolean), favoritesCount: (0, import_drizzle_orm3.countDistinct)(articleFavoriteTable.userId).as( "favoritesCount" ), tagList: import_drizzle_orm3.sql`json_group_array(DISTINCT ${articleTagTable.tag}) filter (where ${articleTagTable.tag} is not null)`.mapWith( (tagList) => (0, import_valibot4.parse)(TagList, JSON.parse(tagList)) ), author: { username: usersTable.username, bio: usersTable.bio, image: usersTable.image, following: (self === null ? import_drizzle_orm3.sql`0` : amIFollowing({ db, them: articlesTable.authorId, me: self.id })).mapWith(Boolean) } }).from(articlesTable).leftJoin( articleFavoriteTable, (0, import_drizzle_orm3.eq)(articlesTable.slug, articleFavoriteTable.articleSlug) ).leftJoin( articleTagTable, (0, import_drizzle_orm3.eq)(articlesTable.slug, articleTagTable.articleSlug) ).innerJoin(usersTable, (0, import_drizzle_orm3.eq)(articlesTable.authorId, usersTable.id)).rightJoin( userFollowTable, (0, import_drizzle_orm3.eq)(userFollowTable.followedId, articlesTable.authorId) ).where((0, import_drizzle_orm3.eq)(userFollowTable.followerId, self.id)).limit(limit).offset(offset).groupBy(articlesTable.slug).orderBy((0, import_drizzle_orm3.desc)(articlesTable.createdAt)); return c.json( (0, import_valibot4.parse)(MultipleArticlesResponse, { articles, articlesCount: articles.length }) ); }); articlesModule.get("/:slug", exposeToken, async (c) => { const db = c.get("db"); const token = c.get("token"); const self = token !== void 0 ? (0, import_valibot4.parse)(JwtClaims, (0, import_jwt2.decode)(token).payload) : null; const slug = c.req.param("slug"); const article = await findArticle(db, slug, self); if (article === void 0) { return c.notFound(); } return c.json((0, import_valibot4.parse)(SingleArticleResponse, { article })); }); articlesModule.post( "/", jwtAuth, (0, import_valibot_validator.vValidator)("json", ArticleToCreate), async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const { tagList, ...articlePayload } = c.req.valid("json").article; const slug = (0, import_slugify.default)(articlePayload.title); await db.insert(articlesTable).values({ slug, ...articlePayload, authorId: self.id }); if (tagList !== void 0) { await db.insert(tagsTable).values(tagList.map((tag) => ({ tag }))); await db.insert(articleTagTable).values(tagList.map((tag) => ({ articleSlug: slug, tag }))); } const article = await findArticle(db, slug, self); return c.json((0, import_valibot4.parse)(SingleArticleResponse, { article })); } ); articlesModule.put( "/:slug", jwtAuth, (0, import_valibot_validator.vValidator)("json", UpdatedArticle), async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); let slug = c.req.param("slug"); const { article: articlePayload } = c.req.valid("json"); const [articleOwnership] = await db.select({ isOwned: (0, import_drizzle_orm3.eq)(articlesTable.authorId, self.id) }).from(articlesTable).where((0, import_drizzle_orm3.eq)(articlesTable.slug, slug)); if (articleOwnership === void 0) { return c.notFound(); } if (!articleOwnership.isOwned) { return new Response("Forbidden", { status: 403 }); } await db.update(articlesTable).set(articlePayload).where((0, import_drizzle_orm3.eq)(articlesTable.slug, slug)); if (articlePayload.title !== void 0) { const newSlug = (0, import_slugify.default)(articlePayload.title); await db.update(articlesTable).set({ slug: newSlug }).where((0, import_drizzle_orm3.eq)(articlesTable.slug, slug)); slug = newSlug; } const article = await findArticle(db, slug, self); return c.json((0, import_valibot4.parse)(SingleArticleResponse, { article })); } ); articlesModule.delete("/:slug", jwtAuth, async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const slug = c.req.param("slug"); const [articleOwnership] = await db.select({ isOwned: (0, import_drizzle_orm3.eq)(articlesTable.authorId, self.id) }).from(articlesTable).where((0, import_drizzle_orm3.eq)(articlesTable.slug, slug)); if (articleOwnership === void 0) { return c.notFound(); } if (!articleOwnership.isOwned) { return new Response("Forbidden", { status: 403 }); } await db.delete(articlesTable).where((0, import_drizzle_orm3.eq)(articlesTable.slug, slug)); return new Response(null, { status: 204 }); }); articlesModule.post("/:slug/favorite", jwtAuth, async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const slug = c.req.param("slug"); const [articleExists] = await db.select({ exists: import_drizzle_orm3.sql`1` }).from(articlesTable).where((0, import_drizzle_orm3.eq)(articlesTable.slug, slug)); if (articleExists === void 0) { return c.notFound(); } await db.insert(articleFavoriteTable).values({ articleSlug: slug, userId: self.id }).onConflictDoNothing(); const updatedArticle = await findArticle(db, slug, self); return c.json((0, import_valibot4.parse)(SingleArticleResponse, { article: updatedArticle })); }); articlesModule.delete("/:slug/favorite", jwtAuth, async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const slug = c.req.param("slug"); const [articleExists] = await db.select({ exists: import_drizzle_orm3.sql`1` }).from(articlesTable).where((0, import_drizzle_orm3.eq)(articlesTable.slug, slug)); if (articleExists === void 0) { return c.notFound(); } await db.delete(articleFavoriteTable).where( (0, import_drizzle_orm3.and)( (0, import_drizzle_orm3.eq)(articleFavoriteTable.articleSlug, slug), (0, import_drizzle_orm3.eq)(articleFavoriteTable.userId, self.id) ) ); const updatedArticle = await findArticle(db, slug, self); return c.json((0, import_valibot4.parse)(SingleArticleResponse, { article: updatedArticle })); }); // src/modules/articles/comments.ts var import_drizzle_orm4 = require("drizzle-orm"); var import_hono2 = require("hono"); var import_jwt3 = require("hono/jwt"); var import_valibot5 = require("valibot"); var import_valibot_validator2 = require("@hono/valibot-validator"); var commentsModule = new import_hono2.Hono(); commentsModule.get("/:slug/comments", exposeToken, async (c) => { const db = c.get("db"); const token = c.get("token"); const self = token !== void 0 ? (0, import_valibot5.parse)(JwtClaims, (0, import_jwt3.decode)(token).payload) : null; const slug = c.req.param("slug"); const [articleExists] = await db.select({ exists: import_drizzle_orm4.sql`1` }).from(articlesTable).where((0, import_drizzle_orm4.eq)(articlesTable.slug, slug)); if (!articleExists) { return c.notFound(); } const { authorId: _authorId, articleSlug: _articleSlug, ...desiredColumns } = (0, import_drizzle_orm4.getTableColumns)(commentsTable); const comments = await db.select({ ...desiredColumns, author: { username: usersTable.username, bio: usersTable.bio, image: usersTable.image, following: (self === null ? import_drizzle_orm4.sql`0` : amIFollowing({ db, them: commentsTable.authorId, me: self.id })).mapWith(Boolean) } }).from(commentsTable).innerJoin(usersTable, (0, import_drizzle_orm4.eq)(commentsTable.authorId, usersTable.id)).where((0, import_drizzle_orm4.eq)(commentsTable.articleSlug, slug)); return c.json((0, import_valibot5.parse)(MultipleCommentsResponse, { comments })); }); commentsModule.post( "/:slug/comments", jwtAuth, (0, import_valibot_validator2.vValidator)("json", CommentToCreate), async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const slug = c.req.param("slug"); const [articleExists] = await db.select({ exists: import_drizzle_orm4.sql`1` }).from(articlesTable).where((0, import_drizzle_orm4.eq)(articlesTable.slug, slug)); if (!articleExists) { return c.notFound(); } const { authorId: _authorId, articleSlug: _articleSlug, ...desiredColumns } = (0, import_drizzle_orm4.getTableColumns)(commentsTable); const commentPayload = c.req.valid("json").comment; const [addedComment] = await db.insert(commentsTable).values([{ ...commentPayload, articleSlug: slug, authorId: self.id }]).returning(desiredColumns); if (addedComment === void 0) { throw new Error("Failed to insert a comment"); } const selfProfile = await db.query.usersTable.findFirst({ columns: { username: true, bio: true, image: true }, where: (0, import_drizzle_orm4.eq)(usersTable.id, self.id) }); return c.json( (0, import_valibot5.parse)(SingleCommentResponse, { comment: { ...addedComment, author: { ...selfProfile, following: false } } }) ); } ); commentsModule.delete("/:slug/comments/:id", jwtAuth, async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const slug = c.req.param("slug"); const id = Number.parseInt(c.req.param("id"), 10); const [commentOwnership] = await db.select({ isOwned: (0, import_drizzle_orm4.eq)(commentsTable.authorId, self.id) }).from(commentsTable).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(commentsTable.id, id), (0, import_drizzle_orm4.eq)(commentsTable.articleSlug, slug))); if (commentOwnership === void 0) { return c.notFound(); } if (!commentOwnership.isOwned) { return new Response("Forbidden", { status: 403 }); } await db.delete(commentsTable).where((0, import_drizzle_orm4.eq)(commentsTable.id, id)); return new Response(null, { status: 204 }); }); // src/modules/profiles/profiles.ts var import_drizzle_orm5 = require("drizzle-orm"); var import_hono3 = require("hono"); var import_valibot6 = require("valibot"); var import_jwt4 = require("hono/jwt"); var profilesModule = new import_hono3.Hono(); profilesModule.get("/:username", exposeToken, async (c) => { const db = c.get("db"); const token = c.get("token"); const self = token !== void 0 ? (0, import_valibot6.parse)(JwtClaims, (0, import_jwt4.decode)(token).payload) : null; const user = await db.query.usersTable.findFirst({ where: (0, import_drizzle_orm5.eq)(usersTable.username, c.req.param("username")) }); if (user === void 0) { return c.notFound(); } const following = self !== null ? await db.query.userFollowTable.findFirst({ where: (0, import_drizzle_orm5.and)( (0, import_drizzle_orm5.eq)(userFollowTable.followerId, self.id), (0, import_drizzle_orm5.eq)(userFollowTable.followedId, user.id) ) }) !== void 0 : false; return c.json((0, import_valibot6.parse)(ProfileResponse, { profile: { ...user, following } })); }); profilesModule.post("/:username/follow", jwtAuth, async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const userToFollow = await db.query.usersTable.findFirst({ where: (0, import_drizzle_orm5.eq)(usersTable.username, c.req.param("username")) }); if (userToFollow === void 0) { return c.notFound(); } await db.insert(userFollowTable).values({ followerId: self.id, followedId: userToFollow.id }); return c.json( (0, import_valibot6.parse)(ProfileResponse, { profile: { ...userToFollow, following: true } }) ); }); profilesModule.delete("/:username/follow", jwtAuth, async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const userToUnfollow = await db.query.usersTable.findFirst({ where: (0, import_drizzle_orm5.eq)(usersTable.username, c.req.param("username")) }); if (userToUnfollow === void 0) { return c.notFound(); } await db.delete(userFollowTable).where( (0, import_drizzle_orm5.and)( (0, import_drizzle_orm5.eq)(userFollowTable.followerId, self.id), (0, import_drizzle_orm5.eq)(userFollowTable.followedId, userToUnfollow.id) ) ); return c.json( (0, import_valibot6.parse)(ProfileResponse, { profile: { ...userToUnfollow, following: false } }) ); }); // src/modules/tags/tags.ts var import_hono4 = require("hono"); var import_valibot8 = require("valibot"); // src/modules/tags/schema.ts var import_valibot7 = require("valibot"); var ListOfTags = (0, import_valibot7.object)({ tags: (0, import_valibot7.array)((0, import_valibot7.string)()) }); // src/modules/tags/tags.ts var tagsModule = new import_hono4.Hono(); tagsModule.get("/", async (c) => { const db = c.get("db"); const tags = await db.query.tagsTable.findMany(); return c.json((0, import_valibot8.parse)(ListOfTags, { tags: tags.map(({ tag }) => tag) })); }); // src/modules/users/user.ts var import_valibot_validator3 = require("@hono/valibot-validator"); var import_drizzle_orm6 = require("drizzle-orm"); var import_hono5 = require("hono"); var import_valibot10 = require("valibot"); // src/modules/users/schema.ts var import_valibot9 = require("valibot"); var LoginCredentials = (0, import_valibot9.object)({ user: (0, import_valibot9.object)({ email: (0, import_valibot9.pipe)((0, import_valibot9.string)(), (0, import_valibot9.email)()), password: (0, import_valibot9.string)() }) }); var RegistrationDetails = (0, import_valibot9.object)({ user: (0, import_valibot9.object)({ username: (0, import_valibot9.string)(), email: (0, import_valibot9.pipe)((0, import_valibot9.string)(), (0, import_valibot9.email)()), password: (0, import_valibot9.string)() }) }); var UpdatedDetails = (0, import_valibot9.object)({ user: (0, import_valibot9.partial)( (0, import_valibot9.object)({ username: (0, import_valibot9.string)(), email: (0, import_valibot9.pipe)((0, import_valibot9.string)(), (0, import_valibot9.email)()), password: (0, import_valibot9.string)(), bio: (0, import_valibot9.nullable)((0, import_valibot9.string)()), image: (0, import_valibot9.nullable)((0, import_valibot9.pipe)((0, import_valibot9.string)(), (0, import_valibot9.url)())) }) ) }); var UserResponse = (0, import_valibot9.object)({ user: (0, import_valibot9.object)({ email: (0, import_valibot9.string)(), token: (0, import_valibot9.string)(), username: (0, import_valibot9.string)(), bio: (0, import_valibot9.nullable)((0, import_valibot9.string)()), image: (0, import_valibot9.nullable)((0, import_valibot9.string)()) }) }); // src/modules/users/user.ts var userModule = new import_hono5.Hono().use(jwtAuth).use(exposeToken); userModule.get("/", async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const user = await db.query.usersTable.findFirst({ where: (0, import_drizzle_orm6.eq)(usersTable.id, self.id) }); if (user === void 0) { return c.notFound(); } return c.json( (0, import_valibot10.parse)(UserResponse, { user: { ...user, token: c.get("token") } }) ); }); userModule.put("/", (0, import_valibot_validator3.vValidator)("json", UpdatedDetails), async (c) => { const db = c.get("db"); const self = c.get("jwtPayload"); const user = await db.query.usersTable.findFirst({ where: (0, import_drizzle_orm6.eq)(usersTable.id, self.id) }); if (user === void 0) { return c.notFound(); } const requestData = c.req.valid("json"); const [updatedUser] = await db.update(usersTable).set(requestData.user).where((0, import_drizzle_orm6.eq)(usersTable.id, self.id)).returning(); return c.json( (0, import_valibot10.parse)(UserResponse, { user: { ...updatedUser, token: c.get("token") } }) ); }); // src/modules/users/users.ts var import_valibot_validator4 = require("@hono/valibot-validator"); var import_bcryptjs = __toESM(require("bcryptjs"), 1); var import_drizzle_orm7 = require("drizzle-orm"); var import_hono6 = require("hono"); var import_jwt5 = require("hono/jwt"); var import_valibot11 = require("valibot"); var usersModule = new import_hono6.Hono(); usersModule.post("/login", (0, import_valibot_validator4.vValidator)("json", LoginCredentials), async (c) => { const db = c.get("db"); const requestData = c.req.valid("json"); const user = await db.query.usersTable.findFirst({ where: (0, import_drizzle_orm7.eq)(usersTable.email, requestData.user.email) }); const isMatch = user !== void 0 && await import_bcryptjs.default.compare(requestData.user.password, user.passwordHash); if (!isMatch) { return c.json({ errors: { password: ["invalid for this email"] } }, 422); } const token = await (0, import_jwt5.sign)((0, import_valibot11.parse)(JwtClaims, user), c.env.JWT_SECRET); return c.json((0, import_valibot11.parse)(UserResponse, { user: { ...user, token } })); }); usersModule.post("/", (0, import_valibot_validator4.vValidator)("json", RegistrationDetails), async (c) => { const db = c.get("db"); const requestData = c.req.valid("json"); const existingUser = await db.query.usersTable.findFirst({ where: (0, import_drizzle_orm7.or)( (0, import_drizzle_orm7.eq)(usersTable.email, requestData.user.email), (0, import_drizzle_orm7.eq)(usersTable.username, requestData.user.username) ) }); if (existingUser?.email === requestData.user.email) { return c.json({ errors: { email: ["already in use"] } }, 422); } if (existingUser?.username === requestData.user.username) { return c.json({ errors: { username: ["already in use"] } }, 422); } const passwordHash = await import_bcryptjs.default.hash(requestData.user.password, 10); const [user] = await db.insert(usersTable).values({ email: requestData.user.email, username: requestData.user.username, passwordHash }).returning(); const token = await (0, import_jwt5.sign)((0, import_valibot11.parse)(JwtClaims, user), c.env.JWT_SECRET); return c.json((0, import_valibot11.parse)(UserResponse, { user: { ...user, token } })); }); // src/factory.ts var factory = (0, import_factory2.createFactory)({ initApp(app2) { app2.use(async (c, next) => { const db = (0, import_libsql.drizzle)(c.env.DATABASE_URL, { schema: schema_exports }); c.set("db", db); await next(); }); app2.route("/api/users", usersModule); app2.route("/api/user", userModule); app2.route("/api/profiles", profilesModule); app2.route("/api/articles", articlesModule); app2.route("/api/articles", commentsModule); app2.route("/api/tags", tagsModule); } }); // src/cli.ts var Environment = (0, import_valibot12.object)({ DATABASE_URL: (0, import_valibot12.optional)((0, import_valibot12.string)(), "file:local.db"), JWT_SECRET: (0, import_valibot12.optional)((0, import_valibot12.string)(), (0, import_node_crypto.randomBytes)(64).toString("base64url")), PORT: (0, import_valibot12.pipe)((0, import_valibot12.optional)((0, import_valibot12.string)(), "3000"), (0, import_valibot12.transform)(Number.parseInt), (0, import_valibot12.number)()) }); var env = (0, import_valibot12.parse)(Environment, process.env); var app = factory.createApp(); console.log(`Server is running on http://localhost:${env.PORT}`); (0, import_migrator.migrate)((0, import_libsql2.drizzle)(env.DATABASE_URL), { migrationsFolder: (0, import_node_path.join)(__dirname, "../src/db/migrations") }).then( () => (0, import_node_server.serve)({ fetch(request, httpBindings) { return app.fetch(request, { ...env, ...httpBindings }); }, port: env.PORT }) ); //# sourceMappingURL=cli.cjs.map