realworld-hono-drizzle
Version:
A RealWorld backend built with Hono and Drizzle
853 lines (832 loc) • 35.9 kB
JavaScript
#!/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