Merge branch 'dev' into analytics

This commit is contained in:
Brian Cao 2024-04-29 21:52:04 -07:00
commit 4eb01c5563
17 changed files with 90 additions and 77 deletions

View File

@ -1,14 +1,14 @@
import { UseQueryOptions } from '@tanstack/react-query'; import { UseQueryOptions } from '@tanstack/react-query';
import { useState } from 'react'; import { useState } from 'react';
import { useApi } from './useApi'; import { useApi } from './useApi';
import { FilterResult, SearchFilter, FilterQueryResult } from 'lib/types'; import { PageResult, PageParams, FilterQueryResult } from 'lib/types';
export function useFilterQuery<T = any>({ export function useFilterQuery<T = any>({
queryKey, queryKey,
queryFn, queryFn,
...options ...options
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): FilterQueryResult<T> { }: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): FilterQueryResult<T> {
const [params, setParams] = useState<T | SearchFilter>({ const [params, setParams] = useState<T | PageParams>({
query: '', query: '',
page: 1, page: 1,
}); });
@ -21,7 +21,7 @@ export function useFilterQuery<T = any>({
}); });
return { return {
result: data as FilterResult<any>, result: data as PageResult<any>,
query, query,
params, params,
setParams, setParams,

View File

@ -5,7 +5,7 @@ import { MYSQL, POSTGRESQL, getDatabaseType } from 'lib/db';
import { SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE } from './constants'; import { SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE } from './constants';
import { fetchWebsite } from './load'; import { fetchWebsite } from './load';
import { maxDate } from './date'; import { maxDate } from './date';
import { QueryFilters, QueryOptions, SearchFilter } from './types'; import { QueryFilters, QueryOptions, PageParams } from './types';
import { filtersToArray } from './params'; import { filtersToArray } from './params';
const MYSQL_DATE_FORMATS = { const MYSQL_DATE_FORMATS = {
@ -191,7 +191,7 @@ async function rawQuery(sql: string, data: object): Promise<any> {
return prisma.rawQuery(query, params); return prisma.rawQuery(query, params);
} }
async function pagedQuery<T>(model: string, criteria: T, filters: SearchFilter) { async function pagedQuery<T>(model: string, criteria: T, filters: PageParams) {
const { page = 1, pageSize, orderBy, sortDescending = false } = filters || {}; const { page = 1, pageSize, orderBy, sortDescending = false } = filters || {};
const size = +pageSize || DEFAULT_PAGE_SIZE; const size = +pageSize || DEFAULT_PAGE_SIZE;

View File

@ -24,31 +24,7 @@ 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 ReportType = ObjectValues<typeof REPORT_TYPES>;
export interface WebsiteSearchFilter extends SearchFilter { export interface PageParams {
userId?: string;
teamId?: string;
includeTeams?: boolean;
onlyTeams?: boolean;
}
export interface UserSearchFilter extends SearchFilter {
teamId?: string;
}
export interface TeamSearchFilter extends SearchFilter {
userId?: string;
}
export interface TeamUserSearchFilter extends SearchFilter {
teamId?: string;
}
export interface ReportSearchFilter extends SearchFilter {
userId?: string;
websiteId?: string;
}
export interface SearchFilter {
query?: string; query?: string;
page?: number; page?: number;
pageSize?: number; pageSize?: number;
@ -56,7 +32,7 @@ export interface SearchFilter {
sortDescending?: boolean; sortDescending?: boolean;
} }
export interface FilterResult<T> { export interface PageResult<T> {
data: T; data: T;
count: number; count: number;
page: number; page: number;
@ -66,10 +42,10 @@ export interface FilterResult<T> {
} }
export interface FilterQueryResult<T> { export interface FilterQueryResult<T> {
result: FilterResult<T>; result: PageResult<T>;
query: any; query: any;
params: SearchFilter; params: PageParams;
setParams: Dispatch<SetStateAction<T | SearchFilter>>; setParams: Dispatch<SetStateAction<T | PageParams>>;
} }
export interface DynamicData { export interface DynamicData {

View File

@ -1,13 +1,13 @@
import { canViewUsers } from 'lib/auth'; import { canViewUsers } from 'lib/auth';
import { useAuth, useValidate } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, Role, SearchFilter, User } from 'lib/types'; import { NextApiRequestQueryBody, Role, PageParams, User } from 'lib/types';
import { pageInfo } from 'lib/schema'; import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getUsers } from 'queries'; import { getUsers } from 'queries';
import * as yup from 'yup'; import * as yup from 'yup';
export interface UsersRequestQuery extends SearchFilter {} export interface UsersRequestQuery extends PageParams {}
export interface UsersRequestBody { export interface UsersRequestBody {
userId: string; userId: string;
username: string; username: string;

View File

@ -1,13 +1,17 @@
import { canViewAllWebsites } from 'lib/auth'; import { canViewAllWebsites } from 'lib/auth';
import { ROLES } from 'lib/constants';
import { useAuth, useCors, useValidate } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types'; import { pageInfo } from 'lib/schema';
import { NextApiRequestQueryBody, PageParams } 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 { getWebsites } from 'queries'; import { getWebsites } from 'queries';
import * as yup from 'yup'; import * as yup from 'yup';
import { pageInfo } from 'lib/schema';
export interface WebsitesRequestQuery extends SearchFilter {} export interface WebsitesRequestQuery extends PageParams {
userId?: string;
includeTeams?: boolean;
}
export interface WebsitesRequestBody { export interface WebsitesRequestBody {
name: string; name: string;
@ -39,8 +43,29 @@ export default async (
return unauthorized(res); return unauthorized(res);
} }
const { userId, includeOwnedTeams } = req.query;
const websites = await getWebsites( const websites = await getWebsites(
{ {
where: {
OR: [
...(userId && [{ userId }]),
...(userId &&
includeOwnedTeams && [
{
team: {
deletedAt: null,
teamUser: {
some: {
role: ROLES.teamOwner,
userId,
},
},
},
},
]),
],
},
include: { include: {
user: { user: {
select: { select: {
@ -48,6 +73,18 @@ export default async (
id: true, id: true,
}, },
}, },
team: {
where: {
deletedAt: null,
},
include: {
teamUser: {
where: {
role: ROLES.teamOwner,
},
},
},
},
}, },
}, },
req.query, req.query,

View File

@ -1,13 +1,13 @@
import { canAddUserToTeam, canViewTeam } from 'lib/auth'; import { canAddUserToTeam, canViewTeam } from 'lib/auth';
import { useAuth, useValidate } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { pageInfo } from 'lib/schema'; import { pageInfo } from 'lib/schema';
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types'; import { NextApiRequestQueryBody, PageParams } from 'lib/types';
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 { createTeamUser, getTeamUser, getTeamUsers } from 'queries'; import { createTeamUser, getTeamUser, getTeamUsers } from 'queries';
import * as yup from 'yup'; import * as yup from 'yup';
export interface TeamUserRequestQuery extends SearchFilter { export interface TeamUserRequestQuery extends PageParams {
teamId: string; teamId: string;
} }

View File

@ -1,13 +1,13 @@
import * as yup from 'yup'; import * as yup from 'yup';
import { canViewTeam } from 'lib/auth'; import { canViewTeam } from 'lib/auth';
import { useAuth, useValidate } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types'; import { NextApiRequestQueryBody, PageParams } from 'lib/types';
import { pageInfo } from 'lib/schema'; import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { ok, unauthorized } from 'next-basics'; import { ok, unauthorized } from 'next-basics';
import { getTeamWebsites } from 'queries'; import { getTeamWebsites } from 'queries';
export interface TeamWebsiteRequestQuery extends SearchFilter { export interface TeamWebsiteRequestQuery extends PageParams {
teamId: string; teamId: string;
} }

View File

@ -3,13 +3,13 @@ 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, useValidate } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types'; import { NextApiRequestQueryBody, PageParams } from 'lib/types';
import { pageInfo } from 'lib/schema'; import { pageInfo } from 'lib/schema';
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 } from 'queries'; import { createTeam } from 'queries';
export interface TeamsRequestQuery extends SearchFilter {} export interface TeamsRequestQuery extends PageParams {}
export interface TeamsRequestBody { export interface TeamsRequestBody {
name: string; name: string;
} }

View File

@ -1,12 +1,12 @@
import * as yup from 'yup'; import * as yup from 'yup';
import { useAuth, useCors, useValidate } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types'; import { NextApiRequestQueryBody, PageParams } from 'lib/types';
import { pageInfo } from 'lib/schema'; import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getUserTeams } from 'queries'; import { getUserTeams } from 'queries';
export interface UserTeamsRequestQuery extends SearchFilter { export interface UserTeamsRequestQuery extends PageParams {
userId: string; userId: string;
} }

View File

@ -2,14 +2,14 @@ import { canCreateUser } 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, useValidate } from 'lib/middleware'; import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, Role, SearchFilter, User } from 'lib/types'; import { NextApiRequestQueryBody, Role, PageParams, User } from 'lib/types';
import { pageInfo } from 'lib/schema'; import { pageInfo } from 'lib/schema';
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 } from 'queries'; import { createUser, getUserByUsername } from 'queries';
import * as yup from 'yup'; import * as yup from 'yup';
export interface UsersRequestQuery extends SearchFilter {} export interface UsersRequestQuery extends PageParams {}
export interface UsersRequestBody { export interface UsersRequestBody {
username: string; username: string;
password: string; password: string;

View File

@ -1,13 +1,13 @@
import * as yup from 'yup'; import * as yup from 'yup';
import { canViewWebsite } from 'lib/auth'; import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors, useValidate } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types'; import { NextApiRequestQueryBody, PageParams } 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 { getWebsiteReports } from 'queries'; import { getWebsiteReports } from 'queries';
import { pageInfo } from 'lib/schema'; import { pageInfo } from 'lib/schema';
export interface ReportsRequestQuery extends SearchFilter { export interface ReportsRequestQuery extends PageParams {
websiteId: string; websiteId: string;
} }

View File

@ -1,7 +1,7 @@
import { canCreateTeamWebsite, canCreateWebsite } from 'lib/auth'; import { canCreateTeamWebsite, canCreateWebsite } from 'lib/auth';
import { uuid } from 'lib/crypto'; import { uuid } from 'lib/crypto';
import { useAuth, useCors, useValidate } from 'lib/middleware'; import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types'; import { NextApiRequestQueryBody, PageParams } 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';
@ -9,7 +9,7 @@ import userWebsitesRoute from 'pages/api/users/[userId]/websites';
import * as yup from 'yup'; import * as yup from 'yup';
import { pageInfo } from 'lib/schema'; import { pageInfo } from 'lib/schema';
export interface WebsitesRequestQuery extends SearchFilter {} export interface WebsitesRequestQuery extends PageParams {}
export interface WebsitesRequestBody { export interface WebsitesRequestBody {
name: string; name: string;

View File

@ -1,6 +1,6 @@
import { Prisma, Report } from '@prisma/client'; import { Prisma, Report } from '@prisma/client';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
import { FilterResult, ReportSearchFilter } from 'lib/types'; import { PageResult, PageParams } from 'lib/types';
import ReportFindManyArgs = Prisma.ReportFindManyArgs; import ReportFindManyArgs = Prisma.ReportFindManyArgs;
async function findReport(criteria: Prisma.ReportFindUniqueArgs): Promise<Report> { async function findReport(criteria: Prisma.ReportFindUniqueArgs): Promise<Report> {
@ -17,8 +17,8 @@ export async function getReport(reportId: string): Promise<Report> {
export async function getReports( export async function getReports(
criteria: ReportFindManyArgs, criteria: ReportFindManyArgs,
filters: ReportSearchFilter = {}, filters: PageParams = {},
): Promise<FilterResult<Report[]>> { ): Promise<PageResult<Report[]>> {
const { query } = filters; const { query } = filters;
const where: Prisma.ReportWhereInput = { const where: Prisma.ReportWhereInput = {
@ -50,8 +50,8 @@ export async function getReports(
export async function getUserReports( export async function getUserReports(
userId: string, userId: string,
filters?: ReportSearchFilter, filters?: PageParams,
): Promise<FilterResult<Report[]>> { ): Promise<PageResult<Report[]>> {
return getReports( return getReports(
{ {
where: { where: {
@ -72,8 +72,8 @@ export async function getUserReports(
export async function getWebsiteReports( export async function getWebsiteReports(
websiteId: string, websiteId: string,
filters: ReportSearchFilter = {}, filters: PageParams = {},
): Promise<FilterResult<Report[]>> { ): Promise<PageResult<Report[]>> {
return getReports( return getReports(
{ {
where: { where: {

View File

@ -2,7 +2,7 @@ import { Prisma, Team } from '@prisma/client';
import { ROLES } from 'lib/constants'; import { ROLES } from 'lib/constants';
import { uuid } from 'lib/crypto'; import { uuid } from 'lib/crypto';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
import { FilterResult, TeamSearchFilter } from 'lib/types'; import { PageResult, PageParams } from 'lib/types';
import TeamFindManyArgs = Prisma.TeamFindManyArgs; import TeamFindManyArgs = Prisma.TeamFindManyArgs;
export async function findTeam(criteria: Prisma.TeamFindUniqueArgs): Promise<Team> { export async function findTeam(criteria: Prisma.TeamFindUniqueArgs): Promise<Team> {
@ -22,8 +22,8 @@ export async function getTeam(teamId: string, options: { includeMembers?: boolea
export async function getTeams( export async function getTeams(
criteria: TeamFindManyArgs, criteria: TeamFindManyArgs,
filters: TeamSearchFilter = {}, filters: PageParams = {},
): Promise<FilterResult<Team[]>> { ): Promise<PageResult<Team[]>> {
const { getSearchParameters } = prisma; const { getSearchParameters } = prisma;
const { query } = filters; const { query } = filters;
@ -42,7 +42,7 @@ export async function getTeams(
); );
} }
export async function getUserTeams(userId: string, filters: TeamSearchFilter = {}) { export async function getUserTeams(userId: string, filters: PageParams = {}) {
return getTeams( return getTeams(
{ {
where: { where: {

View File

@ -1,7 +1,7 @@
import { Prisma, TeamUser } from '@prisma/client'; import { Prisma, TeamUser } from '@prisma/client';
import { uuid } from 'lib/crypto'; import { uuid } from 'lib/crypto';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
import { FilterResult, TeamUserSearchFilter } from 'lib/types'; import { PageResult, PageParams } from 'lib/types';
import TeamUserFindManyArgs = Prisma.TeamUserFindManyArgs; import TeamUserFindManyArgs = Prisma.TeamUserFindManyArgs;
export async function findTeamUser(criteria: Prisma.TeamUserFindUniqueArgs): Promise<TeamUser> { export async function findTeamUser(criteria: Prisma.TeamUserFindUniqueArgs): Promise<TeamUser> {
@ -19,8 +19,8 @@ export async function getTeamUser(teamId: string, userId: string): Promise<TeamU
export async function getTeamUsers( export async function getTeamUsers(
criteria: TeamUserFindManyArgs, criteria: TeamUserFindManyArgs,
filters?: TeamUserSearchFilter, filters?: PageParams,
): Promise<FilterResult<TeamUser[]>> { ): Promise<PageResult<TeamUser[]>> {
const { query } = filters; const { query } = filters;
const where: Prisma.TeamUserWhereInput = { const where: Prisma.TeamUserWhereInput = {

View File

@ -1,7 +1,7 @@
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { ROLES } from 'lib/constants'; import { ROLES } from 'lib/constants';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
import { FilterResult, Role, User, UserSearchFilter } from 'lib/types'; import { PageResult, Role, User, PageParams } from 'lib/types';
import { getRandomChars } from 'next-basics'; import { getRandomChars } from 'next-basics';
import UserFindManyArgs = Prisma.UserFindManyArgs; import UserFindManyArgs = Prisma.UserFindManyArgs;
@ -49,8 +49,8 @@ export async function getUserByUsername(username: string, options: GetUserOption
export async function getUsers( export async function getUsers(
criteria: UserFindManyArgs, criteria: UserFindManyArgs,
filters?: UserSearchFilter, filters?: PageParams,
): Promise<FilterResult<User[]>> { ): Promise<PageResult<User[]>> {
const { query } = filters; const { query } = filters;
const where: Prisma.UserWhereInput = { const where: Prisma.UserWhereInput = {

View File

@ -1,6 +1,6 @@
import { Prisma, Website } from '@prisma/client'; import { Prisma, Website } from '@prisma/client';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
import { FilterResult, WebsiteSearchFilter } from 'lib/types'; import { PageResult, PageParams } from 'lib/types';
import WebsiteFindManyArgs = Prisma.WebsiteFindManyArgs; import WebsiteFindManyArgs = Prisma.WebsiteFindManyArgs;
async function findWebsite(criteria: Prisma.WebsiteFindUniqueArgs): Promise<Website> { async function findWebsite(criteria: Prisma.WebsiteFindUniqueArgs): Promise<Website> {
@ -25,8 +25,8 @@ export async function getSharedWebsite(shareId: string) {
export async function getWebsites( export async function getWebsites(
criteria: WebsiteFindManyArgs, criteria: WebsiteFindManyArgs,
filters: WebsiteSearchFilter, filters: PageParams,
): Promise<FilterResult<Website[]>> { ): Promise<PageResult<Website[]>> {
const { query } = filters; const { query } = filters;
const where: Prisma.WebsiteWhereInput = { const where: Prisma.WebsiteWhereInput = {
@ -53,8 +53,8 @@ export async function getAllWebsites(userId: string) {
export async function getUserWebsites( export async function getUserWebsites(
userId: string, userId: string,
filters?: WebsiteSearchFilter, filters?: PageParams,
): Promise<FilterResult<Website[]>> { ): Promise<PageResult<Website[]>> {
return getWebsites( return getWebsites(
{ {
where: { where: {
@ -78,8 +78,8 @@ export async function getUserWebsites(
export async function getTeamWebsites( export async function getTeamWebsites(
teamId: string, teamId: string,
filters?: WebsiteSearchFilter, filters?: PageParams,
): Promise<FilterResult<Website[]>> { ): Promise<PageResult<Website[]>> {
return getWebsites( return getWebsites(
{ {
where: { where: {