mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-24 18:26:20 +01:00
Fix teamWebsite / teamUser.
This commit is contained in:
parent
53b23420a4
commit
9eff565e7a
@ -4,7 +4,7 @@ import { Button, Dropdown, Form, FormButtons, FormRow, Item, SubmitButton } from
|
|||||||
import WebsiteTags from './WebsiteTags';
|
import WebsiteTags from './WebsiteTags';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function WebsiteAddTeamForm({ teamId, onSave, onClose }) {
|
export default function TeamAddWebsiteForm({ teamId, onSave, onClose }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { get, post, useQuery, useMutation } = useApi();
|
const { get, post, useQuery, useMutation } = useApi();
|
||||||
const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
|
const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
|
@ -2,18 +2,21 @@ import { Button, Form, FormButtons, SubmitButton } from 'react-basics';
|
|||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function TeamLeaveForm({ teamUserId, teamName, onSave, onClose }) {
|
export default function TeamLeaveForm({ teamId, userId, teamName, onSave, onClose }) {
|
||||||
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
||||||
const { del, useMutation } = useApi();
|
const { del, useMutation } = useApi();
|
||||||
const { mutate, error, isLoading } = useMutation(data => del(`/teamUsers/${teamUserId}`, data));
|
const { mutate, error, isLoading } = useMutation(() => del(`/team/${teamId}/users/${userId}`));
|
||||||
|
|
||||||
const handleSubmit = async data => {
|
const handleSubmit = async () => {
|
||||||
mutate(data, {
|
mutate(
|
||||||
onSuccess: async () => {
|
{},
|
||||||
onSave();
|
{
|
||||||
onClose();
|
onSuccess: async () => {
|
||||||
|
onSave();
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,14 +2,14 @@ import useApi from 'hooks/useApi';
|
|||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
|
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
|
||||||
|
|
||||||
export default function TeamMemberRemoveButton({ teamUserId, disabled, onSave }) {
|
export default function TeamMemberRemoveButton({ teamId, userId, disabled, onSave }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { del, useMutation } = useApi();
|
const { del, useMutation } = useApi();
|
||||||
const { mutate, isLoading } = useMutation(() => del(`/teamUsers/${teamUserId}`));
|
const { mutate, isLoading } = useMutation(() => del(`/team/${teamId}/users/${userId}`));
|
||||||
|
|
||||||
const handleRemoveTeamMember = () => {
|
const handleRemoveTeamMember = () => {
|
||||||
mutate(
|
mutate(
|
||||||
{ teamUserId },
|
{},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
onSave();
|
onSave();
|
||||||
|
@ -43,7 +43,8 @@ export default function TeamMembersTable({ data = [], onSave, readOnly }) {
|
|||||||
action: !readOnly && (
|
action: !readOnly && (
|
||||||
<Flexbox flex={1} justifyContent="end">
|
<Flexbox flex={1} justifyContent="end">
|
||||||
<TeamMemberRemoveButton
|
<TeamMemberRemoveButton
|
||||||
teamUserId={row.id}
|
teamId={row.teamId}
|
||||||
|
userId={row.userId}
|
||||||
disabled={user.id === row?.user?.id || row.role === ROLES.teamOwner}
|
disabled={user.id === row?.user?.id || row.role === ROLES.teamOwner}
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
></TeamMemberRemoveButton>
|
></TeamMemberRemoveButton>
|
||||||
|
@ -2,14 +2,14 @@ import useApi from 'hooks/useApi';
|
|||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
|
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
|
||||||
|
|
||||||
export default function TeamWebsiteRemoveButton({ teamWebsiteId, onSave }) {
|
export default function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { del, useMutation } = useApi();
|
const { del, useMutation } = useApi();
|
||||||
const { mutate, isLoading } = useMutation(() => del(`/teamWebsites/${teamWebsiteId}`));
|
const { mutate, isLoading } = useMutation(() => del(`/teams/${teamId}/websites/${websiteId}`));
|
||||||
|
|
||||||
const handleRemoveTeamMember = () => {
|
const handleRemoveTeamMember = () => {
|
||||||
mutate(
|
mutate(
|
||||||
{ teamWebsiteId },
|
{},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
onSave();
|
onSave();
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
} from 'react-basics';
|
} from 'react-basics';
|
||||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||||
import TeamWebsitesTable from 'components/pages/settings/teams/TeamWebsitesTable';
|
import TeamWebsitesTable from 'components/pages/settings/teams/TeamWebsitesTable';
|
||||||
import WebsiteAddTeamForm from 'components/pages/settings/teams/WebsiteAddTeamForm';
|
import TeamAddWebsiteForm from 'components/pages/settings/teams/TeamAddWebsiteForm';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ export default function TeamWebsites({ teamId }) {
|
|||||||
<Text>{formatMessage(labels.addWebsite)}</Text>
|
<Text>{formatMessage(labels.addWebsite)}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Modal title={formatMessage(labels.addWebsite)}>
|
<Modal title={formatMessage(labels.addWebsite)}>
|
||||||
{close => <WebsiteAddTeamForm teamId={teamId} onSave={handleSave} onClose={close} />}
|
{close => <TeamAddWebsiteForm teamId={teamId} onSave={handleSave} onClose={close} />}
|
||||||
</Modal>
|
</Modal>
|
||||||
</ModalTrigger>
|
</ModalTrigger>
|
||||||
);
|
);
|
||||||
|
@ -38,7 +38,7 @@ export default function TeamWebsitesTable({ data = [], onSave }) {
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{(row, keys, rowIndex) => {
|
{(row, keys, rowIndex) => {
|
||||||
const { id: teamWebsiteId } = row;
|
const { teamId } = row;
|
||||||
const { id: websiteId, name, domain, userId } = row.website;
|
const { id: websiteId, name, domain, userId } = row.website;
|
||||||
const { teamUser } = row.team;
|
const { teamUser } = row.team;
|
||||||
const owner = teamUser[0];
|
const owner = teamUser[0];
|
||||||
@ -59,7 +59,8 @@ export default function TeamWebsitesTable({ data = [], onSave }) {
|
|||||||
</Link>
|
</Link>
|
||||||
{canRemove && (
|
{canRemove && (
|
||||||
<TeamWebsiteRemoveButton
|
<TeamWebsiteRemoveButton
|
||||||
teamWebsiteId={teamWebsiteId}
|
teamId={teamId}
|
||||||
|
websiteId={websiteId}
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
></TeamWebsiteRemoveButton>
|
></TeamWebsiteRemoveButton>
|
||||||
)}
|
)}
|
||||||
|
@ -92,7 +92,8 @@ export default function TeamsTable({ data = [], onDelete }) {
|
|||||||
<Modal title={formatMessage(labels.leaveTeam)}>
|
<Modal title={formatMessage(labels.leaveTeam)}>
|
||||||
{close => (
|
{close => (
|
||||||
<TeamLeaveForm
|
<TeamLeaveForm
|
||||||
teamUserId={teamUserId}
|
teamId={id}
|
||||||
|
userId={user.id}
|
||||||
teamName={row.name}
|
teamName={row.name}
|
||||||
onSave={onDelete}
|
onSave={onDelete}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
|
@ -205,13 +205,13 @@ export async function canDeleteTeamUser({ user }: Auth, teamId: string, removeUs
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function canDeleteTeamWebsite({ user }: Auth, teamWebsiteId: string) {
|
export async function canDeleteTeamWebsite({ user }: Auth, teamId: string, websiteId: string) {
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validate(teamWebsiteId)) {
|
if (validate(teamId) && validate(websiteId)) {
|
||||||
const teamWebsite = await getTeamWebsite(teamWebsiteId);
|
const teamWebsite = await getTeamWebsite(teamId, websiteId);
|
||||||
|
|
||||||
if (teamWebsite.website.userId === user.id) {
|
if (teamWebsite.website.userId === user.id) {
|
||||||
return true;
|
return true;
|
||||||
@ -219,7 +219,7 @@ export async function canDeleteTeamWebsite({ user }: Auth, teamWebsiteId: string
|
|||||||
|
|
||||||
const teamUser = await getTeamUser(teamWebsite.teamId, user.id);
|
const teamUser = await getTeamUser(teamWebsite.teamId, user.id);
|
||||||
|
|
||||||
return hasPermission(teamUser.role, PERMISSIONS.teamDelete);
|
return hasPermission(teamUser.role, PERMISSIONS.teamUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
29
pages/api/teams/[id]/users/[userId].ts
Normal file
29
pages/api/teams/[id]/users/[userId].ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { canDeleteTeamUser } from 'lib/auth';
|
||||||
|
import { useAuth } from 'lib/middleware';
|
||||||
|
import { NextApiRequestQueryBody } from 'lib/types';
|
||||||
|
import { NextApiResponse } from 'next';
|
||||||
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
|
import { deleteTeamUser } from 'queries';
|
||||||
|
|
||||||
|
export interface TeamUserRequestQuery {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async (req: NextApiRequestQueryBody<TeamUserRequestQuery>, res: NextApiResponse) => {
|
||||||
|
await useAuth(req, res);
|
||||||
|
|
||||||
|
if (req.method === 'DELETE') {
|
||||||
|
const { id: teamId, userId } = req.query;
|
||||||
|
|
||||||
|
if (!(await canDeleteTeamUser(req.auth, teamId, userId))) {
|
||||||
|
return unauthorized(res, 'You must be the owner of this team.');
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteTeamUser(teamId, userId);
|
||||||
|
|
||||||
|
return ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
return methodNotAllowed(res);
|
||||||
|
};
|
@ -1,9 +1,9 @@
|
|||||||
import { NextApiRequestQueryBody } from 'lib/types';
|
import { canUpdateTeam, canViewTeam } from 'lib/auth';
|
||||||
import { canDeleteTeamUser, canUpdateTeam, canViewTeam } from 'lib/auth';
|
|
||||||
import { useAuth } from 'lib/middleware';
|
import { useAuth } from 'lib/middleware';
|
||||||
|
import { NextApiRequestQueryBody } 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, deleteTeamUser, getUser, getTeamUsers } from 'queries';
|
import { createTeamUser, getTeamUsers, getUser } from 'queries';
|
||||||
|
|
||||||
export interface TeamUserRequestQuery {
|
export interface TeamUserRequestQuery {
|
||||||
id: string;
|
id: string;
|
||||||
@ -12,7 +12,6 @@ export interface TeamUserRequestQuery {
|
|||||||
export interface TeamUserRequestBody {
|
export interface TeamUserRequestBody {
|
||||||
email: string;
|
email: string;
|
||||||
roleId: string;
|
roleId: string;
|
||||||
userId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (
|
export default async (
|
||||||
@ -52,17 +51,5 @@ export default async (
|
|||||||
return ok(res, updated);
|
return ok(res, updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'DELETE') {
|
|
||||||
const { userId } = req.body;
|
|
||||||
|
|
||||||
if (await canDeleteTeamUser(req.auth, teamId, userId)) {
|
|
||||||
return unauthorized(res, 'You must be the owner of this team.');
|
|
||||||
}
|
|
||||||
|
|
||||||
await deleteTeamUser(teamId, userId);
|
|
||||||
|
|
||||||
return ok(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
return methodNotAllowed(res);
|
return methodNotAllowed(res);
|
||||||
};
|
};
|
@ -5,24 +5,25 @@ 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';
|
||||||
|
|
||||||
export interface TeamWebsiteRequestQuery {
|
export interface TeamWebsitesRequestQuery {
|
||||||
id: string;
|
id: string;
|
||||||
|
websiteId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (
|
export default async (
|
||||||
req: NextApiRequestQueryBody<TeamWebsiteRequestQuery>,
|
req: NextApiRequestQueryBody<TeamWebsitesRequestQuery>,
|
||||||
res: NextApiResponse,
|
res: NextApiResponse,
|
||||||
) => {
|
) => {
|
||||||
await useAuth(req, res);
|
await useAuth(req, res);
|
||||||
|
|
||||||
const { id: teamWebsiteId } = req.query;
|
const { id: teamId, websiteId } = req.query;
|
||||||
|
|
||||||
if (req.method === 'DELETE') {
|
if (req.method === 'DELETE') {
|
||||||
if (!(await canDeleteTeamWebsite(req.auth, teamWebsiteId))) {
|
if (!(await canDeleteTeamWebsite(req.auth, teamId, websiteId))) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const websites = await deleteTeamWebsite(teamWebsiteId);
|
const websites = await deleteTeamWebsite(teamId, websiteId);
|
||||||
|
|
||||||
return ok(res, websites);
|
return ok(res, websites);
|
||||||
}
|
}
|
@ -1,16 +1,20 @@
|
|||||||
import { TeamWebsite, Prisma, Website, Team, User, TeamUser } from '@prisma/client';
|
import { Prisma, Team, TeamUser, TeamWebsite, Website } 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';
|
||||||
|
|
||||||
export async function getTeamWebsite(teamWebsiteId: string): Promise<
|
export async function getTeamWebsite(
|
||||||
|
teamId: string,
|
||||||
|
websiteId: string,
|
||||||
|
): Promise<
|
||||||
TeamWebsite & {
|
TeamWebsite & {
|
||||||
website: Website;
|
website: Website;
|
||||||
}
|
}
|
||||||
> {
|
> {
|
||||||
return prisma.client.teamWebsite.findFirst({
|
return prisma.client.teamWebsite.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: teamWebsiteId,
|
teamId,
|
||||||
|
websiteId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
website: true,
|
website: true,
|
||||||
@ -110,10 +114,14 @@ export async function createTeamWebsites(teamId: string, websiteIds: string[]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteTeamWebsite(teamWebsiteId: string): Promise<TeamWebsite> {
|
export async function deleteTeamWebsite(
|
||||||
return prisma.client.teamWebsite.delete({
|
teamId: string,
|
||||||
|
websiteId: string,
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return prisma.client.teamWebsite.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
id: teamWebsiteId,
|
teamId,
|
||||||
|
websiteId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user