Skip to content

Commit

Permalink
taxonomy management
Browse files Browse the repository at this point in the history
  • Loading branch information
hieuhani committed Mar 25, 2024
1 parent b771004 commit 682bcc3
Show file tree
Hide file tree
Showing 21 changed files with 288 additions and 1 deletion.
3 changes: 2 additions & 1 deletion apps/api/src/admin/tag-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import z from "zod";
import { type AppEnv } from "../global";
import { useCurrentAppUser } from "../user";
import { zValidator } from "@hono/zod-validator";
import { createTag, deleteTagById, getTagById, updateTag } from "@publiz/core";
import { createTag, deleteTagById, updateTag } from "@publiz/core";

export const adminTagRouter = new Hono<AppEnv>();

const createTagSchema = z.object({
name: z.string().min(1).max(100),
slug: z.string().min(1).max(100),
parentId: z.number().optional(),
taxonomyId: z.number().optional(),
});

adminTagRouter.post(
Expand Down
58 changes: 58 additions & 0 deletions apps/api/src/admin/taxonomy-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Hono } from "hono";
import z from "zod";
import { type AppEnv } from "../global";
import { useCurrentAppUser } from "../user";
import { zValidator } from "@hono/zod-validator";
import {
createTaxonomy,
deleteTaxonomyById,
updateTaxonomy,
} from "@publiz/core";

export const adminTaxonomyRouter = new Hono<AppEnv>();

const createTaxonomySchema = z.object({
name: z.string().min(1).max(100),
slug: z.string().min(1).max(100),
});

adminTaxonomyRouter.post(
"/",
useCurrentAppUser({ required: true }),
zValidator("json", createTaxonomySchema),
async (c) => {
const payload = c.req.valid("json");
const currentUser = c.get("currentAppUser");
const container = c.get("container");
const tag = await createTaxonomy(container, {
...payload,
type: "SYSTEM",
userId: currentUser.id,
});
return c.json({ data: tag }, 201);
}
);

adminTaxonomyRouter.delete(
"/:id",
useCurrentAppUser({ required: true }),
async (c) => {
const container = c.get("container");
const id = c.req.param("id");
await deleteTaxonomyById(container, +id);
return c.body(null, 204);
}
);

adminTaxonomyRouter.put(
"/:id",
useCurrentAppUser({ required: true }),
zValidator("json", createTaxonomySchema),
async (c) => {
const container = c.get("container");
const payload = c.req.valid("json");
const id = c.req.param("id");
const updatedTag = await updateTaxonomy(container, +id, payload);
return c.json({ data: updatedTag });
}
);
6 changes: 6 additions & 0 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { myOrganizationRouter, organizationRouter } from "./organization";
import { metaSchemaRouter } from "./meta-schema";
import { postRouter } from "./post";
import { adminPostRouter } from "./admin/post-router";
import { adminTaxonomyRouter } from "./admin/taxonomy-router";
import { taxonomyRouter } from "./taxonomy";

const app = new Hono<AppEnv>();
const corsMiddleware = cors(config.cors);
Expand Down Expand Up @@ -48,6 +50,9 @@ app.route("/api/v1/users", userRouter);
app.route("/api/v1/tags", tagRouter);
app.route("/api/v1/my_tags", myTagRouter);

// taxonomies api
app.route("/api/v1/taxonomies", taxonomyRouter);

// meta schemas api
app.route("/api/v1/meta_schemas", metaSchemaRouter);

Expand All @@ -69,6 +74,7 @@ app.route("/admin/api/v1/organizations", adminOrganizationRouter);
app.route("/admin/api/v1/meta_schemas", adminMetaSchemaRouter);
app.route("/admin/api/v1/users", adminUserRouter);
app.route("/admin/api/v1/posts", adminPostRouter);
app.route("/admin/api/v1/taxonomies", adminTaxonomyRouter);
app.onError(globalErrorHandler);

export default app;
14 changes: 14 additions & 0 deletions apps/api/src/organization/my-organization-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
createFile,
getFileUrl,
patchOrganizationMetadataById,
deletePost,
} from "@publiz/core";
import { useCurrentAppUser } from "../user";
import { useCheckOrganizationUser } from "./middleware";
Expand Down Expand Up @@ -123,6 +124,19 @@ myOrganizationRouter.put(
}
);

myOrganizationRouter.delete(
"/:organization_id/posts/:id",
zValidator("json", createPostSchema),
useCurrentAppUser({ required: true }),
useCheckOrganizationUser(),
async (c) => {
const id = c.req.param("id");
const container = c.get("container");
await deletePost(container, +id);
return c.body(null, 204);
}
);

myOrganizationRouter.get(
"/:organization_id/posts/:id",
useCurrentAppUser({ required: true }),
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/tag/my-tag-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const createTagSchema = z.object({
name: z.string().min(1).max(100),
slug: z.string().min(1).max(100),
parentId: z.number().optional(),
taxonomyId: z.number().optional(),
});

myTagRouter.post(
Expand Down Expand Up @@ -48,6 +49,7 @@ const updateTagSchema = z.object({
name: z.string().min(1).max(100),
slug: z.string().min(1).max(100),
parentId: z.number().optional(),
taxonomyId: z.number().optional(),
});

myTagRouter.put(
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/taxonomy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./router";
20 changes: 20 additions & 0 deletions apps/api/src/taxonomy/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Hono } from "hono";
import { type AppEnv } from "../global";
import { findSystemTaxonomies, getTaxonomyById } from "@publiz/core";
import { findTagsByTaxonomyId } from "@publiz/sqldb";

export const taxonomyRouter = new Hono<AppEnv>();

taxonomyRouter.get("/", async (c) => {
const container = c.get("container");
const tags = await findSystemTaxonomies(container);
return c.json({ data: tags });
});

taxonomyRouter.get("/:identity/tags", async (c) => {
const container = c.get("container");
const identity = c.req.param("identity");
const taxonomy = await getTaxonomyById(container, identity);
const tags = await findTagsByTaxonomyId(container.sqlDb, taxonomy.id);
return c.json({ data: tags });
});
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from "./organization-role";
export * from "./organization-user";
export * from "./meta-schema";
export * from "./comment";
export * from "./taxonomy";
4 changes: 4 additions & 0 deletions packages/core/src/post/usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,7 @@ export const bulkCreatePosts = async (
return createPostCrudRepository(trx).createMulti(records);
});
};

export const deletePost = async (container: Container, id: number) => {
return createPostCrudRepository(container.sqlDb).delete(id);
};
8 changes: 8 additions & 0 deletions packages/core/src/tag/usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createTagCrudRepository,
getTagByIdAndUserId,
findSystemTags as findSystemTagsRepo,
findTagsByTaxonomyId as findTagsByTaxonomyIdRepo,
} from "@publiz/sqldb";
import { Container } from "../container";

Expand Down Expand Up @@ -42,3 +43,10 @@ export const findSystemTags = async (container: Container) =>

export const findTagsByIds = async (container: Container, ids: number[]) =>
createTagCrudRepository(container.sqlDb).findByIds(ids);

export const findTagsByTaxonomyId = async (
container: Container,
taxonomyId: number
) => {
return findTagsByTaxonomyIdRepo(container.sqlDb, taxonomyId);
};
2 changes: 2 additions & 0 deletions packages/core/src/taxonomy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./usecase";
export * from "./model";
3 changes: 3 additions & 0 deletions packages/core/src/taxonomy/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { type TaxonomyRow } from "@publiz/sqldb";

export type Taxonomy = TaxonomyRow;
44 changes: 44 additions & 0 deletions packages/core/src/taxonomy/usecase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
type InsertableTaxonomyRow,
type UpdateableTaxonomyRow,
createTaxonomyCrudRepository,
findSystemTaxonomies as findSystemTaxonomiesRepo,
getTaxonomyBySlug as getTaxonomyBySlugRepo,
} from "@publiz/sqldb";
import { Container } from "../container";

type CreateTaxonomyInput = InsertableTaxonomyRow;

export const createTaxonomy = async (
container: Container,
input: CreateTaxonomyInput
) => createTaxonomyCrudRepository(container.sqlDb).create(input);

type UpdateTaxonomyInput = UpdateableTaxonomyRow;

export const updateTaxonomy = async (
container: Container,
id: number,
input: UpdateTaxonomyInput
) => createTaxonomyCrudRepository(container.sqlDb).update(id, input);

export const getTaxonomyById = async (
container: Container,
idOrSlug: number | string
) => {
if (Number.isInteger(Number(idOrSlug))) {
return createTaxonomyCrudRepository(container.sqlDb).findById(+idOrSlug);
}
return getTaxonomyBySlugRepo(container.sqlDb, idOrSlug + "");
};

export const deleteTaxonomyById = async (container: Container, id: number) =>
createTaxonomyCrudRepository(container.sqlDb).delete(id);

export const findSystemTaxonomies = async (container: Container) =>
findSystemTaxonomiesRepo(container.sqlDb);

export const findTaxonomiesByIds = async (
container: Container,
ids: number[]
) => createTaxonomyCrudRepository(container.sqlDb).findByIds(ids);
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { withTimestamps } from "../sql";
import { sql, type Kysely } from "kysely";

export async function up(db: Kysely<any>) {
await db.schema
.createType("taxonomy_type")
.asEnum(["SYSTEM", "DEFAULT"])
.execute();

await db.schema
.createTable("taxonomies")
.addColumn("id", "serial", (col) => col.primaryKey())
.addColumn("name", "varchar(512)", (col) => col.notNull())
.addColumn("slug", "varchar(512)", (col) => col.notNull().unique())
.addColumn("type", sql`"taxonomy_type"`, (col) =>
col.notNull().defaultTo("DEFAULT")
)
.addColumn("organization_id", "integer", (col) =>
col.references("organizations.id")
)
.addColumn("user_id", "integer", (col) =>
col.references("users.id").notNull()
)
.$call(withTimestamps)
.execute();

await db.schema
.createIndex("taxonomies_organization_id")
.on("taxonomies")
.columns(["organization_id"])
.execute();

await db.schema
.alterTable("tags")
.addColumn("taxonomy_id", "integer")
.execute();

await db.schema
.createIndex("tags_taxonomy_id")
.on("tags")
.columns(["taxonomy_id"])
.execute();
}

export async function down(db: Kysely<any>) {
await db.schema.dropType("taxonomy_type").execute();
await db.schema.dropIndex("tags_taxonomy_id").on("tags").execute();
await db.schema.alterTable("tags").dropColumn("taxonomy_id").execute();
await db.schema
.dropIndex("taxonomies_organization_id")
.on("taxonomies")
.execute();
await db.schema.dropTable("taxonomies").execute();
}
2 changes: 2 additions & 0 deletions packages/sqldb/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { OrganizationRoleTable } from "./organization-role";
import { OrganizationUserTable } from "./organization-user";
import { MetaSchemaTable } from "./meta-schema";
import { CommentTable } from "./comment";
import { TaxonomyTable } from "./taxonomy";

export interface Database {
users: UserTable;
Expand All @@ -21,6 +22,7 @@ export interface Database {
organizations_users: OrganizationUserTable;
meta_schemas: MetaSchemaTable;
comments: CommentTable;
taxonomies: TaxonomyTable;
}

export const createDatabase = (dialect: Dialect) => {
Expand Down
1 change: 1 addition & 0 deletions packages/sqldb/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from "./organization-role";
export * from "./organization-user";
export * from "./meta-schema";
export * from "./comment";
export * from "./taxonomy";
1 change: 1 addition & 0 deletions packages/sqldb/src/tag/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type TagTable = {
type: TagType;
organizationId?: number;
parentId?: number;
taxonomyId?: number;
userId: number;
};

Expand Down
11 changes: 11 additions & 0 deletions packages/sqldb/src/tag/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ export const findTagsByUserId = async (db: SqlDatabase, userId: number) => {
.execute();
};

export const findTagsByTaxonomyId = async (
db: SqlDatabase,
taxonomyId: number
) => {
return db
.selectFrom("tags")
.selectAll()
.where("taxonomyId", "=", taxonomyId)
.execute();
};

export const findTagsByOrganizationId = async (
db: SqlDatabase,
organizationId: number
Expand Down
2 changes: 2 additions & 0 deletions packages/sqldb/src/taxonomy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./model";
export * from "./repository";
16 changes: 16 additions & 0 deletions packages/sqldb/src/taxonomy/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Generated, Insertable, Selectable, Updateable } from "kysely";

export type TaxonomyType = "SYSTEM" | "DEFAULT";

export type TaxonomyTable = {
id: Generated<number>;
name: string;
slug: string;
type: TaxonomyType;
organizationId?: number;
userId: number;
};

export type TaxonomyRow = Selectable<TaxonomyTable>;
export type InsertableTaxonomyRow = Insertable<TaxonomyTable>;
export type UpdateableTaxonomyRow = Updateable<TaxonomyTable>;
Loading

0 comments on commit 682bcc3

Please sign in to comment.