feat: feedback form
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { authRouter } from "./routers/auth";
|
||||
import { boardRouter } from "./routers/board";
|
||||
import { cardRouter } from "./routers/card";
|
||||
import { feedbackRouter } from "./routers/feedback";
|
||||
import { importRouter } from "./routers/import";
|
||||
import { labelRouter } from "./routers/label";
|
||||
import { listRouter } from "./routers/list";
|
||||
@@ -13,6 +14,7 @@ export const appRouter = createTRPCRouter({
|
||||
auth: authRouter,
|
||||
board: boardRouter,
|
||||
card: cardRouter,
|
||||
feedback: feedbackRouter,
|
||||
label: labelRouter,
|
||||
list: listRouter,
|
||||
member: memberRouter,
|
||||
|
||||
41
packages/api/src/routers/feedback.ts
Normal file
41
packages/api/src/routers/feedback.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import * as feedbackRepo from "@kan/db/repository/feedback.repo";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
export const feedbackRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.meta({ enabled: false })
|
||||
.input(
|
||||
z.object({
|
||||
feedback: z.string().min(1),
|
||||
url: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.output(z.object({ success: z.boolean() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = ctx.user?.id;
|
||||
|
||||
if (!userId)
|
||||
throw new TRPCError({
|
||||
message: `User not authenticated`,
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const result = await feedbackRepo.create(ctx.adminDb, {
|
||||
feedback: input.feedback,
|
||||
createdBy: userId,
|
||||
url: input.url,
|
||||
});
|
||||
|
||||
if (!result?.id)
|
||||
throw new TRPCError({
|
||||
message: `Unable to create workspace`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
});
|
||||
15
packages/db/migrations/0006_spicy_mach_iv.sql
Normal file
15
packages/db/migrations/0006_spicy_mach_iv.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE IF NOT EXISTS "feedback" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"feedback" text NOT NULL,
|
||||
"createdBy" uuid NOT NULL,
|
||||
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||
"updatedAt" timestamp,
|
||||
"url" text NOT NULL,
|
||||
"reviewed" boolean DEFAULT false NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "feedback" ADD CONSTRAINT "feedback_createdBy_user_id_fk" FOREIGN KEY ("createdBy") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
1650
packages/db/migrations/meta/0006_snapshot.json
Normal file
1650
packages/db/migrations/meta/0006_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,13 @@
|
||||
"when": 1737130330536,
|
||||
"tag": "0005_modern_gideon",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1738149668712,
|
||||
"tag": "0006_spicy_mach_iv",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
25
packages/db/src/repository/feedback.repo.ts
Normal file
25
packages/db/src/repository/feedback.repo.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { SupabaseClient } from "@supabase/supabase-js";
|
||||
|
||||
import type { Database } from "@kan/db/types/database.types";
|
||||
|
||||
export const create = async (
|
||||
db: SupabaseClient<Database>,
|
||||
feedbackInput: {
|
||||
feedback: string;
|
||||
createdBy: string;
|
||||
url: string;
|
||||
},
|
||||
) => {
|
||||
const { data } = await db
|
||||
.from("feedback")
|
||||
.insert({
|
||||
feedback: feedbackInput.feedback,
|
||||
createdBy: feedbackInput.createdBy,
|
||||
url: feedbackInput.url,
|
||||
})
|
||||
.select(`id`)
|
||||
.limit(1)
|
||||
.single();
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
bigserial,
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
pgEnum,
|
||||
@@ -465,3 +466,22 @@ export const slugs = pgTable("workspace_slugs", {
|
||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||
type: slugTypeEnum("type").notNull(),
|
||||
});
|
||||
|
||||
export const feedback = pgTable("feedback", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
feedback: text("feedback").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
url: text("url").notNull(),
|
||||
reviewed: boolean("reviewed").default(false).notNull(),
|
||||
});
|
||||
|
||||
export const feedbackRelations = relations(feedback, ({ one }) => ({
|
||||
createdBy: one(users, {
|
||||
fields: [feedback.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -394,6 +394,44 @@ export type Database = {
|
||||
},
|
||||
]
|
||||
}
|
||||
feedback: {
|
||||
Row: {
|
||||
createdAt: string
|
||||
createdBy: string
|
||||
feedback: string
|
||||
id: number
|
||||
reviewed: boolean
|
||||
updatedAt: string | null
|
||||
url: string
|
||||
}
|
||||
Insert: {
|
||||
createdAt?: string
|
||||
createdBy: string
|
||||
feedback: string
|
||||
id?: number
|
||||
reviewed?: boolean
|
||||
updatedAt?: string | null
|
||||
url: string
|
||||
}
|
||||
Update: {
|
||||
createdAt?: string
|
||||
createdBy?: string
|
||||
feedback?: string
|
||||
id?: number
|
||||
reviewed?: boolean
|
||||
updatedAt?: string | null
|
||||
url?: string
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "feedback_createdBy_user_id_fk"
|
||||
columns: ["createdBy"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
import: {
|
||||
Row: {
|
||||
createdAt: string
|
||||
|
||||
Reference in New Issue
Block a user