feat(20-01): add tags table, tag service/route, register global-items route

- Create tags table in schema with id, name (unique), createdAt
- Generate migration for tags table
- Create tag.service.ts with getAllTags (id+name, alphabetical order)
- Create tags.ts route with GET / handler
- Register /api/global-items and /api/tags routes in index.ts
- Add auth skip for GET /api/tags and GET /api/global-items
This commit is contained in:
2026-04-06 07:56:40 +02:00
parent 6f07e874f9
commit 2ec1276849
7 changed files with 1190 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
CREATE TABLE "tags" (
"id" serial PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "tags_name_unique" UNIQUE("name")
);

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,13 @@
"when": 1775386658636,
"tag": "0001_tough_boomerang",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1775454835904,
"tag": "0002_square_pyro",
"breakpoints": true
}
]
}

View File

@@ -141,6 +141,14 @@ export const globalItems = pgTable("global_items", {
createdAt: timestamp("created_at").defaultNow().notNull(),
});
// ── Tags ────────────────────────────────────────────────────────────
export const tags = pgTable("tags", {
id: serial("id").primaryKey(),
name: text("name").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
// ── Item Global Links ───────────────────────────────────────────────
export const itemGlobalLinks = pgTable("item_global_links", {

View File

@@ -15,7 +15,9 @@ import { categoryRoutes } from "./routes/categories.ts";
import { imageRoutes } from "./routes/images.ts";
import { itemRoutes } from "./routes/items.ts";
import { oauthRoutes, wellKnownRoute } from "./routes/oauth.ts";
import { globalItemRoutes } from "./routes/global-items.ts";
import { profileRoutes } from "./routes/profiles.ts";
import { tagRoutes } from "./routes/tags.ts";
import { settingsRoutes } from "./routes/settings.ts";
import { setupRoutes } from "./routes/setups.ts";
import { threadRoutes } from "./routes/threads.ts";
@@ -98,6 +100,12 @@ app.use("/api/*", async (c, next) => {
// Skip public setup view (GET /api/setups/:id/public)
if (/^\/api\/setups\/\d+\/public$/.test(c.req.path) && c.req.method === "GET")
return next();
// Skip public tags endpoint (GET /api/tags)
if (c.req.path.startsWith("/api/tags") && c.req.method === "GET")
return next();
// Skip public global-items endpoint (GET /api/global-items)
if (c.req.path.startsWith("/api/global-items") && c.req.method === "GET")
return next();
// All other methods require auth for userId resolution
return requireAuth(c, next);
});
@@ -112,6 +120,8 @@ app.route("/api/settings", settingsRoutes);
app.route("/api/threads", threadRoutes);
app.route("/api/users", profileRoutes);
app.route("/api/setups", setupRoutes);
app.route("/api/global-items", globalItemRoutes);
app.route("/api/tags", tagRoutes);
// MCP server (conditionally mounted)
if (process.env.GEARBOX_MCP !== "false") {

14
src/server/routes/tags.ts Normal file
View File

@@ -0,0 +1,14 @@
import { Hono } from "hono";
import { getAllTags } from "../services/tag.service.ts";
type Env = { Variables: { db?: any } };
const app = new Hono<Env>();
app.get("/", async (c) => {
const db = c.get("db");
const allTags = await getAllTags(db);
return c.json(allTags);
});
export { app as tagRoutes };

View File

@@ -0,0 +1,12 @@
import { asc } from "drizzle-orm";
import { db as prodDb } from "../../db/index.ts";
import { tags } from "../../db/schema.ts";
type Db = typeof prodDb;
export async function getAllTags(db: Db = prodDb) {
return db
.select({ id: tags.id, name: tags.name })
.from(tags)
.orderBy(asc(tags.name));
}