Add api validations.

This commit is contained in:
Brian Cao 2023-08-19 22:23:15 -07:00
parent 7d5a24044a
commit 7a7233ead4
41 changed files with 690 additions and 180 deletions

View File

@ -1,19 +1,20 @@
import redis from '@umami/redis-client';
import cors from 'cors';
import debug from 'debug';
import { getAuthToken, parseShareToken } from 'lib/auth';
import { ROLES } from 'lib/constants';
import { isUuid, secret } from 'lib/crypto';
import { findSession } from 'lib/session';
import { import {
createMiddleware,
unauthorized,
badRequest, badRequest,
createMiddleware,
parseSecureToken, parseSecureToken,
tooManyRequest, tooManyRequest,
unauthorized,
} from 'next-basics'; } from 'next-basics';
import debug from 'debug';
import cors from 'cors';
import redis from '@umami/redis-client';
import { findSession } from 'lib/session';
import { getAuthToken, parseShareToken } from 'lib/auth';
import { secret, isUuid } from 'lib/crypto';
import { ROLES } from 'lib/constants';
import { getUserById } from '../queries';
import { NextApiRequestCollect } from 'pages/api/send'; import { NextApiRequestCollect } from 'pages/api/send';
import { getUserById } from '../queries';
import { NextApiRequestQueryBody } from './types';
const log = debug('umami:middleware'); const log = debug('umami:middleware');
@ -75,3 +76,15 @@ export const useAuth = createMiddleware(async (req, res, next) => {
next(); next();
}); });
export const useValidate = createMiddleware(async (req: any, res, next) => {
try {
const { yup } = req as NextApiRequestQueryBody;
yup[req.method].validateSync({ ...req.query, ...req.body });
} catch (e: any) {
return badRequest(res, e.message);
}
next();
});

View File

@ -5,11 +5,13 @@ import {
EVENT_TYPE, EVENT_TYPE,
KAFKA_TOPIC, KAFKA_TOPIC,
REPORT_FILTER_TYPES, REPORT_FILTER_TYPES,
REPORT_TYPES,
ROLES, ROLES,
TEAM_FILTER_TYPES, TEAM_FILTER_TYPES,
USER_FILTER_TYPES, USER_FILTER_TYPES,
WEBSITE_FILTER_TYPES, WEBSITE_FILTER_TYPES,
} from './constants'; } from './constants';
import * as yup from 'yup';
type ObjectValues<T> = T[keyof T]; type ObjectValues<T> = T[keyof T];
@ -18,6 +20,8 @@ export type Role = ObjectValues<typeof ROLES>;
export type EventType = ObjectValues<typeof EVENT_TYPE>; export type EventType = ObjectValues<typeof EVENT_TYPE>;
export type DynamicDataType = ObjectValues<typeof DATA_TYPE>; export type DynamicDataType = ObjectValues<typeof DATA_TYPE>;
export type KafkaTopic = ObjectValues<typeof KAFKA_TOPIC>; export type KafkaTopic = ObjectValues<typeof KAFKA_TOPIC>;
export type ReportType = ObjectValues<typeof REPORT_TYPES>;
export type ReportSearchFilterType = ObjectValues<typeof REPORT_FILTER_TYPES>; export type ReportSearchFilterType = ObjectValues<typeof REPORT_FILTER_TYPES>;
export type UserSearchFilterType = ObjectValues<typeof USER_FILTER_TYPES>; export type UserSearchFilterType = ObjectValues<typeof USER_FILTER_TYPES>;
export type WebsiteSearchFilterType = ObjectValues<typeof WEBSITE_FILTER_TYPES>; export type WebsiteSearchFilterType = ObjectValues<typeof WEBSITE_FILTER_TYPES>;
@ -47,8 +51,8 @@ export interface ReportSearchFilter extends SearchFilter<ReportSearchFilterType>
export interface SearchFilter<T> { export interface SearchFilter<T> {
filter?: string; filter?: string;
filterType?: T; filterType?: T;
pageSize?: number; pageSize: number;
page?: number; page: number;
orderBy?: string; orderBy?: string;
} }
@ -76,11 +80,19 @@ export interface Auth {
}; };
} }
export interface YupRequest {
GET?: yup.ObjectSchema<any>;
POST?: yup.ObjectSchema<any>;
PUT?: yup.ObjectSchema<any>;
DELETE?: yup.ObjectSchema<any>;
}
export interface NextApiRequestQueryBody<TQuery = any, TBody = any> extends NextApiRequest { export interface NextApiRequestQueryBody<TQuery = any, TBody = any> extends NextApiRequest {
auth?: Auth; auth?: Auth;
query: TQuery & { [key: string]: string | string[] }; query: TQuery & { [key: string]: string | string[] };
body: TBody; body: TBody;
headers: any; headers: any;
yup: YupRequest;
} }
export interface NextApiRequestAuth extends NextApiRequest { export interface NextApiRequestAuth extends NextApiRequest {
@ -168,7 +180,6 @@ export interface RealtimeUpdate {
export interface DateRange { export interface DateRange {
startDate: Date; startDate: Date;
endDate: Date; endDate: Date;
unit: string;
value: string; value: string;
} }

19
lib/yup.ts Normal file
View File

@ -0,0 +1,19 @@
import * as yup from 'yup';
export function getDateRangeValidation() {
return {
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
};
}
// ex: /funnel|insights|retention/i
export function getFilterValidation(matchRegex) {
return {
filter: yup.string(),
filterType: yup.string().matches(matchRegex),
pageSize: yup.number().integer().positive().max(200),
page: yup.number().integer().positive(),
orderBy: yup.string(),
};
}

View File

@ -1,19 +1,20 @@
import redis from '@umami/redis-client';
import debug from 'debug'; import debug from 'debug';
import { setAuthKey } from 'lib/auth';
import { secret } from 'lib/crypto';
import { useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, User } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { import {
ok,
unauthorized,
badRequest,
checkPassword, checkPassword,
createSecureToken, createSecureToken,
methodNotAllowed,
forbidden, forbidden,
methodNotAllowed,
ok,
unauthorized,
} from 'next-basics'; } from 'next-basics';
import redis from '@umami/redis-client';
import { getUserByUsername } from 'queries'; import { getUserByUsername } from 'queries';
import { secret } from 'lib/crypto'; import * as yup from 'yup';
import { NextApiRequestQueryBody, User } from 'lib/types';
import { setAuthKey } from 'lib/auth';
const log = debug('umami:auth'); const log = debug('umami:auth');
@ -27,6 +28,13 @@ export interface LoginResponse {
user: User; user: User;
} }
const schema = {
POST: yup.object().shape({
username: yup.string().required(),
password: yup.string().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<any, LoginRequestBody>, req: NextApiRequestQueryBody<any, LoginRequestBody>,
res: NextApiResponse<LoginResponse>, res: NextApiResponse<LoginResponse>,
@ -35,13 +43,12 @@ export default async (
return forbidden(res); return forbidden(res);
} }
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') { if (req.method === 'POST') {
const { username, password } = req.body; const { username, password } = req.body;
if (!username || !password) {
return badRequest(res);
}
const user = await getUserByUsername(username, { includePassword: true }); const user = await getUserByUsername(username, { includePassword: true });
if (user && checkPassword(password, user.password)) { if (user && checkPassword(password, user.password)) {

View File

@ -1,26 +1,37 @@
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getEventDataEvents } from 'queries'; import { getEventDataEvents } from 'queries';
import * as yup from 'yup';
export interface EventDataEventsRequestQuery { export interface EventDataFieldsRequestQuery {
websiteId: string; websiteId: string;
dateRange: { startAt: string;
startDate: string; endAt: string;
endDate: string; event: string;
};
event?: string;
} }
const schema = {
GET: yup.object().shape({
websiteId: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
event: yup.string().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<EventDataEventsRequestQuery>, req: NextApiRequestQueryBody<EventDataFieldsRequestQuery, any>,
res: NextApiResponse<any>, res: NextApiResponse<any>,
) => { ) => {
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') { if (req.method === 'GET') {
const { websiteId, startAt, endAt, event } = req.query; const { websiteId, startAt, endAt, event } = req.query;

View File

@ -1,19 +1,27 @@
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getEventDataFields } from 'queries'; import { getEventDataFields } from 'queries';
import * as yup from 'yup';
export interface EventDataFieldsRequestQuery { export interface EventDataFieldsRequestQuery {
websiteId: string; websiteId: string;
dateRange: { startAt: string;
startDate: string; endAt: string;
endDate: string;
};
field?: string; field?: string;
} }
const schema = {
GET: yup.object().shape({
websiteId: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
field: yup.string(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<EventDataFieldsRequestQuery>, req: NextApiRequestQueryBody<EventDataFieldsRequestQuery>,
res: NextApiResponse<any>, res: NextApiResponse<any>,
@ -21,6 +29,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') { if (req.method === 'GET') {
const { websiteId, startAt, endAt, field } = req.query; const { websiteId, startAt, endAt, field } = req.query;

View File

@ -1,18 +1,24 @@
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getEventDataStats } from 'queries'; import * as yup from 'yup';
export interface EventDataStatsRequestQuery { export interface EventDataStatsRequestQuery {
websiteId: string; websiteId: string;
dateRange: { startAt: string;
startDate: string; endAt: string;
endDate: string;
};
} }
const schema = {
GET: yup.object().shape({
websiteId: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<EventDataStatsRequestQuery>, req: NextApiRequestQueryBody<EventDataStatsRequestQuery>,
res: NextApiResponse<any>, res: NextApiResponse<any>,
@ -20,6 +26,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') { if (req.method === 'GET') {
const { websiteId, startAt, endAt } = req.query; const { websiteId, startAt, endAt } = req.query;

View File

@ -1,15 +1,16 @@
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, User } from 'lib/types'; import { NextApiRequestQueryBody, User } from 'lib/types';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { import {
badRequest, badRequest,
checkPassword, checkPassword,
forbidden,
hashPassword, hashPassword,
methodNotAllowed, methodNotAllowed,
forbidden,
ok, ok,
} from 'next-basics'; } from 'next-basics';
import { getUserById, updateUser } from 'queries'; import { getUserById, updateUser } from 'queries';
import * as yup from 'yup';
export interface UserPasswordRequestQuery { export interface UserPasswordRequestQuery {
id: string; id: string;
@ -20,6 +21,14 @@ export interface UserPasswordRequestBody {
newPassword: string; newPassword: string;
} }
const schema = {
POST: yup.object().shape({
id: yup.string().uuid().required(),
currentPassword: yup.string().required(),
newPassword: yup.string().min(8).required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<UserPasswordRequestQuery, UserPasswordRequestBody>, req: NextApiRequestQueryBody<UserPasswordRequestQuery, UserPasswordRequestBody>,
res: NextApiResponse<User>, res: NextApiResponse<User>,
@ -30,6 +39,9 @@ export default async (
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { currentPassword, newPassword } = req.body; const { currentPassword, newPassword } = req.body;
const { id } = req.auth.user; const { id } = req.auth.user;

View File

@ -1,10 +1,20 @@
import { useCors } from 'lib/middleware'; import { useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types'; import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed } from 'next-basics'; import { methodNotAllowed } from 'next-basics';
import userTeams from 'pages/api/users/[id]/teams'; import userTeams from 'pages/api/users/[id]/teams';
import * as yup from 'yup';
export interface MyTeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {} export interface MyTeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {
id: string;
}
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Owner/i),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<MyTeamsRequestQuery, any>, req: NextApiRequestQueryBody<MyTeamsRequestQuery, any>,
@ -12,7 +22,12 @@ export default async (
) => { ) => {
await useCors(req, res); await useCors(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') { if (req.method === 'GET') {
req.query.id = req.auth.user.id;
return userTeams(req, res); return userTeams(req, res);
} }

View File

@ -1,11 +1,20 @@
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types'; import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed } from 'next-basics'; import { methodNotAllowed } from 'next-basics';
import userWebsites from 'pages/api/users/[id]/websites'; import userWebsites from 'pages/api/users/[id]/websites';
import * as yup from 'yup';
export interface MyWebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> {} export interface MyWebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> {
id: string;
}
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Domain/i),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<MyWebsitesRequestQuery, any>, req: NextApiRequestQueryBody<MyWebsitesRequestQuery, any>,
@ -14,6 +23,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') { if (req.method === 'GET') {
req.query.id = req.auth.user.id; req.query.id = req.auth.user.id;

View File

@ -1,22 +1,34 @@
import { subMinutes } from 'date-fns'; import { subMinutes } from 'date-fns';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, RealtimeInit } from 'lib/types'; import { NextApiRequestQueryBody, RealtimeInit } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getRealtimeData } from 'queries'; import { getRealtimeData } from 'queries';
import * as yup from 'yup';
export interface RealtimeRequestQuery { export interface RealtimeRequestQuery {
id: string; id: string;
startAt: number; startAt: number;
} }
const currentDate = new Date().getTime();
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
startAt: yup.number().integer().max(currentDate).required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<RealtimeRequestQuery>, req: NextApiRequestQueryBody<RealtimeRequestQuery>,
res: NextApiResponse<RealtimeInit>, res: NextApiResponse<RealtimeInit>,
) => { ) => {
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') { if (req.method === 'GET') {
const { id: websiteId, startAt } = req.query; const { id: websiteId, startAt } = req.query;

View File

@ -1,9 +1,10 @@
import { canUpdateReport, canViewReport, canDeleteReport } from 'lib/auth'; import { canDeleteReport, canUpdateReport, canViewReport } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody, ReportType, YupRequest } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getReportById, updateReport, deleteReport } from 'queries'; import { deleteReport, getReportById, updateReport } from 'queries';
import * as yup from 'yup';
export interface ReportRequestQuery { export interface ReportRequestQuery {
id: string; id: string;
@ -11,12 +12,34 @@ export interface ReportRequestQuery {
export interface ReportRequestBody { export interface ReportRequestBody {
websiteId: string; websiteId: string;
type: string; type: ReportType;
name: string; name: string;
description: string; description: string;
parameters: string; parameters: string;
} }
const schema: YupRequest = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
POST: yup.object().shape({
id: yup.string().uuid().required(),
websiteId: yup.string().uuid().required(),
type: yup
.string()
.matches(/funnel|insights|retention/i)
.required(),
name: yup.string().max(200).required(),
description: yup.string().max(500),
parameters: yup
.object()
.test('len', 'Must not exceed 6000 characters.', val => JSON.stringify(val).length < 6000),
}),
DELETE: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<ReportRequestQuery, ReportRequestBody>, req: NextApiRequestQueryBody<ReportRequestQuery, ReportRequestBody>,
res: NextApiResponse, res: NextApiResponse,
@ -24,6 +47,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: reportId } = req.query; const { id: reportId } = req.query;
const { const {
user: { id: userId }, user: { id: userId },

View File

@ -1,9 +1,10 @@
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getFunnel } from 'queries'; import { getFunnel } from 'queries';
import * as yup from 'yup';
export interface FunnelRequestBody { export interface FunnelRequestBody {
websiteId: string; websiteId: string;
@ -22,6 +23,21 @@ export interface FunnelResponse {
endAt: number; endAt: number;
} }
const schema = {
POST: yup.object().shape({
websiteId: yup.string().uuid().required(),
urls: yup.array().min(2).of(yup.string()).required(),
window: yup.number().positive().required(),
dateRange: yup
.object()
.shape({
startDate: yup.date().required(),
endDate: yup.date().required(),
})
.required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<any, FunnelRequestBody>, req: NextApiRequestQueryBody<any, FunnelRequestBody>,
res: NextApiResponse<FunnelResponse>, res: NextApiResponse<FunnelResponse>,
@ -29,6 +45,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') { if (req.method === 'POST') {
const { const {
websiteId, websiteId,

View File

@ -1,10 +1,11 @@
import { canViewWebsite } from 'lib/auth';
import { uuid } from 'lib/crypto'; import { uuid } from 'lib/crypto';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types'; import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok } from 'next-basics';
import { createReport, getReportsByUserId, getReportsByWebsiteId } from 'queries'; import { createReport, getReportsByUserId } from 'queries';
import * as yup from 'yup';
export interface ReportsRequestQuery extends SearchFilter<ReportSearchFilterType> {} export interface ReportsRequestQuery extends SearchFilter<ReportSearchFilterType> {}
@ -14,11 +15,28 @@ export interface ReportRequestBody {
type: string; type: string;
description: string; description: string;
parameters: { parameters: {
window: string; [key: string]: any;
urls: string[];
}; };
} }
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Description|Type|Username|Website Name|Website Domain/i),
}),
POST: yup.object().shape({
websiteId: yup.string().uuid().required(),
name: yup.string().max(200).required(),
type: yup
.string()
.matches(/funnel|insights|retention/i)
.required(),
description: yup.string().max(500),
parameters: yup
.object()
.test('len', 'Must not exceed 6000 characters.', val => JSON.stringify(val).length < 6000),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<any, ReportRequestBody>, req: NextApiRequestQueryBody<any, ReportRequestBody>,
res: NextApiResponse, res: NextApiResponse,
@ -26,6 +44,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { const {
user: { id: userId }, user: { id: userId },
} = req.auth; } = req.auth;

View File

@ -1,9 +1,10 @@
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getInsights } from 'queries'; import { getInsights } from 'queries';
import * as yup from 'yup';
export interface InsightsRequestBody { export interface InsightsRequestBody {
websiteId: string; websiteId: string;
@ -16,6 +17,37 @@ export interface InsightsRequestBody {
groups: { name: string; type: string }[]; groups: { name: string; type: string }[];
} }
const schema = {
POST: yup.object().shape({
websiteId: yup.string().uuid().required(),
dateRange: yup
.object()
.shape({
startDate: yup.date().required(),
endDate: yup.date().required(),
})
.required(),
fields: yup
.array()
.of(
yup.object().shape({
name: yup.string().required(),
type: yup.string().required(),
value: yup.string().required(),
}),
)
.min(1)
.required(),
filters: yup.array().of(yup.string()).min(1).required(),
groups: yup.array().of(
yup.object().shape({
name: yup.string().required(),
type: yup.string().required(),
}),
),
}),
};
function convertFilters(filters) { function convertFilters(filters) {
return filters.reduce((obj, { name, ...value }) => { return filters.reduce((obj, { name, ...value }) => {
obj[name] = value; obj[name] = value;
@ -31,6 +63,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') { if (req.method === 'POST') {
const { const {
websiteId, websiteId,

View File

@ -1,33 +1,43 @@
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getRetention } from 'queries'; import { getRetention } from 'queries';
import * as yup from 'yup';
export interface RetentionRequestBody { export interface RetentionRequestBody {
websiteId: string; websiteId: string;
dateRange: { window; startDate: string; endDate: string }; dateRange: { startDate: string; endDate: string };
timezone: string;
} }
export interface RetentionResponse { const schema = {
startAt: number; POST: yup.object().shape({
endAt: number; websiteId: yup.string().uuid().required(),
} dateRange: yup
.object()
.shape({
startDate: yup.date().required(),
endDate: yup.date().required(),
})
.required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<any, RetentionRequestBody>, req: NextApiRequestQueryBody<any, RetentionRequestBody>,
res: NextApiResponse<RetentionResponse>, res: NextApiResponse,
) => { ) => {
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') { if (req.method === 'POST') {
const { const {
websiteId, websiteId,
dateRange: { startDate, endDate }, dateRange: { startDate, endDate },
timezone,
} = req.body; } = req.body;
if (!(await canViewWebsite(req.auth, websiteId))) { if (!(await canViewWebsite(req.auth, websiteId))) {
@ -37,7 +47,6 @@ export default async (
const data = await getRetention(websiteId, { const data = await getRetention(websiteId, {
startDate: new Date(startDate), startDate: new Date(startDate),
endDate: new Date(endDate), endDate: new Date(endDate),
timezone,
}); });
return ok(res, data); return ok(res, data);

View File

@ -1,14 +1,15 @@
import isbot from 'isbot';
import ipaddr from 'ipaddr.js';
import { createToken, ok, send, badRequest, forbidden } from 'next-basics';
import { saveEvent, saveSessionData } from 'queries';
import { useCors, useSession } from 'lib/middleware';
import { getJsonBody, getIpAddress } from 'lib/detect';
import { secret } from 'lib/crypto';
import { NextApiRequest, NextApiResponse } from 'next';
import { Resolver } from 'dns/promises'; import { Resolver } from 'dns/promises';
import { CollectionType } from 'lib/types'; import ipaddr from 'ipaddr.js';
import { COLLECTION_TYPE } from 'lib/constants'; import isbot from 'isbot';
import { COLLECTION_TYPE, HOSTNAME_REGEX } from 'lib/constants';
import { secret } from 'lib/crypto';
import { getIpAddress, getJsonBody } from 'lib/detect';
import { useCors, useSession, useValidate } from 'lib/middleware';
import { CollectionType, YupRequest } from 'lib/types';
import { NextApiRequest, NextApiResponse } from 'next';
import { badRequest, createToken, forbidden, ok, send } from 'next-basics';
import { saveEvent, saveSessionData } from 'queries';
import * as yup from 'yup';
export interface CollectRequestBody { export interface CollectRequestBody {
payload: { payload: {
@ -43,8 +44,32 @@ export interface NextApiRequestCollect extends NextApiRequest {
city: string; city: string;
}; };
headers: { [key: string]: any }; headers: { [key: string]: any };
yup: YupRequest;
} }
const schema = {
POST: yup.object().shape({
payload: yup
.object()
.shape({
data: yup.object(),
hostname: yup.string().matches(HOSTNAME_REGEX).max(100),
language: yup.string().max(35),
referrer: yup.string().max(500),
screen: yup.string().max(11),
title: yup.string().max(500),
url: yup.string().max(500),
website: yup.string().uuid().required(),
name: yup.string().max(50),
})
.required(),
type: yup
.string()
.matches(/event|identify/i)
.required(),
}),
};
export default async (req: NextApiRequestCollect, res: NextApiResponse) => { export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
await useCors(req, res); await useCors(req, res);
@ -54,11 +79,8 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
const { type, payload } = getJsonBody<CollectRequestBody>(req); const { type, payload } = getJsonBody<CollectRequestBody>(req);
const error = validateBody({ type, payload }); req.yup = schema;
await useValidate(req, res);
if (error) {
return badRequest(res, error);
}
if (await hasBlockedIp(req)) { if (await hasBlockedIp(req)) {
return forbidden(res); return forbidden(res);
@ -118,22 +140,6 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
return send(res, token); return send(res, token);
}; };
function validateBody({ type, payload }: CollectRequestBody) {
if (!type || !payload) {
return 'Invalid payload.';
}
if (type !== COLLECTION_TYPE.event && type !== COLLECTION_TYPE.identify) {
return 'Wrong payload type.';
}
const { data } = payload;
if (data && !(typeof data === 'object' && !Array.isArray(data))) {
return 'Invalid event data.';
}
}
async function hasBlockedIp(req: NextApiRequestCollect) { async function hasBlockedIp(req: NextApiRequestCollect) {
const ignoreIps = process.env.IGNORE_IP; const ignoreIps = process.env.IGNORE_IP;
const ignoreHostnames = process.env.IGNORE_HOSTNAME; const ignoreHostnames = process.env.IGNORE_HOSTNAME;

View File

@ -1,8 +1,10 @@
import { NextApiRequestQueryBody } from 'lib/types';
import { secret } from 'lib/crypto'; import { secret } from 'lib/crypto';
import { useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { createToken, methodNotAllowed, notFound, ok } from 'next-basics'; import { createToken, methodNotAllowed, notFound, ok } from 'next-basics';
import { getWebsiteByShareId } from 'queries'; import { getWebsiteByShareId } from 'queries';
import * as yup from 'yup';
export interface ShareRequestQuery { export interface ShareRequestQuery {
id: string; id: string;
@ -13,10 +15,19 @@ export interface ShareResponse {
token: string; token: string;
} }
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<ShareRequestQuery>, req: NextApiRequestQueryBody<ShareRequestQuery>,
res: NextApiResponse<ShareResponse>, res: NextApiResponse<ShareResponse>,
) => { ) => {
req.yup = schema;
await useValidate(req, res);
const { id: shareId } = req.query; const { id: shareId } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {

View File

@ -1,10 +1,11 @@
import { Team } from '@prisma/client'; import { Team } from '@prisma/client';
import { NextApiRequestQueryBody } from 'lib/types';
import { canDeleteTeam, canUpdateTeam, canViewTeam } from 'lib/auth'; import { canDeleteTeam, canUpdateTeam, canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { deleteTeam, getTeamById, updateTeam } from 'queries'; import { deleteTeam, getTeamById, updateTeam } from 'queries';
import * as yup from 'yup';
export interface TeamRequestQuery { export interface TeamRequestQuery {
id: string; id: string;
@ -15,12 +16,29 @@ export interface TeamRequestBody {
accessCode: string; accessCode: string;
} }
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
POST: yup.object().shape({
id: yup.string().uuid().required(),
name: yup.string().max(50).required(),
accessCode: yup.string().max(50).required(),
}),
DELETE: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<TeamRequestQuery, TeamRequestBody>, req: NextApiRequestQueryBody<TeamRequestQuery, TeamRequestBody>,
res: NextApiResponse<Team>, res: NextApiResponse<Team>,
) => { ) => {
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: teamId } = req.query; const { id: teamId } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {

View File

@ -1,18 +1,28 @@
import { canDeleteTeamUser } from 'lib/auth'; import { canDeleteTeamUser } from 'lib/auth';
import { useAuth } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { deleteTeamUser } from 'queries'; import { deleteTeamUser } from 'queries';
import * as yup from 'yup';
export interface TeamUserRequestQuery { export interface TeamUserRequestQuery {
id: string; id: string;
userId: string; userId: string;
} }
const schema = {
DELETE: yup.object().shape({
id: yup.string().uuid().required(),
userId: yup.string().uuid().required(),
}),
};
export default async (req: NextApiRequestQueryBody<TeamUserRequestQuery>, res: NextApiResponse) => { export default async (req: NextApiRequestQueryBody<TeamUserRequestQuery>, res: NextApiResponse) => {
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'DELETE') { if (req.method === 'DELETE') {
const { id: teamId, userId } = req.query; const { id: teamId, userId } = req.query;

View File

@ -1,9 +1,9 @@
import { canUpdateTeam, canViewTeam } from 'lib/auth'; import { canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware'; import { useAuth } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types'; import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createTeamUser, getUserByUsername, getUsersByTeamId } from 'queries'; import { getUsersByTeamId } from 'queries';
export interface TeamUserRequestQuery extends SearchFilter<TeamSearchFilterType> { export interface TeamUserRequestQuery extends SearchFilter<TeamSearchFilterType> {
id: string; id: string;
@ -38,24 +38,5 @@ export default async (
return ok(res, users); return ok(res, users);
} }
if (req.method === 'POST') {
if (!(await canUpdateTeam(req.auth, teamId))) {
return unauthorized(res, 'You must be the owner of this team.');
}
const { email, roleId: roleId } = req.body;
// Check for User
const user = await getUserByUsername(email);
if (!user) {
return badRequest(res, 'The User does not exists.');
}
const updated = await createTeamUser(user.id, teamId, roleId);
return ok(res, updated);
}
return methodNotAllowed(res); return methodNotAllowed(res);
}; };

View File

@ -1,21 +1,32 @@
import { canDeleteTeamWebsite } from 'lib/auth'; import { canDeleteTeamWebsite } from 'lib/auth';
import { useAuth } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { deleteTeamWebsite } from 'queries/admin/teamWebsite'; import { deleteTeamWebsite } from 'queries/admin/teamWebsite';
import * as yup from 'yup';
export interface TeamWebsitesRequestQuery { export interface TeamWebsitesRequestQuery {
id: string; id: string;
websiteId: string; websiteId: string;
} }
const schema = {
DELETE: yup.object().shape({
id: yup.string().uuid().required(),
websiteId: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<TeamWebsitesRequestQuery>, req: NextApiRequestQueryBody<TeamWebsitesRequestQuery>,
res: NextApiResponse, res: NextApiResponse,
) => { ) => {
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: teamId, websiteId } = req.query; const { id: teamId, websiteId } = req.query;
if (req.method === 'DELETE') { if (req.method === 'DELETE') {

View File

@ -1,9 +1,10 @@
import { canViewTeam } from 'lib/auth'; import { canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types'; import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsites, getWebsitesByTeamId } from 'queries'; import { getWebsitesByTeamId } from 'queries';
import { createTeamWebsites } from 'queries/admin/teamWebsite'; import { createTeamWebsites } from 'queries/admin/teamWebsite';
export interface TeamWebsiteRequestQuery extends SearchFilter<WebsiteSearchFilterType> { export interface TeamWebsiteRequestQuery extends SearchFilter<WebsiteSearchFilterType> {
@ -14,12 +15,28 @@ export interface TeamWebsiteRequestBody {
websiteIds?: string[]; websiteIds?: string[];
} }
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
...getFilterValidation(/All|Name|Domain/i),
}),
POST: yup.object().shape({
id: yup.string().uuid().required(),
websiteIds: yup.array().of(yup.string()).min(1).required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<TeamWebsiteRequestQuery, TeamWebsiteRequestBody>, req: NextApiRequestQueryBody<TeamWebsiteRequestQuery, TeamWebsiteRequestBody>,
res: NextApiResponse, res: NextApiResponse,
) => { ) => {
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: teamId } = req.query; const { id: teamId } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {

View File

@ -1,23 +1,39 @@
import { Team } from '@prisma/client'; import { Team } from '@prisma/client';
import { canCreateTeam } from 'lib/auth'; import { canCreateTeam } from 'lib/auth';
import { uuid } from 'lib/crypto'; import { uuid } from 'lib/crypto';
import { useAuth } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types'; import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { getRandomChars, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getRandomChars, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createTeam, getTeamsByUserId } from 'queries'; import { createTeam, getTeamsByUserId } from 'queries';
import * as yup from 'yup';
export interface TeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {} export interface TeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {}
export interface TeamsRequestBody extends SearchFilter<TeamSearchFilterType> { export interface TeamsRequestBody {
name: string; name: string;
} }
export interface MyTeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {}
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Owner/i),
}),
POST: yup.object().shape({
name: yup.string().max(50).required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<TeamsRequestQuery, TeamsRequestBody>, req: NextApiRequestQueryBody<TeamsRequestQuery, TeamsRequestBody>,
res: NextApiResponse<Team[] | Team>, res: NextApiResponse<Team[] | Team>,
) => { ) => {
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { const {
user: { id: userId }, user: { id: userId },
} = req.auth; } = req.auth;

View File

@ -1,21 +1,30 @@
import { Team } from '@prisma/client'; import { Team } from '@prisma/client';
import { NextApiRequestQueryBody } from 'lib/types';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, notFound } from 'next-basics';
import { createTeamUser, getTeamByAccessCode, getTeamUser } from 'queries';
import { ROLES } from 'lib/constants'; import { ROLES } from 'lib/constants';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, notFound, ok } from 'next-basics';
import { createTeamUser, getTeamByAccessCode, getTeamUser } from 'queries';
import * as yup from 'yup';
export interface TeamsJoinRequestBody { export interface TeamsJoinRequestBody {
accessCode: string; accessCode: string;
} }
const schema = {
POST: yup.object().shape({
accessCode: yup.string().max(50).required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<any, TeamsJoinRequestBody>, req: NextApiRequestQueryBody<any, TeamsJoinRequestBody>,
res: NextApiResponse<Team>, res: NextApiResponse<Team>,
) => { ) => {
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') { if (req.method === 'POST') {
const { accessCode } = req.body; const { accessCode } = req.body;

View File

@ -1,9 +1,10 @@
import { NextApiRequestQueryBody, Role, User } from 'lib/types';
import { canDeleteUser, canUpdateUser, canViewUser } from 'lib/auth'; import { canDeleteUser, canUpdateUser, canViewUser } from 'lib/auth';
import { useAuth } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, Role, User } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { deleteUser, getUserById, getUserByUsername, updateUser } from 'queries'; import { deleteUser, getUserById, getUserByUsername, updateUser } from 'queries';
import * as yup from 'yup';
export interface UserRequestQuery { export interface UserRequestQuery {
id: string; id: string;
@ -15,12 +16,27 @@ export interface UserRequestBody {
role: Role; role: Role;
} }
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
POST: yup.object().shape({
id: yup.string().uuid().required(),
username: yup.string().max(255),
password: yup.string(),
role: yup.string().matches(/admin|user|view-only/i),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<UserRequestQuery, UserRequestBody>, req: NextApiRequestQueryBody<UserRequestQuery, UserRequestBody>,
res: NextApiResponse<User>, res: NextApiResponse<User>,
) => { ) => {
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { const {
user: { id: userId, isAdmin }, user: { id: userId, isAdmin },
} = req.auth; } = req.auth;

View File

@ -1,9 +1,10 @@
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types'; import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getTeamsByUserId } from 'queries'; import { getTeamsByUserId } from 'queries';
import * as yup from 'yup';
export interface UserTeamsRequestQuery extends SearchFilter<TeamSearchFilterType> { export interface UserTeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {
id: string; id: string;
} }
@ -14,6 +15,13 @@ export interface UserTeamsRequestBody {
shareId: string; shareId: string;
} }
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
...getFilterValidation('/All|Name|Owner/i'),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<any, UserTeamsRequestBody>, req: NextApiRequestQueryBody<any, UserTeamsRequestBody>,
res: NextApiResponse, res: NextApiResponse,
@ -21,6 +29,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { user } = req.auth; const { user } = req.auth;
const { id: userId } = req.query; const { id: userId } = req.query;

View File

@ -1,8 +1,9 @@
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getEventDataUsage, getEventUsage, getUserWebsites } from 'queries'; import { getEventDataUsage, getEventUsage, getUserWebsites } from 'queries';
import * as yup from 'yup';
export interface UserUsageRequestQuery { export interface UserUsageRequestQuery {
id: string; id: string;
@ -21,6 +22,14 @@ export interface UserUsageRequestResponse {
}[]; }[];
} }
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<UserUsageRequestQuery>, req: NextApiRequestQueryBody<UserUsageRequestQuery>,
res: NextApiResponse<UserUsageRequestResponse>, res: NextApiResponse<UserUsageRequestResponse>,
@ -28,6 +37,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { user } = req.auth; const { user } = req.auth;
if (req.method === 'GET') { if (req.method === 'GET') {

View File

@ -1,25 +1,36 @@
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types'; import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsitesByUserId } from 'queries'; import { getWebsitesByUserId } from 'queries';
import * as yup from 'yup';
export interface UserWebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> { export interface UserWebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> {
id: string; id: string;
} includeTeams?: boolean;
export interface UserWebsitesRequestBody { onlyTeams?: boolean;
name: string;
domain: string;
shareId: string;
} }
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
includeTeams: yup.boolean(),
onlyTeams: yup.boolean(),
...getFilterValidation(/All|Name|Domain/i),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<any, UserWebsitesRequestBody>, req: NextApiRequestQueryBody<UserWebsitesRequestQuery>,
res: NextApiResponse, res: NextApiResponse,
) => { ) => {
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { user } = req.auth; const { user } = req.auth;
const { id: userId, page, filter, pageSize, includeTeams, onlyTeams } = req.query; const { id: userId, page, filter, pageSize, includeTeams, onlyTeams } = req.query;

View File

@ -1,8 +1,9 @@
import { canCreateUser, canViewUsers } from 'lib/auth'; import { canCreateUser, canViewUsers } from 'lib/auth';
import { ROLES } from 'lib/constants'; import { ROLES } from 'lib/constants';
import { uuid } from 'lib/crypto'; import { uuid } from 'lib/crypto';
import { useAuth } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, Role, SearchFilter, User, UserSearchFilterType } from 'lib/types'; import { NextApiRequestQueryBody, Role, SearchFilter, User, UserSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createUser, getUserByUsername, getUsers } from 'queries'; import { createUser, getUserByUsername, getUsers } from 'queries';
@ -15,12 +16,31 @@ export interface UsersRequestBody {
role?: Role; role?: Role;
} }
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Username/i),
}),
POST: yup.object().shape({
username: yup.string().max(255).required(),
password: yup.string().required(),
id: yup.string().uuid(),
role: yup
.string()
.matches(/admin|user|view-only/i)
.required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<UsersRequestQuery, UsersRequestBody>, req: NextApiRequestQueryBody<UsersRequestQuery, UsersRequestBody>,
res: NextApiResponse<User[] | User>, res: NextApiResponse<User[] | User>,
) => { ) => {
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') { if (req.method === 'GET') {
if (!(await canViewUsers(req.auth))) { if (!(await canViewUsers(req.auth))) {
return unauthorized(res); return unauthorized(res);
@ -28,7 +48,7 @@ export default async (
const { page, filter, pageSize } = req.query; const { page, filter, pageSize } = req.query;
const users = await getUsers({ page, filter, pageSize: +pageSize || null }); const users = await getUsers({ page, filter, pageSize: pageSize ? +pageSize : null });
return ok(res, users); return ok(res, users);
} }

View File

@ -1,14 +1,21 @@
import { WebsiteActive, NextApiRequestQueryBody } from 'lib/types'; import { WebsiteActive, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getActiveVisitors } from 'queries'; import { getActiveVisitors } from 'queries';
import * as yup from 'yup';
export interface WebsiteActiveRequestQuery { export interface WebsiteActiveRequestQuery {
id: string; id: string;
} }
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsiteActiveRequestQuery>, req: NextApiRequestQueryBody<WebsiteActiveRequestQuery>,
res: NextApiResponse<WebsiteActive>, res: NextApiResponse<WebsiteActive>,
@ -16,6 +23,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query; const { id: websiteId } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {

View File

@ -1,14 +1,21 @@
import { WebsiteActive, NextApiRequestQueryBody } from 'lib/types'; import { WebsiteActive, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsiteDateRange } from 'queries'; import { getWebsiteDateRange } from 'queries';
import * as yup from 'yup';
export interface WebsiteDateRangeRequestQuery { export interface WebsiteDateRangeRequestQuery {
id: string; id: string;
} }
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsiteDateRangeRequestQuery>, req: NextApiRequestQueryBody<WebsiteDateRangeRequestQuery>,
res: NextApiResponse<WebsiteActive>, res: NextApiResponse<WebsiteActive>,
@ -16,6 +23,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query; const { id: websiteId } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {

View File

@ -1,6 +1,6 @@
import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types'; import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -16,9 +16,21 @@ export interface WebsiteEventsRequestQuery {
unit: string; unit: string;
timezone: string; timezone: string;
url: string; url: string;
eventName: string;
} }
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
unit: yup.string().required(),
timezone: yup.string().required(),
url: yup.string(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsiteEventsRequestQuery>, req: NextApiRequestQueryBody<WebsiteEventsRequestQuery>,
res: NextApiResponse<WebsiteMetric>, res: NextApiResponse<WebsiteMetric>,
@ -26,7 +38,10 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
const { id: websiteId, timezone, url, eventName } = req.query; req.yup = schema;
await useValidate(req, res);
const { id: websiteId, timezone, url } = req.query;
const { startDate, endDate, unit } = await parseDateRangeQuery(req); const { startDate, endDate, unit } = await parseDateRangeQuery(req);
if (req.method === 'GET') { if (req.method === 'GET') {
@ -44,7 +59,6 @@ export default async (
timezone, timezone,
unit, unit,
url, url,
eventName,
}); });
return ok(res, events); return ok(res, events);

View File

@ -2,7 +2,7 @@ import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, serverError, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, serverError, unauthorized } from 'next-basics';
import { Website, NextApiRequestQueryBody } from 'lib/types'; import { Website, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite, canUpdateWebsite, canDeleteWebsite } from 'lib/auth'; import { canViewWebsite, canUpdateWebsite, canDeleteWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { deleteWebsite, getWebsiteById, updateWebsite } from 'queries'; import { deleteWebsite, getWebsiteById, updateWebsite } from 'queries';
import { SHARE_ID_REGEX } from 'lib/constants'; import { SHARE_ID_REGEX } from 'lib/constants';
@ -16,6 +16,13 @@ export interface WebsiteRequestBody {
shareId: string; shareId: string;
} }
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsiteRequestQuery, WebsiteRequestBody>, req: NextApiRequestQueryBody<WebsiteRequestQuery, WebsiteRequestBody>,
res: NextApiResponse<Website>, res: NextApiResponse<Website>,
@ -23,6 +30,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query; const { id: websiteId } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {

View File

@ -2,10 +2,11 @@ import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types'; import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { SESSION_COLUMNS, EVENT_COLUMNS, FILTER_COLUMNS } from 'lib/constants'; import { SESSION_COLUMNS, EVENT_COLUMNS, FILTER_COLUMNS } from 'lib/constants';
import { getPageviewMetrics, getSessionMetrics } from 'queries'; import { getPageviewMetrics, getSessionMetrics } from 'queries';
import { parseDateRangeQuery } from 'lib/query'; import { parseDateRangeQuery } from 'lib/query';
import * as yup from 'yup';
export interface WebsiteMetricsRequestQuery { export interface WebsiteMetricsRequestQuery {
id: string; id: string;
@ -26,6 +27,12 @@ export interface WebsiteMetricsRequestQuery {
language: string; language: string;
} }
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsiteMetricsRequestQuery>, req: NextApiRequestQueryBody<WebsiteMetricsRequestQuery>,
res: NextApiResponse<WebsiteMetric[]>, res: NextApiResponse<WebsiteMetric[]>,
@ -33,6 +40,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { const {
id: websiteId, id: websiteId,
type, type,

View File

@ -3,7 +3,7 @@ import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types'; import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { getPageviewStats, getSessionStats } from 'queries'; import { getPageviewStats, getSessionStats } from 'queries';
import { parseDateRangeQuery } from 'lib/query'; import { parseDateRangeQuery } from 'lib/query';
@ -24,6 +24,13 @@ export interface WebsitePageviewRequestQuery {
city?: string; city?: string;
} }
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsitePageviewRequestQuery>, req: NextApiRequestQueryBody<WebsitePageviewRequestQuery>,
res: NextApiResponse<WebsitePageviews>, res: NextApiResponse<WebsitePageviews>,
@ -31,6 +38,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { const {
id: websiteId, id: websiteId,
timezone, timezone,

View File

@ -1,5 +1,5 @@
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types'; import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -9,6 +9,13 @@ export interface ReportsRequestQuery extends SearchFilter<ReportSearchFilterType
id: string; id: string;
} }
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<ReportsRequestQuery, any>, req: NextApiRequestQueryBody<ReportsRequestQuery, any>,
res: NextApiResponse, res: NextApiResponse,
@ -16,6 +23,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query; const { id: websiteId } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {

View File

@ -1,6 +1,6 @@
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { canUpdateWebsite } from 'lib/auth'; import { canUpdateWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { resetWebsite } from 'queries'; import { resetWebsite } from 'queries';
@ -9,6 +9,13 @@ export interface WebsiteResetRequestQuery {
id: string; id: string;
} }
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsiteResetRequestQuery>, req: NextApiRequestQueryBody<WebsiteResetRequestQuery>,
res: NextApiResponse, res: NextApiResponse,
@ -16,6 +23,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query; const { id: websiteId } = req.query;
if (req.method === 'POST') { if (req.method === 'POST') {

View File

@ -2,7 +2,7 @@ import { subMinutes, differenceInMinutes } from 'date-fns';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, WebsiteStats } from 'lib/types'; import { NextApiRequestQueryBody, WebsiteStats } from 'lib/types';
import { parseDateRangeQuery } from 'lib/query'; import { parseDateRangeQuery } from 'lib/query';
import { getWebsiteStats } from 'queries'; import { getWebsiteStats } from 'queries';
@ -24,6 +24,13 @@ export interface WebsiteStatsRequestQuery {
city: string; city: string;
} }
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsiteStatsRequestQuery>, req: NextApiRequestQueryBody<WebsiteStatsRequestQuery>,
res: NextApiResponse<WebsiteStats>, res: NextApiResponse<WebsiteStats>,
@ -31,6 +38,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { const {
id: websiteId, id: websiteId,
url, url,

View File

@ -1,6 +1,6 @@
import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { EVENT_COLUMNS, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants'; import { EVENT_COLUMNS, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants';
@ -10,6 +10,13 @@ export interface WebsiteResetRequestQuery {
id: string; id: string;
} }
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsiteResetRequestQuery>, req: NextApiRequestQueryBody<WebsiteResetRequestQuery>,
res: NextApiResponse, res: NextApiResponse,
@ -17,6 +24,9 @@ export default async (
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId, type } = req.query; const { id: websiteId, type } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {

View File

@ -1,11 +1,13 @@
import { canCreateWebsite } from 'lib/auth'; import { canCreateWebsite } from 'lib/auth';
import { uuid } from 'lib/crypto'; import { uuid } from 'lib/crypto';
import { useAuth, useCors } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types'; import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createWebsite } from 'queries'; import { createWebsite } from 'queries';
import userWebsites from 'pages/api/users/[id]/websites'; import userWebsites from 'pages/api/users/[id]/websites';
import * as yup from 'yup';
import { getFilterValidation } from 'lib/yup';
export interface WebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> {} export interface WebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> {}
@ -15,12 +17,25 @@ export interface WebsitesRequestBody {
shareId: string; shareId: string;
} }
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Domain/i),
}),
POST: yup.object().shape({
name: yup.string().max(100).required(),
domain: yup.string().max(500).required(),
shareId: yup.string().max(50),
}),
};
export default async ( export default async (
req: NextApiRequestQueryBody<WebsitesRequestQuery, WebsitesRequestBody>, req: NextApiRequestQueryBody<WebsitesRequestQuery, WebsitesRequestBody>,
res: NextApiResponse, res: NextApiResponse,
) => { ) => {
await useCors(req, res); await useCors(req, res);
await useAuth(req, res); await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { const {
user: { id: userId }, user: { id: userId },
@ -30,7 +45,7 @@ export default async (
req.query.id = userId; req.query.id = userId;
req.query.pageSize = 100; req.query.pageSize = 100;
return userWebsites(req, res); return userWebsites(req as any, res);
} }
if (req.method === 'POST') { if (req.method === 'POST') {