Skip to content

Commit

Permalink
feat(web): add database level request loggin (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
cstrnt authored Sep 1, 2023
1 parent 69756fd commit f0c99c4
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- CreateTable
CREATE TABLE `ApiRequest` (
`id` VARCHAR(191) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`type` ENUM('GET_CONFIG', 'TRACK_VIEW') NOT NULL,
`hashedIp` VARCHAR(191) NOT NULL,
`durationInMs` INTEGER NOT NULL,
`projectId` VARCHAR(191) NOT NULL,

INDEX `ApiRequest_projectId_idx`(`projectId`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `hashedIp` on the `ApiRequest` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE `ApiRequest` DROP COLUMN `hashedIp`;
19 changes: 19 additions & 0 deletions apps/web/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ model Project {
stripeSubscriptionId String? @unique
stripePriceId String?
currentPeriodEnd DateTime @default(dbgenerated("(CURRENT_TIMESTAMP(3) + INTERVAL 30 DAY)"))
apiRequests ApiRequest[]
}

model ProjectUser {
Expand Down Expand Up @@ -272,3 +274,20 @@ model ApiKey {
@@index([userId])
}

enum ApiRequestType {
GET_CONFIG
TRACK_VIEW
}

model ApiRequest {
id String @id @default(cuid())
createdAt DateTime @default(now())
type ApiRequestType
durationInMs Int
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@index([projectId])
}
1 change: 1 addition & 0 deletions apps/web/src/env/schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const serverSchema = z.object({
GITHUB_OAUTH_TOKEN: z.string().optional(),
GOOGLE_CLIENT_ID: z.string().optional(),
GOOGLE_CLIENT_SECRET: z.string().optional(),
HASHING_SECRET: z.string().min(1),
});

/**
Expand Down
17 changes: 10 additions & 7 deletions apps/web/src/pages/api/dashboard/[projectId]/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RequestCache } from "server/services/RequestCache";
import { transformFlagValue } from "lib/flags";
import { LegacyAbbyDataResponse } from "@tryabby/core";
import { PlausibleService } from "server/services/PlausibleService";
import { RequestService } from "server/services/RequestService";

const incomingQuerySchema = z.object({
projectId: z.string(),
Expand All @@ -18,6 +19,8 @@ export default async function getWeightsHandler(
req: NextApiRequest,
res: NextApiResponse
) {
const now = performance.now();

await NextCors(req, res, {
methods: ["GET"],
origin: "*",
Expand Down Expand Up @@ -84,13 +87,13 @@ export default async function getWeightsHandler(

await RequestCache.increment(projectId);

PlausibleService.trackPlausibleGoal(
"API Project Data Retrieved",
{ projectId: projectId },
req.url
).catch((e) =>
console.error("Error while sending tracking data to Plausible: ", e)
);
RequestService.storeRequest({
projectId,
type: "GET_CONFIG",
durationInMs: performance.now() - now,
}).then((e) => {
console.error("Unable to store request", e);
});

return;
} catch (e) {
Expand Down
9 changes: 9 additions & 0 deletions apps/web/src/pages/api/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import isBot from "isbot";
import { Ratelimit } from "@upstash/ratelimit"; // for deno: see above
import { Redis } from "@upstash/redis";
import { RequestCache } from "server/services/RequestCache";
import { RequestService } from "server/services/RequestService";

export default async function incomingDataHandler(
req: NextApiRequest,
res: NextApiResponse
) {
const now = performance.now();
await NextCors(req, res, {
methods: ["POST"],
origin: "*",
Expand Down Expand Up @@ -75,6 +77,13 @@ export default async function incomingDataHandler(
}

await RequestCache.increment(event.projectId);
RequestService.storeRequest({
projectId: event.projectId,
type: "TRACK_VIEW",
durationInMs: performance.now() - now,
}).then((e) => {
console.error("Unable to store request", e);
});
} catch (err) {
console.error(err);
res.status(500).end();
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/pages/api/v1/config/[projectId]/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { NextApiRequest, NextApiResponse } from "next";
import NextCors from "nextjs-cors";
import { prisma } from "server/db/client";
import * as ConfigService from "server/services/ConfigService";
import { hashApiKey } from "utils/apiKey";
import { hashString } from "utils/apiKey";
import { z } from "zod";

const incomingQuerySchema = z.object({
Expand Down Expand Up @@ -36,7 +36,7 @@ export default async function handler(
return;
}

const hashedApiKey = hashApiKey(apiKey);
const hashedApiKey = hashString(apiKey);

const apiKeyEntry = await prisma.apiKey.findUnique({
where: {
Expand Down
17 changes: 10 additions & 7 deletions apps/web/src/pages/api/v1/data/[projectId].ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { trackPlanOverage } from "lib/logsnag";
import { RequestCache } from "server/services/RequestCache";
import { transformFlagValue } from "lib/flags";
import { PlausibleService } from "server/services/PlausibleService";
import { RequestService } from "server/services/RequestService";

const incomingQuerySchema = z.object({
projectId: z.string(),
Expand All @@ -18,6 +19,8 @@ export default async function getWeightsHandler(
req: NextApiRequest,
res: NextApiResponse
) {
const now = performance.now();

await NextCors(req, res, {
methods: ["GET"],
origin: "*",
Expand Down Expand Up @@ -82,13 +85,13 @@ export default async function getWeightsHandler(

await RequestCache.increment(projectId);

PlausibleService.trackPlausibleGoal(
"API Project Data Retrieved",
{ projectId },
req.url
).catch((e) =>
console.error("Error while sending tracking data to Plausible: ", e)
);
RequestService.storeRequest({
projectId,
type: "GET_CONFIG",
durationInMs: performance.now() - now,
}).then((e) => {
console.error("Unable to store request", e);
});

return;
} catch (e) {
Expand Down
13 changes: 13 additions & 0 deletions apps/web/src/server/services/RequestService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ApiRequest } from "@prisma/client";
import { hashString } from "utils/apiKey";
import { prisma } from "server/db/client";

export abstract class RequestService {
static async storeRequest(request: Omit<ApiRequest, "id" | "createdAt">) {
await prisma.apiRequest.create({
data: {
...request,
},
});
}
}
4 changes: 2 additions & 2 deletions apps/web/src/server/trpc/router/apikey.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";
import { protectedProcedure, router } from "../trpc";
import { generateRandomString, hashApiKey } from "utils/apiKey";
import { generateRandomString, hashString } from "utils/apiKey";
import dayjs from "dayjs";

export const apiKeyRouter = router({
Expand All @@ -12,7 +12,7 @@ export const apiKeyRouter = router({
)
.mutation(async ({ ctx, input }) => {
const apiKey = generateRandomString();
const hashedApiKey = hashApiKey(apiKey);
const hashedApiKey = hashString(apiKey);

await ctx.prisma.apiKey.create({
data: {
Expand Down
10 changes: 4 additions & 6 deletions apps/web/src/utils/apiKey.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { createHmac, randomBytes } from "crypto";
import { env } from "env/server.mjs";

export function generateRandomString(length = 32): string {
const apiKey = randomBytes(length).toString("hex");
return apiKey;
}

export function hashApiKey(apiKey: string): string {
const hmac = createHmac(
"sha256",
"dieserkeyistsupergeheimbittenichtweitergebendanke"
);
hmac.update(apiKey);
export function hashString(data: string): string {
const hmac = createHmac("sha256", env.HASHING_SECRET);
hmac.update(data);
const hashKey = hmac.digest("hex");
return hashKey;
}

1 comment on commit f0c99c4

@vercel
Copy link

@vercel vercel bot commented on f0c99c4 Sep 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.