feat: add feed endpoint

This commit is contained in:
moonrailgun 2024-06-22 20:02:07 +08:00
parent c6747073b1
commit f459c6beea
13 changed files with 347 additions and 64 deletions

View File

@ -47,7 +47,7 @@ export const OpenAPI: OpenAPIConfig = {
PASSWORD: undefined,
TOKEN: undefined,
USERNAME: undefined,
VERSION: '1.9.3',
VERSION: '1.11.4',
WITH_CREDENTIALS: false,
interceptors: {
request: new Interceptors(),

View File

@ -51,21 +51,6 @@ export class UserService {
});
}
/**
* @param data The data for the request.
* @param data.requestBody
* @returns unknown Successful response
* @throws ApiError
*/
public static userRegister(data: $OpenApiTs['/register']['post']['req']): CancelablePromise<$OpenApiTs['/register']['post']['res'][200]> {
return __request(OpenAPI, {
method: 'POST',
url: '/register',
body: data.requestBody,
mediaType: 'application/json'
});
}
}
export class WorkspaceService {
@ -123,6 +108,23 @@ export class WebsiteService {
});
}
/**
* @param data The data for the request.
* @param data.workspaceId
* @returns number Successful response
* @returns unknown Error response
* @throws ApiError
*/
public static websiteAllOverview(data: $OpenApiTs['/workspace/{workspaceId}/website/allOverview']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/website/allOverview']['get']['res'][200] | $OpenApiTs['/workspace/{workspaceId}/website/allOverview']['get']['res'][200]> {
return __request(OpenAPI, {
method: 'GET',
url: '/workspace/{workspaceId}/website/allOverview',
path: {
workspaceId: data.workspaceId
}
});
}
/**
* @param data The data for the request.
* @param data.workspaceId
@ -1076,6 +1078,8 @@ export class SurveyService {
* @param data.surveyId
* @param data.limit
* @param data.cursor
* @param data.startAt
* @param data.endAt
* @returns unknown Successful response
* @throws ApiError
*/
@ -1089,7 +1093,9 @@ export class SurveyService {
},
query: {
limit: data.limit,
cursor: data.cursor
cursor: data.cursor,
startAt: data.startAt,
endAt: data.endAt
}
});
}
@ -1142,4 +1148,41 @@ export class BillingService {
});
}
}
export class FeedService {
/**
* @param data The data for the request.
* @param data.workspaceId
* @returns unknown Successful response
* @throws ApiError
*/
public static feedChannels(data: $OpenApiTs['/workspace/{workspaceId}/feed/channels']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/channels']['get']['res'][200]> {
return __request(OpenAPI, {
method: 'GET',
url: '/workspace/{workspaceId}/feed/channels',
path: {
workspaceId: data.workspaceId
}
});
}
/**
* @param data The data for the request.
* @param data.workspaceId
* @param data.channelId
* @returns unknown Successful response
* @throws ApiError
*/
public static feedEvents(data: $OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/events']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/events']['get']['res'][200]> {
return __request(OpenAPI, {
method: 'GET',
url: '/workspace/{workspaceId}/feed/{channelId}/events',
path: {
workspaceId: data.workspaceId,
channelId: data.channelId
}
});
}
}

View File

@ -104,49 +104,6 @@ export type $OpenApiTs = {
};
};
};
'/register': {
post: {
req: {
requestBody: {
username: string;
password: string;
};
};
res: {
/**
* Successful response
*/
200: {
info: {
username: string;
id: string;
role: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
currentWorkspace: {
id: string;
name: string;
dashboardLayout: {
layouts: {
[key: string]: (unknown[]);
};
items: unknown[];
} | null;
};
workspaces: Array<{
role: string;
workspace: {
id: string;
name: string;
};
}>;
};
token: string;
};
};
};
};
'/workspace/{workspaceId}/getServiceCount': {
get: {
req: {
@ -211,6 +168,25 @@ export type $OpenApiTs = {
};
};
};
'/workspace/{workspaceId}/website/allOverview': {
get: {
req: {
workspaceId: string;
};
res: {
/**
* Error response
*/
200: {
message: string;
code: string;
issues?: Array<{
message: string;
}>;
};
};
};
};
'/workspace/{workspaceId}/website/{websiteId}/info': {
get: {
req: {
@ -345,7 +321,7 @@ export type $OpenApiTs = {
region?: string;
startAt: number;
title?: string;
type: 'url' | 'language' | 'referrer' | 'browser' | 'os' | 'device' | 'country' | 'event';
type: 'url' | 'language' | 'referrer' | 'title' | 'browser' | 'os' | 'device' | 'country' | 'event';
url?: string;
websiteId: string;
workspaceId: string;
@ -1319,7 +1295,9 @@ export type $OpenApiTs = {
get: {
req: {
cursor?: string;
endAt?: number;
limit?: number;
startAt?: number;
surveyId: string;
workspaceId: string;
};
@ -1397,4 +1375,52 @@ export type $OpenApiTs = {
};
};
};
'/workspace/{workspaceId}/feed/channels': {
get: {
req: {
workspaceId: string;
};
res: {
/**
* Successful response
*/
200: Array<{
id: string;
workspaceId: string;
name: string;
createdAt: string;
updatedAt: string;
_count: {
events: number;
};
}>;
};
};
};
'/workspace/{workspaceId}/feed/{channelId}/events': {
get: {
req: {
channelId: string;
workspaceId: string;
};
res: {
/**
* Successful response
*/
200: Array<{
id: string;
channelId: string;
createdAt: string;
updatedAt: string;
eventName: string;
eventContent: string;
tags: Array<(string)>;
source: string;
senderId?: string | null;
senderName?: string | null;
important: boolean;
}>;
};
};
};
};

View File

@ -0,0 +1,39 @@
-- CreateTable
CREATE TABLE "FeedChannel" (
"id" VARCHAR(30) NOT NULL,
"workspaceId" VARCHAR(30) NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
CONSTRAINT "FeedChannel_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FeedEvent" (
"id" VARCHAR(30) NOT NULL,
"channelId" VARCHAR(30) NOT NULL,
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
"eventName" TEXT NOT NULL,
"eventContent" TEXT NOT NULL,
"tags" TEXT[],
"source" TEXT NOT NULL,
"senderId" TEXT,
"senderName" TEXT,
"important" BOOLEAN NOT NULL,
CONSTRAINT "FeedEvent_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "FeedChannel_workspaceId_idx" ON "FeedChannel"("workspaceId");
-- CreateIndex
CREATE INDEX "FeedEvent_channelId_idx" ON "FeedEvent"("channelId");
-- AddForeignKey
ALTER TABLE "FeedChannel" ADD CONSTRAINT "FeedChannel_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FeedEvent" ADD CONSTRAINT "FeedEvent_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "FeedChannel"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -56,6 +56,7 @@ model Workspace {
workspaceDailyUsage WorkspaceDailyUsage[]
workspaceAuditLog WorkspaceAuditLog[]
surveys Survey[]
feedChannels FeedChannel[]
}
model WorkspacesOnUsers {
@ -438,9 +439,39 @@ model SurveyResult {
latitude Float?
accuracyRadius Int?
survey Survey @relation(fields: [surveyId], references: [id], onUpdate: Cascade, onDelete: Cascade)
@@index([surveyId])
@@index([sessionId])
}
model FeedChannel {
id String @id @default(cuid()) @db.VarChar(30)
workspaceId String @db.VarChar(30)
name String
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
events FeedEvent[]
@@index([workspaceId])
}
model FeedEvent {
id String @id @default(cuid()) @db.VarChar(30)
channelId String @db.VarChar(30)
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
eventName String
eventContent String
tags String[]
source String // custom text or third-party integrations
senderId String? // maybe user id
senderName String? // use for display who
important Boolean
channel FeedChannel @relation(fields: [channelId], references: [id], onUpdate: Cascade, onDelete: Cascade)
@@index([channelId])
}

View File

@ -0,0 +1,26 @@
import * as z from "zod"
import * as imports from "./schemas"
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteFeedEvent, RelatedFeedEventModelSchema } from "./index"
export const FeedChannelModelSchema = z.object({
id: z.string(),
workspaceId: z.string(),
name: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
})
export interface CompleteFeedChannel extends z.infer<typeof FeedChannelModelSchema> {
workspace: CompleteWorkspace
events: CompleteFeedEvent[]
}
/**
* RelatedFeedChannelModelSchema contains all relations on your model in addition to the scalars
*
* NOTE: Lazy required in case of potential circular dependencies within schema
*/
export const RelatedFeedChannelModelSchema: z.ZodSchema<CompleteFeedChannel> = z.lazy(() => FeedChannelModelSchema.extend({
workspace: RelatedWorkspaceModelSchema,
events: RelatedFeedEventModelSchema.array(),
}))

View File

@ -0,0 +1,30 @@
import * as z from "zod"
import * as imports from "./schemas"
import { CompleteFeedChannel, RelatedFeedChannelModelSchema } from "./index"
export const FeedEventModelSchema = z.object({
id: z.string(),
channelId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
eventName: z.string(),
eventContent: z.string(),
tags: z.string().array(),
source: z.string(),
senderId: z.string().nullish(),
senderName: z.string().nullish(),
important: z.boolean(),
})
export interface CompleteFeedEvent extends z.infer<typeof FeedEventModelSchema> {
channel: CompleteFeedChannel
}
/**
* RelatedFeedEventModelSchema contains all relations on your model in addition to the scalars
*
* NOTE: Lazy required in case of potential circular dependencies within schema
*/
export const RelatedFeedEventModelSchema: z.ZodSchema<CompleteFeedEvent> = z.lazy(() => FeedEventModelSchema.extend({
channel: RelatedFeedChannelModelSchema,
}))

View File

@ -19,3 +19,5 @@ export * from "./workspacedailyusage"
export * from "./workspaceauditlog"
export * from "./survey"
export * from "./surveyresult"
export * from "./feedchannel"
export * from "./feedevent"

View File

@ -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, CompleteSurvey, RelatedSurveyModelSchema } from "./index"
import { CompleteWorkspacesOnUsers, RelatedWorkspacesOnUsersModelSchema, CompleteWebsite, RelatedWebsiteModelSchema, CompleteNotification, RelatedNotificationModelSchema, CompleteMonitor, RelatedMonitorModelSchema, CompleteMonitorStatusPage, RelatedMonitorStatusPageModelSchema, CompleteTelemetry, RelatedTelemetryModelSchema, CompleteUser, RelatedUserModelSchema, CompleteWorkspaceDailyUsage, RelatedWorkspaceDailyUsageModelSchema, CompleteWorkspaceAuditLog, RelatedWorkspaceAuditLogModelSchema, CompleteSurvey, RelatedSurveyModelSchema, CompleteFeedChannel, RelatedFeedChannelModelSchema } from "./index"
// Helper schema for JSON fields
type Literal = boolean | number | string
@ -35,6 +35,7 @@ export interface CompleteWorkspace extends z.infer<typeof WorkspaceModelSchema>
workspaceDailyUsage: CompleteWorkspaceDailyUsage[]
workspaceAuditLog: CompleteWorkspaceAuditLog[]
surveys: CompleteSurvey[]
feedChannels: CompleteFeedChannel[]
}
/**
@ -53,4 +54,5 @@ export const RelatedWorkspaceModelSchema: z.ZodSchema<CompleteWorkspace> = z.laz
workspaceDailyUsage: RelatedWorkspaceDailyUsageModelSchema.array(),
workspaceAuditLog: RelatedWorkspaceAuditLogModelSchema.array(),
surveys: RelatedSurveyModelSchema.array(),
feedChannels: RelatedFeedChannelModelSchema.array(),
}))

View File

@ -0,0 +1,81 @@
import { z } from 'zod';
import { OpenApiMetaInfo, router, workspaceProcedure } from '../trpc';
import { OPENAPI_TAG } from '../../utils/const';
import { OpenApiMeta } from 'trpc-openapi';
import { FeedChannelModelSchema, FeedEventModelSchema } from '../../prisma/zod';
import { prisma } from '../../model/_client';
export const feedRouter = router({
channels: workspaceProcedure
.meta(
buildFeedOpenapi({
method: 'GET',
path: '/channels',
})
)
.input(z.object({}))
.output(
z.array(
FeedChannelModelSchema.merge(
z.object({
_count: z.object({
events: z.number(),
}),
})
)
)
)
.query(async ({ input }) => {
const { workspaceId } = input;
const channels = await prisma.feedChannel.findMany({
where: {
workspaceId,
},
include: {
_count: {
select: {
events: true,
},
},
},
});
return channels;
}),
events: workspaceProcedure
.meta(
buildFeedOpenapi({
method: 'GET',
path: '/{channelId}/events',
})
)
.input(
z.object({
channelId: z.string(),
})
)
.output(z.array(FeedEventModelSchema))
.query(async ({ input }) => {
const { channelId } = input;
const events = await prisma.feedEvent.findMany({
where: {
channelId: channelId,
},
});
return events;
}),
});
function buildFeedOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
return {
openapi: {
tags: [OPENAPI_TAG.FEED],
protect: true,
...meta,
path: `/workspace/{workspaceId}/feed${meta.path}`,
},
};
}

View File

@ -10,6 +10,7 @@ import { auditLogRouter } from './auditLog';
import { billingRouter } from './billing';
import { telemetryRouter } from './telemetry';
import { surveyRouter } from './survey';
import { feedRouter } from './feed';
export const appRouter = router({
global: globalRouter,
@ -23,6 +24,7 @@ export const appRouter = router({
serverStatus: serverStatusRouter,
auditLog: auditLogRouter,
billing: billingRouter,
feed: feedRouter,
});
export type AppRouter = typeof appRouter;

View File

@ -111,4 +111,5 @@ export enum OPENAPI_TAG {
BILLING = 'Billing',
TELEMETRY = 'Telemetry',
SURVEY = 'Survey',
FEED = 'Feed',
}

File diff suppressed because one or more lines are too long