Implement setting a measurement value by api POST
This commit is contained in:
parent
a932321324
commit
bbe612ae43
43
apps/web/app/api/v1/measurements/route.ts
Normal file
43
apps/web/app/api/v1/measurements/route.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { zMeasurementInputSchema } from "@lifetracker/shared/types/metrics";
|
||||
|
||||
import { buildHandler } from "../utils/handler";
|
||||
|
||||
import spacetime from "spacetime";
|
||||
|
||||
export const POST = (req: NextRequest) =>
|
||||
buildHandler({
|
||||
req,
|
||||
bodySchema: zMeasurementInputSchema,
|
||||
handler: async ({ api, body }) => {
|
||||
|
||||
const datetime = spacetime(
|
||||
body?.dateTimeQuery || new Date(),
|
||||
body?.timezone || "Etc/UTC"
|
||||
).goto("Etc/UTC");
|
||||
|
||||
// const dayId = (await api.days.get({
|
||||
// dateQuery: datetime.format("iso-short")
|
||||
// })).id;
|
||||
|
||||
const hour = await api.hours.get({
|
||||
dateQuery: datetime.format("iso-short"),
|
||||
time: datetime.hour(),
|
||||
});
|
||||
|
||||
const obj = {
|
||||
metricName: body!.metricName,
|
||||
hourId: hour.id!,
|
||||
dayId: hour.dayId
|
||||
};
|
||||
|
||||
const measurement =
|
||||
body?.value
|
||||
? await api.measurements.setValue({
|
||||
...obj,
|
||||
value: body.value!,
|
||||
})
|
||||
: await api.measurements.incrementCount(obj);
|
||||
return { status: 201, resp: measurement };
|
||||
},
|
||||
});
|
||||
@ -13,6 +13,13 @@ export const zMeasurementSchema = z.object({
|
||||
});
|
||||
export type ZMeasurement = z.infer<typeof zMeasurementSchema>;
|
||||
|
||||
export const zMeasurementInputSchema = z.object({
|
||||
metricName: z.string(),
|
||||
dateTimeQuery: z.string().nullish(),
|
||||
timezone: z.string().nullish(),
|
||||
value: z.coerce.number().nullish(),
|
||||
});
|
||||
|
||||
export const zMetricSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string(),
|
||||
|
||||
@ -92,15 +92,16 @@ export const hoursAppRouter = router({
|
||||
}))
|
||||
.output(zHourSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const date = dateFromInput({ dateQuery: input.dateQuery });
|
||||
const hourRes = await getHourSelectQuery(ctx, date, input.time,
|
||||
and(eq(hours.time, input.time), eq(days.date, date))
|
||||
);
|
||||
|
||||
return {
|
||||
date: format(date, "yyyy-MM-dd"),
|
||||
...hourRes[0]
|
||||
};
|
||||
const hourRes = await ctx.db.select().from(hours)
|
||||
.leftJoin(days, eq(days.id, hours.dayId))
|
||||
.where(
|
||||
and(
|
||||
eq(hours.time, input.time),
|
||||
eq(days.date, input.dateQuery),
|
||||
eq(hours.userId, ctx.user!.id)
|
||||
)
|
||||
);
|
||||
return hourRes[0].hour;
|
||||
}),
|
||||
update: authedProcedure
|
||||
.input(
|
||||
@ -151,7 +152,8 @@ export const hoursAppRouter = router({
|
||||
};
|
||||
|
||||
if (newProps.comment == "") { newProps.comment = null }
|
||||
|
||||
console.log(input.dayId);
|
||||
console.log(input.hourTime);
|
||||
const hourRes = await ctx.db
|
||||
.update(hours)
|
||||
.set(newProps)
|
||||
|
||||
@ -8,6 +8,23 @@ import { zMetricSchema, zMeasurementSchema } from "@lifetracker/shared/types/met
|
||||
import type { Context } from "../index";
|
||||
import { authedProcedure, router } from "../index";
|
||||
import { zColorSchema } from "@lifetracker/shared/types/colors";
|
||||
import { titleCase } from "title-case";
|
||||
|
||||
const getMetricFromInput = async (ctx: Context, input:
|
||||
{ metricId?: string | null | undefined; metricName?: string | null | undefined; }) => {
|
||||
const metric = input.metricName
|
||||
? await ctx.db.select().from(metrics).where(eq(metrics.name, titleCase(input.metricName)))
|
||||
: await ctx.db.select().from(metrics).where(eq(metrics.id, input.metricId!));
|
||||
|
||||
if (!metric[0]) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Metric not found",
|
||||
});
|
||||
}
|
||||
return metric[0];
|
||||
}
|
||||
|
||||
|
||||
export const measurementsAppRouter = router({
|
||||
list: authedProcedure
|
||||
@ -25,19 +42,23 @@ export const measurementsAppRouter = router({
|
||||
return dbMeasurements;
|
||||
}),
|
||||
setValue: authedProcedure
|
||||
.input(z.object({ metricId: z.string(), hourId: z.string(), dayId: z.string(), value: z.number() }))
|
||||
.input(z.object({
|
||||
metricId: z.string().nullish(), metricName: z.string().nullish(),
|
||||
hourId: z.string(), dayId: z.string(), value: z.number()
|
||||
}))
|
||||
.output(zMeasurementSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const metric = await ctx.db.select().from(metrics).where(eq(metrics.id, input.metricId));
|
||||
if (!metric[0]) {
|
||||
if (!input.metricId && !input.metricName) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Metric not found",
|
||||
code: "BAD_REQUEST",
|
||||
message: "Metric name or id is required",
|
||||
});
|
||||
}
|
||||
const metric = await getMetricFromInput(ctx, input);
|
||||
|
||||
// Check if there is a measurement for this metric in this hour, if so, update it, if not, create it
|
||||
const existingMeasurement = await ctx.db.select().from(measurements).where(and(
|
||||
eq(measurements.metricId, input.metricId),
|
||||
eq(measurements.metricId, metric.id),
|
||||
eq(measurements.hourId, input.hourId),
|
||||
));
|
||||
if (existingMeasurement[0]) {
|
||||
@ -47,12 +68,12 @@ export const measurementsAppRouter = router({
|
||||
|
||||
return {
|
||||
...updatedMeasurement[0],
|
||||
icon: metric[0].icon,
|
||||
metricName: metric[0].name,
|
||||
icon: metric.icon,
|
||||
metricName: metric.name,
|
||||
};
|
||||
} else {
|
||||
const newMeasurement = await ctx.db.insert(measurements).values({
|
||||
metricId: input.metricId,
|
||||
metricId: metric.id,
|
||||
hourId: input.hourId,
|
||||
dayId: input.dayId,
|
||||
value: input.value.toString(),
|
||||
@ -60,16 +81,16 @@ export const measurementsAppRouter = router({
|
||||
}).returning();
|
||||
return {
|
||||
...newMeasurement[0],
|
||||
icon: metric[0].icon,
|
||||
metricName: metric[0].name,
|
||||
icon: metric.icon,
|
||||
metricName: metric.name,
|
||||
};
|
||||
}
|
||||
}),
|
||||
incrementCount: authedProcedure
|
||||
.input(z.object({ metricId: z.string(), hourId: z.string(), dayId: z.string() }))
|
||||
.input(z.object({ metricId: z.string().nullish(), metricName: z.string().nullish(), hourId: z.string(), dayId: z.string() }))
|
||||
.output(zMeasurementSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const metric = await ctx.db.select().from(metrics).where(eq(metrics.id, input.metricId));
|
||||
const metric = await getMetricFromInput(ctx, input);
|
||||
if (!metric[0]) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
@ -144,4 +165,20 @@ export const measurementsAppRouter = router({
|
||||
});
|
||||
}
|
||||
}),
|
||||
timeSinceLastMeasurement: authedProcedure
|
||||
.input(z.object({ metricId: z.string() }))
|
||||
.output(z.number())
|
||||
.query(async ({ input, ctx }) => {
|
||||
const lastMeasurement = await ctx.db.select().from(measurements).where(and(
|
||||
eq(measurements.metricId, input.metricId),
|
||||
eq(measurements.userId, ctx.user.id),
|
||||
)).orderBy(desc(measurements.createdAt)).limit(1);
|
||||
if (lastMeasurement[0]) {
|
||||
const lastMeasurementTime = new Date(lastMeasurement[0].createdAt).getTime();
|
||||
const currentTime = new Date().getTime();
|
||||
return (currentTime - lastMeasurementTime) / 1000;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}),
|
||||
});
|
||||
@ -8,6 +8,7 @@ import { zMetricSchema, zMeasurementSchema } from "@lifetracker/shared/types/met
|
||||
import type { Context } from "../index";
|
||||
import { authedProcedure, router } from "../index";
|
||||
import { zColorSchema } from "@lifetracker/shared/types/colors";
|
||||
import { titleCase } from "title-case";
|
||||
|
||||
export const metricsAppRouter = router({
|
||||
list: authedProcedure
|
||||
@ -29,7 +30,7 @@ export const metricsAppRouter = router({
|
||||
const result = await trx
|
||||
.insert(metrics)
|
||||
.values({
|
||||
name: input.name,
|
||||
name: titleCase(input.name),
|
||||
userId: ctx.user!.id,
|
||||
unit: input.unit ?? null,
|
||||
type: input.type,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user