diff --git a/src/server/prisma/migrations/20240427130848_add_survey/migration.sql b/src/server/prisma/migrations/20240427130848_add_survey/migration.sql new file mode 100644 index 0000000..09950d6 --- /dev/null +++ b/src/server/prisma/migrations/20240427130848_add_survey/migration.sql @@ -0,0 +1,48 @@ +-- CreateTable +CREATE TABLE "Survey" ( + "id" VARCHAR(30) NOT NULL, + "workspaceId" VARCHAR(30) NOT NULL, + "name" TEXT NOT NULL, + "payload" JSON NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "Survey_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SurveyResult" ( + "id" VARCHAR(30) NOT NULL, + "surveyId" VARCHAR(30) NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "sessionId" UUID NOT NULL, + "payload" JSON NOT NULL, + "browser" VARCHAR(20), + "os" VARCHAR(20), + "language" VARCHAR(35), + "ip" VARCHAR(45), + "country" CHAR(2), + "subdivision1" VARCHAR(20), + "subdivision2" VARCHAR(50), + "city" VARCHAR(50), + "longitude" DOUBLE PRECISION, + "latitude" DOUBLE PRECISION, + "accuracyRadius" INTEGER, + + CONSTRAINT "SurveyResult_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "Survey_workspaceId_idx" ON "Survey"("workspaceId"); + +-- CreateIndex +CREATE INDEX "SurveyResult_surveyId_idx" ON "SurveyResult"("surveyId"); + +-- CreateIndex +CREATE INDEX "SurveyResult_sessionId_idx" ON "SurveyResult"("sessionId"); + +-- AddForeignKey +ALTER TABLE "Survey" ADD CONSTRAINT "Survey_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SurveyResult" ADD CONSTRAINT "SurveyResult_surveyId_fkey" FOREIGN KEY ("surveyId") REFERENCES "Survey"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/server/prisma/schema.prisma b/src/server/prisma/schema.prisma index 0bac4ef..d254be9 100644 --- a/src/server/prisma/schema.prisma +++ b/src/server/prisma/schema.prisma @@ -55,6 +55,7 @@ model Workspace { selectedUsers User[] // user list who select this workspace, not use in most of case workspaceDailyUsage WorkspaceDailyUsage[] workspaceAuditLog WorkspaceAuditLog[] + surveys Survey[] } model WorkspacesOnUsers { @@ -400,3 +401,46 @@ enum WorkspaceAuditLogType { Monitor Notification } + +model Survey { + id String @id @default(cuid()) @db.VarChar(30) + workspaceId String @db.VarChar(30) + name String + /// [SurveyPayload] + /// @zod.custom(imports.SurveyPayloadSchema) + payload Json @db.Json + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + + workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade) + surveyResultList SurveyResult[] + + @@index([workspaceId]) +} + +model SurveyResult { + id String @id @default(cuid()) @db.VarChar(30) + surveyId String @db.VarChar(30) + createdAt DateTime @default(now()) @db.Timestamptz(6) + sessionId String @db.Uuid + /// [CommonPayload] + /// @zod.custom(imports.CommonPayloadSchema) + payload Json @db.Json + browser String? @db.VarChar(20) + os String? @db.VarChar(20) + language String? @db.VarChar(35) + ip String? @db.VarChar(45) // The max length of ipv6 which adapter with ipv4 is 45(for example: [::ffff:192.168.100.228] => 0000:0000:0000:0000:0000:ffff:192.168.100.228) + country String? @db.Char(2) + subdivision1 String? @db.VarChar(20) + subdivision2 String? @db.VarChar(50) + city String? @db.VarChar(50) + longitude Float? + latitude Float? + accuracyRadius Int? + + + survey Survey @relation(fields: [surveyId], references: [id], onUpdate: Cascade, onDelete: Cascade) + + @@index([surveyId]) + @@index([sessionId]) +} diff --git a/src/server/prisma/zod/index.ts b/src/server/prisma/zod/index.ts index 6a65c69..f7d95ee 100644 --- a/src/server/prisma/zod/index.ts +++ b/src/server/prisma/zod/index.ts @@ -17,3 +17,5 @@ export * from "./monitorstatus" export * from "./monitorstatuspage" export * from "./workspacedailyusage" export * from "./workspaceauditlog" +export * from "./survey" +export * from "./surveyresult" diff --git a/src/server/prisma/zod/schemas/index.ts b/src/server/prisma/zod/schemas/index.ts index 8fea0c3..e580b38 100644 --- a/src/server/prisma/zod/schemas/index.ts +++ b/src/server/prisma/zod/schemas/index.ts @@ -1,5 +1,7 @@ import { z } from 'zod'; +export const CommonPayloadSchema = z.record(z.string(), z.any()); + export const MonitorStatusPageListSchema = z.array( z.object({ id: z.string(), @@ -7,4 +9,11 @@ export const MonitorStatusPageListSchema = z.array( }) ); -export const CommonPayloadSchema = z.record(z.string(), z.any()); +export const SurveyPayloadSchema = z.object({ + items: z.object({ + label: z.string(), + name: z.string(), + type: z.enum(['text', 'select', 'email']), + options: z.array(z.string()).optional(), + }), +}); diff --git a/src/server/prisma/zod/survey.ts b/src/server/prisma/zod/survey.ts new file mode 100644 index 0000000..0f9dba0 --- /dev/null +++ b/src/server/prisma/zod/survey.ts @@ -0,0 +1,36 @@ +import * as z from "zod" +import * as imports from "./schemas" +import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteSurveyResult, RelatedSurveyResultModelSchema } from "./index" + +// Helper schema for JSON fields +type Literal = boolean | number | string +type Json = Literal | { [key: string]: Json } | Json[] +const literalSchema = z.union([z.string(), z.number(), z.boolean()]) +const jsonSchema: z.ZodSchema = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])) + +export const SurveyModelSchema = z.object({ + id: z.string(), + workspaceId: z.string(), + name: z.string(), + /** + * [SurveyPayload] + */ + payload: imports.SurveyPayloadSchema, + createdAt: z.date(), + updatedAt: z.date(), +}) + +export interface CompleteSurvey extends z.infer { + workspace: CompleteWorkspace + surveyResultList: CompleteSurveyResult[] +} + +/** + * RelatedSurveyModelSchema contains all relations on your model in addition to the scalars + * + * NOTE: Lazy required in case of potential circular dependencies within schema + */ +export const RelatedSurveyModelSchema: z.ZodSchema = z.lazy(() => SurveyModelSchema.extend({ + workspace: RelatedWorkspaceModelSchema, + surveyResultList: RelatedSurveyResultModelSchema.array(), +})) diff --git a/src/server/prisma/zod/surveyresult.ts b/src/server/prisma/zod/surveyresult.ts new file mode 100644 index 0000000..831b7dd --- /dev/null +++ b/src/server/prisma/zod/surveyresult.ts @@ -0,0 +1,44 @@ +import * as z from "zod" +import * as imports from "./schemas" +import { CompleteSurvey, RelatedSurveyModelSchema } from "./index" + +// Helper schema for JSON fields +type Literal = boolean | number | string +type Json = Literal | { [key: string]: Json } | Json[] +const literalSchema = z.union([z.string(), z.number(), z.boolean()]) +const jsonSchema: z.ZodSchema = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])) + +export const SurveyResultModelSchema = z.object({ + id: z.string(), + surveyId: z.string(), + createdAt: z.date(), + sessionId: z.string(), + /** + * [CommonPayload] + */ + payload: imports.CommonPayloadSchema, + browser: z.string().nullish(), + os: z.string().nullish(), + language: z.string().nullish(), + ip: z.string().nullish(), + country: z.string().nullish(), + subdivision1: z.string().nullish(), + subdivision2: z.string().nullish(), + city: z.string().nullish(), + longitude: z.number().nullish(), + latitude: z.number().nullish(), + accuracyRadius: z.number().int().nullish(), +}) + +export interface CompleteSurveyResult extends z.infer { + survey: CompleteSurvey +} + +/** + * RelatedSurveyResultModelSchema contains all relations on your model in addition to the scalars + * + * NOTE: Lazy required in case of potential circular dependencies within schema + */ +export const RelatedSurveyResultModelSchema: z.ZodSchema = z.lazy(() => SurveyResultModelSchema.extend({ + survey: RelatedSurveyModelSchema, +})) diff --git a/src/server/prisma/zod/workspace.ts b/src/server/prisma/zod/workspace.ts index 2e842f1..76580ba 100644 --- a/src/server/prisma/zod/workspace.ts +++ b/src/server/prisma/zod/workspace.ts @@ -1,6 +1,6 @@ import * as z from "zod" import * as imports from "./schemas" -import { CompleteWorkspacesOnUsers, RelatedWorkspacesOnUsersModelSchema, CompleteWebsite, RelatedWebsiteModelSchema, CompleteNotification, RelatedNotificationModelSchema, CompleteMonitor, RelatedMonitorModelSchema, CompleteMonitorStatusPage, RelatedMonitorStatusPageModelSchema, CompleteTelemetry, RelatedTelemetryModelSchema, CompleteUser, RelatedUserModelSchema, CompleteWorkspaceDailyUsage, RelatedWorkspaceDailyUsageModelSchema, CompleteWorkspaceAuditLog, RelatedWorkspaceAuditLogModelSchema } from "./index" +import { CompleteWorkspacesOnUsers, RelatedWorkspacesOnUsersModelSchema, CompleteWebsite, RelatedWebsiteModelSchema, CompleteNotification, RelatedNotificationModelSchema, CompleteMonitor, RelatedMonitorModelSchema, CompleteMonitorStatusPage, RelatedMonitorStatusPageModelSchema, CompleteTelemetry, RelatedTelemetryModelSchema, CompleteUser, RelatedUserModelSchema, CompleteWorkspaceDailyUsage, RelatedWorkspaceDailyUsageModelSchema, CompleteWorkspaceAuditLog, RelatedWorkspaceAuditLogModelSchema, CompleteSurvey, RelatedSurveyModelSchema } from "./index" // Helper schema for JSON fields type Literal = boolean | number | string @@ -34,6 +34,7 @@ export interface CompleteWorkspace extends z.infer selectedUsers: CompleteUser[] workspaceDailyUsage: CompleteWorkspaceDailyUsage[] workspaceAuditLog: CompleteWorkspaceAuditLog[] + surveys: CompleteSurvey[] } /** @@ -51,4 +52,5 @@ export const RelatedWorkspaceModelSchema: z.ZodSchema = z.laz selectedUsers: RelatedUserModelSchema.array(), workspaceDailyUsage: RelatedWorkspaceDailyUsageModelSchema.array(), workspaceAuditLog: RelatedWorkspaceAuditLogModelSchema.array(), + surveys: RelatedSurveyModelSchema.array(), })) diff --git a/src/server/types/global.d.ts b/src/server/types/global.d.ts index cfee091..8895df7 100644 --- a/src/server/types/global.d.ts +++ b/src/server/types/global.d.ts @@ -1,5 +1,8 @@ import type { JWTPayload } from '../middleware/auth'; -import type { MonitorStatusPageListSchema } from '../prisma/zod/schemas'; +import type { + MonitorStatusPageListSchema, + SurveyPayloadSchema, +} from '../prisma/zod/schemas'; declare global { namespace Express { @@ -13,5 +16,6 @@ declare global { items: any[]; } | null; type MonitorStatusPageList = z.infer; + type SurveyPayload = z.infer; } }