mirror of https://github.com/buster-so/buster.git
feat: implement reports API endpoints for BUS-1605
- Add Report schema extending ReportElements with metadata fields - Create GET /api/v2/reports endpoint with pagination - Create PUT /api/v2/reports/:id endpoint for updates - Use stubbed data responses following established patterns - Follow modular route structure and Zod-first validation Co-Authored-By: nate@buster.so <nate@buster.so>
This commit is contained in:
parent
ef8b105d64
commit
abc801f10b
|
@ -5,6 +5,7 @@ import chatsRoutes from './chats';
|
|||
import dictionariesRoutes from './dictionaries';
|
||||
import electricShapeRoutes from './electric-shape';
|
||||
import organizationRoutes from './organization';
|
||||
import reportsRoutes from './reports';
|
||||
import securityRoutes from './security';
|
||||
import slackRoutes from './slack';
|
||||
import supportRoutes from './support';
|
||||
|
@ -23,6 +24,7 @@ const app = new Hono()
|
|||
.route('/organizations', organizationRoutes)
|
||||
.route('/dictionaries', dictionariesRoutes)
|
||||
.route('/title', titleRoutes)
|
||||
.route('/temp', tempRoutes);
|
||||
.route('/temp', tempRoutes)
|
||||
.route('/reports', reportsRoutes);
|
||||
|
||||
export default app;
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
import type { User } from '@buster/database';
|
||||
import type {
|
||||
GetReportsListRequest,
|
||||
GetReportsListResponse
|
||||
} from '@buster/server-shared/reports';
|
||||
import { GetReportsListRequestSchema } from '@buster/server-shared/reports';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
|
||||
async function getReportsListHandler(
|
||||
request: GetReportsListRequest,
|
||||
user: User
|
||||
): Promise<GetReportsListResponse> {
|
||||
const stubbedReports = [
|
||||
{
|
||||
id: 'report-1',
|
||||
name: 'Sales Analysis Q4',
|
||||
file_name: 'sales_analysis_q4.md',
|
||||
description: 'Quarterly sales performance analysis',
|
||||
created_by: user.id,
|
||||
created_at: '2024-01-15T10:00:00Z',
|
||||
updated_at: '2024-01-20T14:30:00Z',
|
||||
deleted_at: null,
|
||||
publicly_accessible: false,
|
||||
content: [
|
||||
{
|
||||
type: 'h1' as const,
|
||||
children: [{ text: 'Sales Analysis Q4' }]
|
||||
},
|
||||
{
|
||||
type: 'p' as const,
|
||||
children: [{ text: 'This report analyzes our Q4 sales performance.' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'report-2',
|
||||
name: 'Customer Metrics Dashboard',
|
||||
file_name: 'customer_metrics.md',
|
||||
description: 'Key customer engagement metrics',
|
||||
created_by: user.id,
|
||||
created_at: '2024-01-10T09:00:00Z',
|
||||
updated_at: '2024-01-18T16:45:00Z',
|
||||
deleted_at: null,
|
||||
publicly_accessible: true,
|
||||
content: [
|
||||
{
|
||||
type: 'h1' as const,
|
||||
children: [{ text: 'Customer Metrics' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'report-3',
|
||||
name: 'Marketing Campaign Results',
|
||||
file_name: 'marketing_campaign_results.md',
|
||||
description: 'Analysis of recent marketing campaigns',
|
||||
created_by: user.id,
|
||||
created_at: '2024-01-05T08:30:00Z',
|
||||
updated_at: '2024-01-12T11:15:00Z',
|
||||
deleted_at: null,
|
||||
publicly_accessible: false,
|
||||
content: [
|
||||
{
|
||||
type: 'h1' as const,
|
||||
children: [{ text: 'Marketing Campaign Results' }]
|
||||
},
|
||||
{
|
||||
type: 'p' as const,
|
||||
children: [{ text: 'Overview of our recent marketing initiatives and their performance.' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const { page_token, page_size } = request;
|
||||
const startIndex = page_token * page_size;
|
||||
const endIndex = startIndex + page_size;
|
||||
const paginatedReports = stubbedReports.slice(startIndex, endIndex);
|
||||
|
||||
return {
|
||||
data: paginatedReports,
|
||||
pagination: {
|
||||
page: page_token,
|
||||
page_size: page_size,
|
||||
total: stubbedReports.length,
|
||||
total_pages: Math.ceil(stubbedReports.length / page_size)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono()
|
||||
.get('/', zValidator('query', GetReportsListRequestSchema), async (c) => {
|
||||
const request = c.req.valid('query');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
const response = await getReportsListHandler(request, user);
|
||||
return c.json(response);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,61 @@
|
|||
import type { User } from '@buster/database';
|
||||
import type {
|
||||
UpdateReportRequest,
|
||||
UpdateReportResponse
|
||||
} from '@buster/server-shared/reports';
|
||||
import { UpdateReportRequestSchema } from '@buster/server-shared/reports';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
async function updateReportHandler(
|
||||
reportId: string,
|
||||
request: UpdateReportRequest,
|
||||
user: User
|
||||
): Promise<UpdateReportResponse> {
|
||||
const existingReport = {
|
||||
id: reportId,
|
||||
name: 'Sales Analysis Q4',
|
||||
file_name: 'sales_analysis_q4.md',
|
||||
description: 'Quarterly sales performance analysis',
|
||||
created_by: user.id,
|
||||
created_at: '2024-01-15T10:00:00Z',
|
||||
updated_at: '2024-01-20T14:30:00Z',
|
||||
deleted_at: null,
|
||||
publicly_accessible: false,
|
||||
content: [
|
||||
{
|
||||
type: 'h1' as const,
|
||||
children: [{ text: 'Sales Analysis Q4' }]
|
||||
},
|
||||
{
|
||||
type: 'p' as const,
|
||||
children: [{ text: 'This report analyzes our Q4 sales performance.' }]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (!reportId || reportId === 'invalid') {
|
||||
throw new HTTPException(404, { message: 'Report not found' });
|
||||
}
|
||||
|
||||
const updatedReport = {
|
||||
...existingReport,
|
||||
...request,
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
return updatedReport;
|
||||
}
|
||||
|
||||
const app = new Hono()
|
||||
.put('/', zValidator('json', UpdateReportRequestSchema), async (c) => {
|
||||
const reportId = c.req.param('id');
|
||||
const request = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
const response = await updateReportHandler(reportId, request, user);
|
||||
return c.json(response);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,11 @@
|
|||
import { Hono } from 'hono';
|
||||
import { requireAuth } from '../../../middleware/auth';
|
||||
import GET from './GET';
|
||||
import PUT_ID from './[id]';
|
||||
|
||||
const app = new Hono()
|
||||
.use('*', requireAuth)
|
||||
.route('/', GET)
|
||||
.route('/:id', PUT_ID);
|
||||
|
||||
export default app;
|
|
@ -1 +1,4 @@
|
|||
export * from './report-elements';
|
||||
export * from './reports.types';
|
||||
export * from './requests';
|
||||
export * from './responses';
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { z } from 'zod';
|
||||
import { ReportElementsSchema } from './report-elements';
|
||||
|
||||
export const ReportSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
file_name: z.string(),
|
||||
description: z.string(),
|
||||
created_by: z.string(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
deleted_at: z.string().nullable(),
|
||||
publicly_accessible: z.boolean(),
|
||||
content: ReportElementsSchema,
|
||||
});
|
||||
|
||||
export type Report = z.infer<typeof ReportSchema>;
|
|
@ -0,0 +1,17 @@
|
|||
import { z } from 'zod';
|
||||
import { ReportElementsSchema } from './report-elements';
|
||||
|
||||
export const GetReportsListRequestSchema = z.object({
|
||||
page_token: z.number().optional().default(0),
|
||||
page_size: z.number().optional().default(50),
|
||||
});
|
||||
|
||||
export const UpdateReportRequestSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
publicly_accessible: z.boolean().optional(),
|
||||
content: ReportElementsSchema.optional(),
|
||||
});
|
||||
|
||||
export type GetReportsListRequest = z.infer<typeof GetReportsListRequestSchema>;
|
||||
export type UpdateReportRequest = z.infer<typeof UpdateReportRequestSchema>;
|
|
@ -0,0 +1,9 @@
|
|||
import { z } from 'zod';
|
||||
import { PaginatedResponseSchema } from '../type-utilities/pagination';
|
||||
import { ReportSchema } from './reports.types';
|
||||
|
||||
export const GetReportsListResponseSchema = PaginatedResponseSchema(ReportSchema);
|
||||
export const UpdateReportResponseSchema = ReportSchema;
|
||||
|
||||
export type GetReportsListResponse = z.infer<typeof GetReportsListResponseSchema>;
|
||||
export type UpdateReportResponse = z.infer<typeof UpdateReportResponseSchema>;
|
Loading…
Reference in New Issue