Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: qrcode api #20

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions prisma/migrations/20241103153144_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"oidcId" TEXT NOT NULL,
"studentId" TEXT NOT NULL,
"firstNameTh" TEXT NOT NULL,
"middleNameTh" TEXT,
"lastNameTh" TEXT NOT NULL,
"firstNameEn" TEXT NOT NULL,
"middleNameEn" TEXT,
"lastNameEn" TEXT NOT NULL,

CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "UserShortenedLink" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"url" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"count" INTEGER NOT NULL DEFAULT 0,
"userId" INTEGER NOT NULL,
"editedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "UserShortenedLink_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "UserShortenedLinkVisitedRecord" (
"id" SERIAL NOT NULL,
"utmCampaignSource" TEXT,
"utmCampaignMedium" TEXT,
"utmCampaignName" TEXT,
"utmCampaignId" TEXT,
"utmCampaignTerm" TEXT,
"utmCampaignContent" TEXT,
"userShortenedLinkId" INTEGER NOT NULL,
"editedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "UserShortenedLinkVisitedRecord_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "UserQrCode" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"url" TEXT NOT NULL,
"qrCode" TEXT NOT NULL,
"color" TEXT NOT NULL,
"logo" TEXT,
"userId" INTEGER NOT NULL,
"editedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "UserQrCode_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "TechMonthStamp" (
"id" SERIAL NOT NULL,
"studentId" TEXT NOT NULL,
"eventId" TEXT NOT NULL,

CONSTRAINT "TechMonthStamp_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_oidcId_key" ON "User"("oidcId");

-- CreateIndex
CREATE UNIQUE INDEX "User_studentId_key" ON "User"("studentId");

-- CreateIndex
CREATE UNIQUE INDEX "UserShortenedLink_slug_key" ON "UserShortenedLink"("slug");

-- AddForeignKey
ALTER TABLE "UserShortenedLink" ADD CONSTRAINT "UserShortenedLink_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "UserShortenedLinkVisitedRecord" ADD CONSTRAINT "UserShortenedLinkVisitedRecord_userShortenedLinkId_fkey" FOREIGN KEY ("userShortenedLinkId") REFERENCES "UserShortenedLink"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "UserQrCode" ADD CONSTRAINT "UserQrCode_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
3 changes: 3 additions & 0 deletions prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default async function ScheduleSection(): Promise<JSX.Element> {
<span className="text-techmonth-magenta">dule</span>
</h2>
<div className="flex w-full flex-col gap-4">
{error
{(error ?? !events)
? "something went wrong..."
: events.map((event, index) => (
<div
Expand Down
2 changes: 1 addition & 1 deletion src/app/(events)/techmonth/stamps/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default async function StampsPage(): Promise<JSX.Element> {
});

const { data: events, error } = await getEvents();
if (error) {
if (error ?? !events) {
return <div>Something went wrong...</div>;
}
const eventMap = new Map(events.map((event) => [event.eventId, event]));
Expand Down
35 changes: 27 additions & 8 deletions src/server/actions/techmonth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export async function login(
cookieStore.set("studentId", studentId);

return {
message: "Successfully logged in",
data: null,
};
}
Expand All @@ -23,23 +24,28 @@ export async function logout(): Promise<ServerActionResponse<null>> {
cookieStore.delete("studentId");

return {
message: "Successfully logged out",
data: null,
};
}

export async function getEvents(): Promise<ServerActionResponse<Event[]>> {
export async function getEvents(): Promise<
ServerActionResponse<Event[] | null>
> {
try {
const events = await directus.request(
readItems("Tech_web_techmonth_event" as never),
);

return {
message: "Successfully fetched events",
data: events as never as Event[],
};
} catch (error) {
return {
error: "Failed to fetch events",
data: [],
message: "Failed to fetch events",
error: error instanceof Error ? error.message : "Unknown error",
data: null,
};
}
}
Expand All @@ -56,19 +62,22 @@ export async function getEventByEventId(

if (events.length === 0) {
return {
error: "Event not found",
message: "Event not found",
error: "Not found",
data: null,
};
}

const event = events[0];

return {
message: "Successfully fetched event",
data: event as never as Event,
};
} catch (error) {
return {
error: "Failed to fetch event",
message: "Failed to fetch event",
error: error instanceof Error ? error.message : "Unknown error",
data: null,
};
}
Expand All @@ -80,18 +89,24 @@ export async function addStamp(
const cookieStore = cookies();
const studentId = cookieStore.get("studentId")?.value;
if (!studentId) {
throw new Error("Student not found");
return {
message: "Not logged in",
error: "Unauthorized",
data: null,
};
}

const { data: event, error } = await getEventByEventId(eventId);
if (error) {
return {
message: "Failed to fetch event",
error: error,
data: null,
};
}
if (!event) {
return {
message: "Event not found",
error: "Invalid Event ID",
data: null,
};
Expand All @@ -102,6 +117,7 @@ export async function addStamp(
const eventDate = new Date(event.date);
if (today.getDate() !== eventDate.getDate()) {
return {
message: "Failed to stamp",
error: "Invalid Event Date",
data: null,
};
Expand All @@ -115,7 +131,8 @@ export async function addStamp(
.catch(() => null);
if (!userStamps) {
return {
error: "Failed to fetch user stamps",
message: "Failed to fetch user stamps",
error: "Unknown error",
data: null,
};
}
Expand All @@ -126,7 +143,8 @@ export async function addStamp(

if (hasAlreadyStamped) {
return {
error: "User already stamped",
message: "User already stamped",
error: "Failed to stamp",
data: null,
};
}
Expand All @@ -137,6 +155,7 @@ export async function addStamp(
});

return {
message: "Successfully stamped",
data: null,
};
}
9 changes: 9 additions & 0 deletions src/server/api/dto/qrcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { z } from "Zod";
export const CreateQRCodeDto = z.object({
name: z.string(),
url: z.string(),
qrCode: z.string(),
color: z.string(),
logo: z.string(),
userId: z.string(),
});
106 changes: 106 additions & 0 deletions src/server/api/routers/qrcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { z } from "zod";

import { createTRPCRouter, publicProcedure } from "@/server/api/trpc";
import { type QRcode } from "@/types/qrcode";
import { CreateQRCodeDto } from "@/server/api/dto/qrcode";

export const postRouter = createTRPCRouter({
create: publicProcedure
.input(CreateQRCodeDto)
.mutation(async ({ ctx, input }) => {
const newQRCode: QRcode = await ctx.db.userQrCode.create({
data: {
name: input.name,
url: input.url,
qrCode: input.qrCode,
color: input.color,
logo: input.logo,
userId: Number(input.userId),
},
});
return {
data: newQRCode,
message: `Created QRcode for ID ${input.userId}`,
};
}),

get: publicProcedure
.input(z.object({ userId: z.string() }))
.query(async ({ ctx, input }) => {
const qrCode = await ctx.db.userQrCode.findFirst({
where: {
userId: Number(input.userId),
},
});

if (qrCode === null) {
return {
data: null,
error: `No QR code found for ID ${input.userId}`,
};
}

return {
data: qrCode,
message: `Updated for ID ${input.userId}`,
};
}),

update: publicProcedure
.input(CreateQRCodeDto)
.mutation(async ({ ctx, input }) => {
const user = await ctx.db.userQrCode.findFirst({
where: {
userId: Number(input.userId),
},
});
const newQRCode = await ctx.db.userQrCode.update({
where: {
id: Number(user?.id),
},
data: {
...input,
userId: Number(input.userId),
editedAt: new Date(),
},
});
if (newQRCode === null) {
return {
data: null,
error: `No QR code found for ID ${input.userId}`,
};
}

return {
data: newQRCode,
message: `Updated QRCode for ${user?.id}`,
};
}),

delete: publicProcedure
.input(z.object({ userId: z.string() }))
.mutation(async ({ ctx, input }) => {
const user = await ctx.db.userQrCode.findFirst({
where: {
userId: Number(input.userId),
},
});
if (user === null) {
return {
data: null,
error: `No QR code found for ID ${input.userId}`,
};
}

await ctx.db.userQrCode.delete({
where: {
id: Number(user?.id),
},
});

return {
data: user,
message: `Deleted QRCode for ${input.userId}`,
};
}),
});
10 changes: 10 additions & 0 deletions src/types/qrcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface QRcode {
name: string;
url: string;
qrCode: string;
color: string;
logo: string | null;
userId: number;
id: number;
editedAt: Date;
}
7 changes: 4 additions & 3 deletions src/types/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type ServerActionResponse<T> = {
error?: string;
export interface ServerActionResponse<T> {
data: T;
};
message?: string;
error?: string;
}