web: added the base for the web project
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { exampleRouter } from "@/server/api/routers/example";
|
||||
import { createTRPCRouter } from "@/server/api/trpc";
|
||||
import { chartMetaRouter } from "./routers/chartMeta";
|
||||
import { chartRouter } from "./routers/chart";
|
||||
import { reportRouter } from "./routers/report";
|
||||
import { organizationRouter } from "./routers/organization";
|
||||
import { userRouter } from "./routers/user";
|
||||
import { projectRouter } from "./routers/project";
|
||||
import { clientRouter } from "./routers/client";
|
||||
import { dashboardRouter } from "./routers/dashboard";
|
||||
|
||||
/**
|
||||
* This is the primary router for your server.
|
||||
@@ -9,9 +13,13 @@ import { reportRouter } from "./routers/report";
|
||||
* All routers added in /api/routers should be manually added here.
|
||||
*/
|
||||
export const appRouter = createTRPCRouter({
|
||||
example: exampleRouter,
|
||||
chartMeta: chartMetaRouter,
|
||||
chart: chartRouter,
|
||||
report: reportRouter,
|
||||
dashboard: dashboardRouter,
|
||||
organization: organizationRouter,
|
||||
user: userRouter,
|
||||
project: projectRouter,
|
||||
client: clientRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
@@ -1,12 +1,131 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import { map, path, pipe, sort, uniq } from "ramda";
|
||||
import { pipe, sort, uniq } from "ramda";
|
||||
import { toDots } from "@/utils/object";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { zChartInput } from "@/utils/validation";
|
||||
import { type IChartBreakdown, type IChartEvent } from "@/types";
|
||||
import { type IChartInput, type IChartEvent } from "@/types";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const chartRouter = createTRPCRouter({
|
||||
events: protectedProcedure
|
||||
.query(async () => {
|
||||
const events = await db.event.findMany({
|
||||
take: 500,
|
||||
distinct: ["name"],
|
||||
});
|
||||
|
||||
return events;
|
||||
}),
|
||||
|
||||
properties: protectedProcedure
|
||||
.input(z.object({ event: z.string() }).optional())
|
||||
.query(async ({ input }) => {
|
||||
const events = await db.event.findMany({
|
||||
take: 500,
|
||||
where: {
|
||||
...(input?.event
|
||||
? {
|
||||
name: input.event,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
const properties = events
|
||||
.reduce((acc, event) => {
|
||||
const properties = event as Record<string, unknown>;
|
||||
const dotNotation = toDots(properties);
|
||||
return [...acc, ...Object.keys(dotNotation)];
|
||||
}, [] as string[])
|
||||
.map((item) => item.replace(/\.([0-9]+)\./g, ".*."))
|
||||
.map((item) => item.replace(/\.([0-9]+)/g, "[*]"));
|
||||
|
||||
return pipe(
|
||||
sort<string>((a, b) => a.length - b.length),
|
||||
uniq,
|
||||
)(properties);
|
||||
}),
|
||||
|
||||
values: protectedProcedure
|
||||
.input(z.object({ event: z.string(), property: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
if (isJsonPath(input.property)) {
|
||||
const events = await db.$queryRawUnsafe<{ value: string }[]>(
|
||||
`SELECT ${selectJsonPath(
|
||||
input.property,
|
||||
)} AS value from events WHERE name = '${
|
||||
input.event
|
||||
}' AND "createdAt" >= NOW() - INTERVAL '30 days'`,
|
||||
);
|
||||
return {
|
||||
values: uniq(events.map((item) => item.value)),
|
||||
};
|
||||
} else {
|
||||
const events = await db.event.findMany({
|
||||
where: {
|
||||
name: input.event,
|
||||
[input.property]: {
|
||||
not: null,
|
||||
},
|
||||
createdAt: {
|
||||
gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 30),
|
||||
},
|
||||
},
|
||||
distinct: input.property as any,
|
||||
select: {
|
||||
[input.property]: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
values: uniq(events.map((item) => item[input.property]!)),
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
chart: protectedProcedure
|
||||
.input(zChartInput)
|
||||
.query(async ({ input: { events, ...input } }) => {
|
||||
const startDate = input.startDate ?? new Date();
|
||||
const endDate = input.endDate ?? new Date();
|
||||
const series: Awaited<ReturnType<typeof getChartData>> = [];
|
||||
for (const event of events) {
|
||||
series.push(
|
||||
...(await getChartData({
|
||||
...input,
|
||||
event,
|
||||
startDate,
|
||||
endDate,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
series: series.sort((a, b) => {
|
||||
const sumA = a.data.reduce((acc, item) => acc + item.count, 0);
|
||||
const sumB = b.data.reduce((acc, item) => acc + item.count, 0);
|
||||
return sumB - sumA;
|
||||
}),
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
function selectJsonPath(property: string) {
|
||||
const jsonPath = property
|
||||
.replace(/^properties\./, "")
|
||||
.replace(/\.\*\./g, ".**.");
|
||||
return `jsonb_path_query(properties, '$.${jsonPath}')`;
|
||||
}
|
||||
|
||||
function isJsonPath(property: string) {
|
||||
return property.startsWith("properties");
|
||||
}
|
||||
|
||||
type ResultItem = {
|
||||
label: string | null;
|
||||
@@ -20,14 +139,14 @@ function propertyNameToSql(name: string) {
|
||||
.split(".")
|
||||
.map((item, index) => (index === 0 ? item : `'${item}'`))
|
||||
.join("->");
|
||||
const findLastOf = "->"
|
||||
const findLastOf = "->";
|
||||
const lastArrow = str.lastIndexOf(findLastOf);
|
||||
if(lastArrow === -1) {
|
||||
if (lastArrow === -1) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
const first = str.slice(0, lastArrow);
|
||||
const last = str.slice(lastArrow + findLastOf.length);
|
||||
return `${first}->>${last}`
|
||||
return `${first}->>${last}`;
|
||||
}
|
||||
|
||||
return name;
|
||||
@@ -41,12 +160,6 @@ function getTotalCount(arr: ResultItem[]) {
|
||||
return arr.reduce((acc, item) => acc + item.count, 0);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: false,
|
||||
},
|
||||
};
|
||||
|
||||
async function getChartData({
|
||||
chartType,
|
||||
event,
|
||||
@@ -55,18 +168,19 @@ async function getChartData({
|
||||
startDate,
|
||||
endDate,
|
||||
}: {
|
||||
chartType: string;
|
||||
event: IChartEvent;
|
||||
breakdowns: IChartBreakdown[];
|
||||
interval: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
}) {
|
||||
const select = [`count(*)::int as count`];
|
||||
} & Omit<IChartInput, 'events'>) {
|
||||
const select = [];
|
||||
const where = [];
|
||||
const groupBy = [];
|
||||
const orderBy = [];
|
||||
|
||||
if (event.segment === "event") {
|
||||
select.push(`count(*)::int as count`);
|
||||
} else {
|
||||
select.push(`count(DISTINCT profile_id)::int as count`);
|
||||
}
|
||||
|
||||
switch (chartType) {
|
||||
case "bar": {
|
||||
orderBy.push("count DESC");
|
||||
@@ -86,10 +200,43 @@ async function getChartData({
|
||||
if (filters.length > 0) {
|
||||
filters.forEach((filter) => {
|
||||
const { name, value } = filter;
|
||||
if (name.includes(".")) {
|
||||
where.push(`${propertyNameToSql(name)} = '${value}'`);
|
||||
} else {
|
||||
where.push(`${name} = '${value}'`);
|
||||
switch (filter.operator) {
|
||||
case "is": {
|
||||
if (name.includes(".*.") || name.endsWith("[*]")) {
|
||||
where.push(
|
||||
`properties @? '$.${name
|
||||
.replace(/^properties\./, "")
|
||||
.replace(/\.\*\./g, "[*].")} ? (${value
|
||||
.map((val) => `@ == "${val}"`)
|
||||
.join(" || ")})'`,
|
||||
);
|
||||
} else {
|
||||
where.push(
|
||||
`${propertyNameToSql(name)} in (${value
|
||||
.map((val) => `'${val}'`)
|
||||
.join(", ")})`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "isNot": {
|
||||
if (name.includes(".*.") || name.endsWith("[*]")) {
|
||||
where.push(
|
||||
`properties @? '$.${name
|
||||
.replace(/^properties\./, "")
|
||||
.replace(/\.\*\./g, "[*].")} ? (${value
|
||||
.map((val) => `@ != "${val}"`)
|
||||
.join(" && ")})'`,
|
||||
);
|
||||
} else if (name.includes(".")) {
|
||||
where.push(
|
||||
`${propertyNameToSql(name)} not in (${value
|
||||
.map((val) => `'${val}'`)
|
||||
.join(", ")})`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -98,7 +245,11 @@ async function getChartData({
|
||||
if (breakdowns.length) {
|
||||
const breakdown = breakdowns[0];
|
||||
if (breakdown) {
|
||||
select.push(`${propertyNameToSql(breakdown.name)} as label`);
|
||||
if (isJsonPath(breakdown.name)) {
|
||||
select.push(`${selectJsonPath(breakdown.name)} as label`);
|
||||
} else {
|
||||
select.push(`${breakdown.name} as label`);
|
||||
}
|
||||
groupBy.push(`label`);
|
||||
}
|
||||
} else {
|
||||
@@ -123,7 +274,7 @@ async function getChartData({
|
||||
ORDER BY ${orderBy.join(", ")}
|
||||
`;
|
||||
|
||||
const result = await db.$queryRawUnsafe<ResultItem[]>(sql);
|
||||
const result = await db.$queryRawUnsafe<ResultItem[]>(sql);
|
||||
|
||||
// group by sql label
|
||||
const series = result.reduce(
|
||||
@@ -166,108 +317,6 @@ async function getChartData({
|
||||
});
|
||||
}
|
||||
|
||||
export const chartMetaRouter = createTRPCRouter({
|
||||
events: protectedProcedure
|
||||
// .input(z.object())
|
||||
.query(async ({ input }) => {
|
||||
const events = await db.event.findMany({
|
||||
take: 500,
|
||||
distinct: ["name"],
|
||||
});
|
||||
|
||||
return events;
|
||||
}),
|
||||
|
||||
properties: protectedProcedure
|
||||
.input(z.object({ event: z.string() }).optional())
|
||||
.query(async ({ input }) => {
|
||||
const events = await db.event.findMany({
|
||||
take: 500,
|
||||
where: {
|
||||
...(input?.event
|
||||
? {
|
||||
name: input.event,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
const properties = events.reduce((acc, event) => {
|
||||
const properties = event as Record<string, unknown>;
|
||||
const dotNotation = toDots(properties);
|
||||
return [...acc, ...Object.keys(dotNotation)];
|
||||
}, [] as string[]);
|
||||
|
||||
return pipe(
|
||||
sort<string>((a, b) => a.length - b.length),
|
||||
uniq,
|
||||
)(properties);
|
||||
}),
|
||||
|
||||
values: protectedProcedure
|
||||
.input(z.object({ event: z.string(), property: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
const events = await db.event.findMany({
|
||||
where: {
|
||||
name: input.event,
|
||||
properties: {
|
||||
path: input.property.split(".").slice(1),
|
||||
not: Prisma.DbNull,
|
||||
},
|
||||
createdAt: {
|
||||
// Take last 30 days
|
||||
gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 30),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const values = uniq(
|
||||
map(path(input.property.split(".")), events),
|
||||
) as string[];
|
||||
|
||||
return {
|
||||
types: uniq(
|
||||
values.map((value) =>
|
||||
Array.isArray(value) ? "array" : typeof value,
|
||||
),
|
||||
),
|
||||
values,
|
||||
};
|
||||
}),
|
||||
|
||||
chart: protectedProcedure
|
||||
.input(zChartInput)
|
||||
.query(
|
||||
async ({
|
||||
input: { chartType, events, breakdowns, interval, ...input },
|
||||
}) => {
|
||||
const startDate = input.startDate ?? new Date();
|
||||
const endDate = input.endDate ?? new Date();
|
||||
const series: Awaited<ReturnType<typeof getChartData>> = [];
|
||||
for (const event of events) {
|
||||
series.push(
|
||||
...(await getChartData({
|
||||
chartType,
|
||||
event,
|
||||
breakdowns,
|
||||
interval,
|
||||
startDate,
|
||||
endDate,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
series: series.sort((a, b) => {
|
||||
const sumA = a.data.reduce((acc, item) => acc + item.count, 0);
|
||||
const sumB = b.data.reduce((acc, item) => acc + item.count, 0);
|
||||
return sumB - sumA;
|
||||
}),
|
||||
};
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
function fillEmptySpotsInTimeline(
|
||||
items: ResultItem[],
|
||||
interval: string,
|
||||
@@ -277,14 +326,14 @@ function fillEmptySpotsInTimeline(
|
||||
const result = [];
|
||||
const clonedStartDate = new Date(startDate);
|
||||
const clonedEndDate = new Date(endDate);
|
||||
if(interval === 'hour') {
|
||||
if (interval === "hour") {
|
||||
clonedStartDate.setMinutes(0, 0, 0);
|
||||
clonedEndDate.setMinutes(0, 0, 0)
|
||||
clonedEndDate.setMinutes(0, 0, 0);
|
||||
} else {
|
||||
clonedStartDate.setHours(2, 0, 0, 0);
|
||||
clonedEndDate.setHours(2, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
while (clonedStartDate.getTime() <= clonedEndDate.getTime()) {
|
||||
const getYear = (date: Date) => date.getFullYear();
|
||||
const getMonth = (date: Date) => date.getMonth();
|
||||
96
apps/web/src/server/api/routers/client.ts
Normal file
96
apps/web/src/server/api/routers/client.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import { hashPassword } from "@/server/services/hash.service";
|
||||
import { randomUUID } from "crypto";
|
||||
import { getOrganizationBySlug } from "@/server/services/organization.service";
|
||||
|
||||
export const clientRouter = createTRPCRouter({
|
||||
list: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
organizationSlug: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const organization = await getOrganizationBySlug(input.organizationSlug);
|
||||
return db.client.findMany({
|
||||
where: {
|
||||
organization_id: organization.id,
|
||||
},
|
||||
include: {
|
||||
project: true,
|
||||
},
|
||||
});
|
||||
}),
|
||||
get: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(({ input }) => {
|
||||
return db.client.findUniqueOrThrow({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(({ input }) => {
|
||||
return db.client.update({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
data: {
|
||||
name: input.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
create: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
projectId: z.string(),
|
||||
organizationSlug: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const organization = await getOrganizationBySlug(input.organizationSlug);
|
||||
const secret = randomUUID();
|
||||
const client = await db.client.create({
|
||||
data: {
|
||||
organization_id: organization.id,
|
||||
project_id: input.projectId,
|
||||
name: input.name,
|
||||
secret: await hashPassword(secret),
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
clientSecret: secret,
|
||||
clientId: client.id,
|
||||
};
|
||||
}),
|
||||
remove: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
await db.client.delete({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
25
apps/web/src/server/api/routers/dashboard.ts
Normal file
25
apps/web/src/server/api/routers/dashboard.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import { getProjectBySlug } from "@/server/services/project.service";
|
||||
|
||||
|
||||
|
||||
export const dashboardRouter = createTRPCRouter({
|
||||
list: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
organizationSlug: z.string(),
|
||||
projectSlug: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input: { projectSlug } }) => {
|
||||
const project = await getProjectBySlug(projectSlug)
|
||||
return db.dashboard.findMany({
|
||||
where: {
|
||||
project_id: project.id,
|
||||
},
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
publicProcedure,
|
||||
} from "@/server/api/trpc";
|
||||
|
||||
export const exampleRouter = createTRPCRouter({
|
||||
hello: publicProcedure
|
||||
.input(z.object({ text: z.string() }))
|
||||
.query(({ input }) => {
|
||||
return {
|
||||
greeting: `Hello ${input.text}`,
|
||||
};
|
||||
}),
|
||||
|
||||
getAll: publicProcedure.query(() => {
|
||||
return []
|
||||
}),
|
||||
|
||||
getSecretMessage: protectedProcedure.query(() => {
|
||||
return "you can now see this secret message!";
|
||||
}),
|
||||
});
|
||||
45
apps/web/src/server/api/routers/organization.ts
Normal file
45
apps/web/src/server/api/routers/organization.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import { getOrganizationBySlug } from "@/server/services/organization.service";
|
||||
|
||||
export const organizationRouter = createTRPCRouter({
|
||||
first: protectedProcedure.query(({ ctx }) => {
|
||||
return db.organization.findFirst({
|
||||
where: {
|
||||
users: {
|
||||
some: {
|
||||
id: ctx.session.user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
get: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
slug: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(({ input }) => {
|
||||
return getOrganizationBySlug(input.slug)
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(({ input }) => {
|
||||
return db.organization.update({
|
||||
where: {
|
||||
slug: input.slug,
|
||||
},
|
||||
data: {
|
||||
name: input.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
});
|
||||
81
apps/web/src/server/api/routers/project.ts
Normal file
81
apps/web/src/server/api/routers/project.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import { getOrganizationBySlug } from "@/server/services/organization.service";
|
||||
|
||||
export const projectRouter = createTRPCRouter({
|
||||
list: protectedProcedure
|
||||
.input(z.object({
|
||||
organizationSlug: z.string()
|
||||
}))
|
||||
.query(async ({ input }) => {
|
||||
const organization = await getOrganizationBySlug(input.organizationSlug)
|
||||
return db.project.findMany({
|
||||
where: {
|
||||
organization_id: organization.id,
|
||||
},
|
||||
});
|
||||
}),
|
||||
get: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(({ input }) => {
|
||||
return db.project.findUniqueOrThrow({
|
||||
where: {
|
||||
id: input.id,
|
||||
organization_id: "d433c614-69f9-443a-8961-92a662869929",
|
||||
},
|
||||
});
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(({ input }) => {
|
||||
return db.project.update({
|
||||
where: {
|
||||
id: input.id,
|
||||
organization_id: "d433c614-69f9-443a-8961-92a662869929",
|
||||
},
|
||||
data: {
|
||||
name: input.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
create: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(({ input }) => {
|
||||
return db.project.create({
|
||||
data: {
|
||||
organization_id: "d433c614-69f9-443a-8961-92a662869929",
|
||||
name: input.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
remove: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
await db.project.delete({
|
||||
where: {
|
||||
id: input.id,
|
||||
organization_id: "d433c614-69f9-443a-8961-92a662869929",
|
||||
},
|
||||
});
|
||||
return true
|
||||
}),
|
||||
});
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
type IChartEvent,
|
||||
} from "@/types";
|
||||
import { type Report as DbReport } from "@prisma/client";
|
||||
import { getProjectBySlug } from "@/server/services/project.service";
|
||||
import { getDashboardBySlug } from "@/server/services/dashboard.service";
|
||||
|
||||
function transform(report: DbReport): IChartInput & { id: string } {
|
||||
return {
|
||||
@@ -40,22 +42,27 @@ export const reportRouter = createTRPCRouter({
|
||||
})
|
||||
.then(transform);
|
||||
}),
|
||||
getDashboard: protectedProcedure
|
||||
list: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
dashboardId: z.string(),
|
||||
projectSlug: z.string(),
|
||||
dashboardSlug: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input: { projectId, dashboardId } }) => {
|
||||
.query(async ({ input: { projectSlug, dashboardSlug } }) => {
|
||||
const project = await getProjectBySlug(projectSlug);
|
||||
const dashboard = await getDashboardBySlug(dashboardSlug);
|
||||
const reports = await db.report.findMany({
|
||||
where: {
|
||||
project_id: projectId,
|
||||
dashboard_id: dashboardId,
|
||||
project_id: project.id,
|
||||
dashboard_id: dashboard.id,
|
||||
},
|
||||
});
|
||||
|
||||
return reports.map(transform);
|
||||
return {
|
||||
reports: reports.map(transform),
|
||||
dashboard,
|
||||
}
|
||||
}),
|
||||
save: protectedProcedure
|
||||
.input(
|
||||
|
||||
67
apps/web/src/server/api/routers/user.ts
Normal file
67
apps/web/src/server/api/routers/user.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
} from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import { hashPassword } from "@/server/services/hash.service";
|
||||
|
||||
export const userRouter = createTRPCRouter({
|
||||
current: protectedProcedure.query(({ ctx }) => {
|
||||
return db.user.findUniqueOrThrow({
|
||||
where: {
|
||||
id: ctx.session.user.id
|
||||
}
|
||||
})
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(({ input, ctx }) => {
|
||||
return db.user.update({
|
||||
where: {
|
||||
id: ctx.session.user.id
|
||||
},
|
||||
data: {
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
}
|
||||
})
|
||||
}),
|
||||
changePassword: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
password: z.string(),
|
||||
oldPassword: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const user = await db.user.findUniqueOrThrow({
|
||||
where: {
|
||||
id: ctx.session.user.id
|
||||
}
|
||||
})
|
||||
|
||||
if(user.password !== input.oldPassword) {
|
||||
throw new Error('Old password is incorrect')
|
||||
}
|
||||
|
||||
if(user.password === input.password) {
|
||||
throw new Error('New password cannot be the same as old password')
|
||||
}
|
||||
|
||||
return db.user.update({
|
||||
where: {
|
||||
id: ctx.session.user.id
|
||||
},
|
||||
data: {
|
||||
password: await hashPassword(input.password),
|
||||
}
|
||||
})
|
||||
}),
|
||||
});
|
||||
@@ -53,7 +53,6 @@ const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||
*/
|
||||
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
|
||||
const { req, res } = opts;
|
||||
|
||||
// Get the session from the server using the getServerSession wrapper function
|
||||
const session = await getServerAuthSession({ req, res });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user