mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 07:25:48 +01:00
♻️ Rename registration token to invite, add created by
This commit is contained in:
@@ -42,17 +42,18 @@ model Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String?
|
name String?
|
||||||
email String? @unique
|
email String? @unique
|
||||||
emailVerified DateTime?
|
emailVerified DateTime?
|
||||||
image String?
|
image String?
|
||||||
password String?
|
password String?
|
||||||
salt String?
|
salt String?
|
||||||
isAdmin Boolean @default(false)
|
isAdmin Boolean @default(false)
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
settings UserSettings?
|
settings UserSettings?
|
||||||
|
createdInvites Invite[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model VerificationToken {
|
model VerificationToken {
|
||||||
@@ -63,10 +64,12 @@ model VerificationToken {
|
|||||||
@@unique([identifier, token])
|
@@unique([identifier, token])
|
||||||
}
|
}
|
||||||
|
|
||||||
model RegistrationToken {
|
model Invite {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
token String @unique
|
token String @unique
|
||||||
expires DateTime
|
expires DateTime
|
||||||
|
createdById String
|
||||||
|
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
model UserSettings {
|
model UserSettings {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"submit": "Register"
|
"submit": "Create account"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ export const Search = ({ isMobile }: SearchProps) => {
|
|||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
useHotkeys([['mod+K', () => ref.current?.focus()]]);
|
useHotkeys([['mod+K', () => ref.current?.focus()]]);
|
||||||
const { data: userWithSettings } = api.user.getWithSettings.useQuery();
|
const { data: userWithSettings } = api.user.withSettings.useQuery();
|
||||||
const { config } = useConfigContext();
|
const { config } = useConfigContext();
|
||||||
const { colors } = useMantineTheme();
|
const { colors } = useMantineTheme();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Button, Mark, Stack, Text } from '@mantine/core';
|
|||||||
import { ContextModalProps, modals } from '@mantine/modals';
|
import { ContextModalProps, modals } from '@mantine/modals';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export const CopyRegistrationToken = ({
|
export const CopyInviteModal = ({
|
||||||
context,
|
context,
|
||||||
id,
|
id,
|
||||||
innerProps,
|
innerProps,
|
||||||
@@ -5,24 +5,20 @@ import { ContextModalProps, modals } from '@mantine/modals';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
||||||
import { createRegistrationTokenSchema } from '~/validations/registration-token';
|
import { createInviteSchema } from '~/validations/invite';
|
||||||
|
|
||||||
export const CreateRegistrationTokenModal = ({
|
export const CreateInviteModal = ({ id }: ContextModalProps<{}>) => {
|
||||||
context,
|
|
||||||
id,
|
|
||||||
innerProps,
|
|
||||||
}: ContextModalProps<{}>) => {
|
|
||||||
const apiContext = api.useContext();
|
const apiContext = api.useContext();
|
||||||
const { isLoading, mutateAsync } = api.registrationTokens.createRegistrationToken.useMutation({
|
const { isLoading, mutateAsync } = api.invites.create.useMutation({
|
||||||
onSuccess: async (data) => {
|
onSuccess: async (data) => {
|
||||||
await apiContext.registrationTokens.getAllInvites.invalidate();
|
await apiContext.invites.all.invalidate();
|
||||||
modals.close(id);
|
modals.close(id);
|
||||||
|
|
||||||
modals.openContextModal({
|
modals.openContextModal({
|
||||||
modal: 'copyRegistrationTokenModal',
|
modal: 'copyInviteModal',
|
||||||
title: <Text weight="bold">Copy invitation</Text>,
|
title: <Text weight="bold">Copy invitation</Text>,
|
||||||
innerProps: data,
|
innerProps: data,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -35,14 +31,14 @@ export const CreateRegistrationTokenModal = ({
|
|||||||
initialValues: {
|
initialValues: {
|
||||||
expirationDate: dayjs().add(7, 'days').toDate(),
|
expirationDate: dayjs().add(7, 'days').toDate(),
|
||||||
},
|
},
|
||||||
validate: i18nZodResolver(createRegistrationTokenSchema),
|
validate: i18nZodResolver(createInviteSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text>
|
<Text>
|
||||||
After the expiration, a token will no longer be valid and the recipient of the token won't
|
After the expiration, an invite will no longer be valid and the recipient of the invite
|
||||||
be able to create an account.
|
won't be able to create an account.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<DateInput
|
<DateInput
|
||||||
@@ -2,15 +2,15 @@ import { Button, Group, Stack, Text } from '@mantine/core';
|
|||||||
import { ContextModalProps, modals } from '@mantine/modals';
|
import { ContextModalProps, modals } from '@mantine/modals';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
export const DeleteRegistrationTokenModal = ({
|
export const DeleteInviteModal = ({
|
||||||
context,
|
context,
|
||||||
id,
|
id,
|
||||||
innerProps,
|
innerProps,
|
||||||
}: ContextModalProps<{ tokenId: string }>) => {
|
}: ContextModalProps<{ tokenId: string }>) => {
|
||||||
const apiContext = api.useContext();
|
const apiContext = api.useContext();
|
||||||
const { isLoading, mutateAsync } = api.registrationTokens.deleteRegistrationToken.useMutation({
|
const { isLoading, mutateAsync } = api.invites.delete.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await apiContext.registrationTokens.getAllInvites.invalidate();
|
await apiContext.invites.all.invalidate();
|
||||||
modals.close(id);
|
modals.close(id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -18,7 +18,7 @@ export const DeleteRegistrationTokenModal = ({
|
|||||||
<Stack>
|
<Stack>
|
||||||
<Text>
|
<Text>
|
||||||
Are you sure, that you want to delete this invitation? Users with this link will no longer
|
Are you sure, that you want to delete this invitation? Users with this link will no longer
|
||||||
be able to register using that link.
|
be able to create an account using that link.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Group grow>
|
<Group grow>
|
||||||
@@ -10,7 +10,7 @@ export const DeleteUserModal = ({
|
|||||||
const apiContext = api.useContext();
|
const apiContext = api.useContext();
|
||||||
const { isLoading, mutateAsync } = api.user.deleteUser.useMutation({
|
const { isLoading, mutateAsync } = api.user.deleteUser.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await apiContext.user.getAll.invalidate();
|
await apiContext.user.all.invalidate();
|
||||||
modals.close(id);
|
modals.close(id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import { WidgetsEditModal } from '~/components/Dashboard/Tiles/Widgets/WidgetsEd
|
|||||||
import { WidgetsRemoveModal } from '~/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal';
|
import { WidgetsRemoveModal } from '~/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal';
|
||||||
import { CategoryEditModal } from '~/components/Dashboard/Wrappers/Category/CategoryEditModal';
|
import { CategoryEditModal } from '~/components/Dashboard/Wrappers/Category/CategoryEditModal';
|
||||||
|
|
||||||
import { DeleteUserModal } from './delete-user/delete-user.modal';
|
import { CopyInviteModal } from './copy-invite/copy-invite.modal';
|
||||||
import { CreateRegistrationTokenModal } from './create-registration-token/create-registration-token.modal';
|
|
||||||
import { DeleteRegistrationTokenModal } from './delete-registration-token/delete-registration-token.modal';
|
|
||||||
import { CreateDashboardModal } from './create-dashboard/create-dashboard.modal';
|
import { CreateDashboardModal } from './create-dashboard/create-dashboard.modal';
|
||||||
import { CopyRegistrationToken } from './copy-regristration-token/copy-registration-token.modal';
|
import { CreateInviteModal } from './create-invite/create-invite.modal';
|
||||||
import { DeleteBoardModal } from './delete-board/delete-board.modal';
|
import { DeleteBoardModal } from './delete-board/delete-board.modal';
|
||||||
|
import { DeleteInviteModal } from './delete-invite/delete-invite.modal';
|
||||||
|
import { DeleteUserModal } from './delete-user/delete-user.modal';
|
||||||
|
|
||||||
export const modals = {
|
export const modals = {
|
||||||
editApp: EditAppModal,
|
editApp: EditAppModal,
|
||||||
@@ -22,11 +22,11 @@ export const modals = {
|
|||||||
changeAppPositionModal: ChangeAppPositionModal,
|
changeAppPositionModal: ChangeAppPositionModal,
|
||||||
changeIntegrationPositionModal: ChangeWidgetPositionModal,
|
changeIntegrationPositionModal: ChangeWidgetPositionModal,
|
||||||
deleteUserModal: DeleteUserModal,
|
deleteUserModal: DeleteUserModal,
|
||||||
createRegistrationTokenModal: CreateRegistrationTokenModal,
|
createInviteModal: CreateInviteModal,
|
||||||
deleteRegistrationTokenModal: DeleteRegistrationTokenModal,
|
deleteInviteModal: DeleteInviteModal,
|
||||||
createDashboardModal: CreateDashboardModal,
|
createDashboardModal: CreateDashboardModal,
|
||||||
copyRegistrationTokenModal: CopyRegistrationToken,
|
copyInviteModal: CopyInviteModal,
|
||||||
deleteBoardModal: DeleteBoardModal
|
deleteBoardModal: DeleteBoardModal,
|
||||||
};
|
};
|
||||||
|
|
||||||
declare module '@mantine/modals' {
|
declare module '@mantine/modals' {
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { prisma } from '~/server/db';
|
import { prisma } from '~/server/db';
|
||||||
import { registerNamespaces } from '~/tools/server/translation-namespaces';
|
import { inviteNamespaces } from '~/tools/server/translation-namespaces';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
||||||
import { signUpFormSchema } from '~/validations/user';
|
import { signUpFormSchema } from '~/validations/user';
|
||||||
|
|
||||||
export default function AuthInvitePage() {
|
export default function AuthInvitePage() {
|
||||||
const { t } = useTranslation('authentication/register');
|
const { t } = useTranslation('authentication/invite');
|
||||||
const { i18nZodResolver } = useI18nZodResolver();
|
const { i18nZodResolver } = useI18nZodResolver();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const query = router.query as { token: string };
|
const query = router.query as { token: string };
|
||||||
const { mutateAsync } = api.user.register.useMutation();
|
const { mutateAsync } = api.user.createFromInvite.useMutation();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof signUpFormSchema>>({
|
const form = useForm<z.infer<typeof signUpFormSchema>>({
|
||||||
validateInputOnChange: true,
|
validateInputOnChange: true,
|
||||||
@@ -37,7 +37,7 @@ export default function AuthInvitePage() {
|
|||||||
void mutateAsync(
|
void mutateAsync(
|
||||||
{
|
{
|
||||||
...values,
|
...values,
|
||||||
registerToken: query.token,
|
inviteToken: query.token,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
@@ -124,7 +124,7 @@ export const getServerSideProps: GetServerSideProps = async ({ locale, query, pa
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await prisma.registrationToken.findUnique({
|
const token = await prisma.invite.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: routeParams.data.inviteId,
|
id: routeParams.data.inviteId,
|
||||||
token: queryParams.data.token,
|
token: queryParams.data.token,
|
||||||
@@ -139,7 +139,7 @@ export const getServerSideProps: GetServerSideProps = async ({ locale, query, pa
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
...(await serverSideTranslations(locale ?? '', registerNamespaces)),
|
...(await serverSideTranslations(locale ?? '', inviteNamespaces)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ const CreateNewUserPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const context = api.useContext();
|
const context = api.useContext();
|
||||||
const { mutateAsync, isLoading } = api.user.createUser.useMutation({
|
const { mutateAsync, isLoading } = api.user.create.useMutation({
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
void context.user.getAll.invalidate();
|
void context.user.all.invalidate();
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
nextStep();
|
nextStep();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const ManageUsersPage = () => {
|
|||||||
const [activePage, setActivePage] = useState(0);
|
const [activePage, setActivePage] = useState(0);
|
||||||
const [nonDebouncedSearch, setNonDebouncedSearch] = useState<string | undefined>('');
|
const [nonDebouncedSearch, setNonDebouncedSearch] = useState<string | undefined>('');
|
||||||
const [debouncedSearch] = useDebouncedValue<string | undefined>(nonDebouncedSearch, 200);
|
const [debouncedSearch] = useDebouncedValue<string | undefined>(nonDebouncedSearch, 200);
|
||||||
const { data } = api.user.getAll.useQuery({
|
const { data } = api.user.all.useQuery({
|
||||||
page: activePage,
|
page: activePage,
|
||||||
search: debouncedSearch,
|
search: debouncedSearch,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { api } from '~/utils/api';
|
|||||||
|
|
||||||
const ManageUserInvitesPage = () => {
|
const ManageUserInvitesPage = () => {
|
||||||
const [activePage, setActivePage] = useState(0);
|
const [activePage, setActivePage] = useState(0);
|
||||||
const { data } = api.registrationTokens.getAllInvites.useQuery({
|
const { data } = api.invites.all.useQuery({
|
||||||
page: activePage,
|
page: activePage,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,17 +40,17 @@ const ManageUserInvitesPage = () => {
|
|||||||
</Head>
|
</Head>
|
||||||
<Title mb="md">Manage user invites</Title>
|
<Title mb="md">Manage user invites</Title>
|
||||||
<Text mb="xl">
|
<Text mb="xl">
|
||||||
Using registration tokens, you can invite users to your Homarr instance. An invitation will
|
Using invites, you can invite users to your Homarr instance. An invitation will only be
|
||||||
only be valid for a certain time-span and can be used once. The expiration must be between 5
|
valid for a certain time-span and can be used once. The expiration must be between 5 minutes
|
||||||
minutes and 12 months upon creation.
|
and 12 months upon creation.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex justify="end" mb="md">
|
<Flex justify="end" mb="md">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modals.openContextModal({
|
modals.openContextModal({
|
||||||
modal: 'createRegistrationTokenModal',
|
modal: 'createInviteModal',
|
||||||
title: 'Create registration token',
|
title: 'Create invite',
|
||||||
innerProps: {},
|
innerProps: {},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -67,31 +67,35 @@ const ManageUserInvitesPage = () => {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
|
<th>Creator</th>
|
||||||
<th>Expires</th>
|
<th>Expires</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.registrationTokens.map((token, index) => (
|
{data.invites.map((invite, index) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td className={classes.tableIdCell}>
|
<td className={classes.tableGrowCell}>
|
||||||
<Text lineClamp={1}>{token.id}</Text>
|
<Text lineClamp={1}>{invite.id}</Text>
|
||||||
|
</td>
|
||||||
|
<td className={classes.tableGrowCell}>
|
||||||
|
<Text lineClamp={1}>{invite.creator}</Text>
|
||||||
</td>
|
</td>
|
||||||
<td className={classes.tableCell}>
|
<td className={classes.tableCell}>
|
||||||
{dayjs(dayjs()).isAfter(token.expires) ? (
|
{dayjs(dayjs()).isAfter(invite.expires) ? (
|
||||||
<Text>expired {dayjs(token.expires).fromNow()}</Text>
|
<Text>expired {dayjs(invite.expires).fromNow()}</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text>in {dayjs(token.expires).fromNow(true)}</Text>
|
<Text>in {dayjs(invite.expires).fromNow(true)}</Text>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className={classes.tableCell}>
|
<td className={classes.tableCell}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modals.openContextModal({
|
modals.openContextModal({
|
||||||
modal: 'deleteRegistrationTokenModal',
|
modal: 'deleteInviteModal',
|
||||||
title: <Text weight="bold">Delete registration token</Text>,
|
title: <Text weight="bold">Delete invite</Text>,
|
||||||
innerProps: {
|
innerProps: {
|
||||||
tokenId: token.id,
|
tokenId: invite.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -103,7 +107,7 @@ const ManageUserInvitesPage = () => {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
{data.registrationTokens.length === 0 && (
|
{data.invites.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={3}>
|
<td colSpan={3}>
|
||||||
<Center p="md">
|
<Center p="md">
|
||||||
@@ -137,8 +141,8 @@ const ManageUserInvitesPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = createStyles(() => ({
|
const useStyles = createStyles(() => ({
|
||||||
tableIdCell: {
|
tableGrowCell: {
|
||||||
width: '100%',
|
width: '50%',
|
||||||
},
|
},
|
||||||
tableCell: {
|
tableCell: {
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
|||||||
import { updateSettingsValidationSchema } from '~/validations/user';
|
import { updateSettingsValidationSchema } from '~/validations/user';
|
||||||
|
|
||||||
const PreferencesPage = ({ locale }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
|
const PreferencesPage = ({ locale }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
|
||||||
const { data } = api.user.getWithSettings.useQuery();
|
const { data } = api.user.withSettings.useQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManageLayout>
|
<ManageLayout>
|
||||||
@@ -34,7 +34,7 @@ export const [FormProvider, useFormContext, useForm] =
|
|||||||
const SettingsComponent = ({
|
const SettingsComponent = ({
|
||||||
settings,
|
settings,
|
||||||
}: {
|
}: {
|
||||||
settings: RouterOutputs['user']['getWithSettings']['settings'];
|
settings: RouterOutputs['user']['withSettings']['settings'];
|
||||||
}) => {
|
}) => {
|
||||||
const languagesData = languages.map((language) => ({
|
const languagesData = languages.map((language) => ({
|
||||||
image: 'https://img.icons8.com/clouds/256/000000/futurama-bender.png',
|
image: 'https://img.icons8.com/clouds/256/000000/futurama-bender.png',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { dnsHoleRouter } from './routers/dns-hole';
|
|||||||
import { dockerRouter } from './routers/docker/router';
|
import { dockerRouter } from './routers/docker/router';
|
||||||
import { downloadRouter } from './routers/download';
|
import { downloadRouter } from './routers/download';
|
||||||
import { iconRouter } from './routers/icon';
|
import { iconRouter } from './routers/icon';
|
||||||
|
import { inviteRouter } from './routers/invite';
|
||||||
import { mediaRequestsRouter } from './routers/media-request';
|
import { mediaRequestsRouter } from './routers/media-request';
|
||||||
import { mediaServerRouter } from './routers/media-server';
|
import { mediaServerRouter } from './routers/media-server';
|
||||||
import { overseerrRouter } from './routers/overseerr';
|
import { overseerrRouter } from './routers/overseerr';
|
||||||
@@ -15,7 +16,6 @@ import { rssRouter } from './routers/rss';
|
|||||||
import { usenetRouter } from './routers/usenet/router';
|
import { usenetRouter } from './routers/usenet/router';
|
||||||
import { userRouter } from './routers/user';
|
import { userRouter } from './routers/user';
|
||||||
import { weatherRouter } from './routers/weather';
|
import { weatherRouter } from './routers/weather';
|
||||||
import { inviteRouter } from './routers/registrationTokens';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the primary router for your server.
|
* This is the primary router for your server.
|
||||||
@@ -38,7 +38,7 @@ export const rootRouter = createTRPCRouter({
|
|||||||
usenet: usenetRouter,
|
usenet: usenetRouter,
|
||||||
calendar: calendarRouter,
|
calendar: calendarRouter,
|
||||||
weather: weatherRouter,
|
weather: weatherRouter,
|
||||||
registrationTokens: inviteRouter
|
invites: inviteRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|||||||
@@ -2,34 +2,42 @@ import { randomBytes } from 'crypto';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { createTRPCRouter, publicProcedure } from '../trpc';
|
import { adminProcedure, createTRPCRouter, publicProcedure } from '../trpc';
|
||||||
|
|
||||||
export const inviteRouter = createTRPCRouter({
|
export const inviteRouter = createTRPCRouter({
|
||||||
getAllInvites: publicProcedure
|
all: adminProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
limit: z.number().min(1).max(100).nullish().default(10),
|
limit: z.number().min(1).max(100).nullish().default(10),
|
||||||
page: z.number().min(0)
|
page: z.number().min(0),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const limit = input.limit ?? 50;
|
const limit = input.limit ?? 50;
|
||||||
const registrationTokens = await ctx.prisma.registrationToken.findMany({
|
const invites = await ctx.prisma.invite.findMany({
|
||||||
take: limit,
|
take: limit,
|
||||||
skip: limit * input.page,
|
skip: limit * input.page,
|
||||||
|
include: {
|
||||||
|
createdBy: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const countRegistrationTokens = await ctx.prisma.registrationToken.count();
|
const inviteCount = await ctx.prisma.invite.count();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
registrationTokens: registrationTokens.map((token) => ({
|
invites: invites.map((token) => ({
|
||||||
id: token.id,
|
id: token.id,
|
||||||
expires: token.expires,
|
expires: token.expires,
|
||||||
|
creator: token.createdBy.name,
|
||||||
})),
|
})),
|
||||||
countPages: Math.ceil(countRegistrationTokens / limit)
|
countPages: Math.ceil(inviteCount / limit),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
createRegistrationToken: publicProcedure
|
create: adminProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
expiration: z
|
expiration: z
|
||||||
@@ -39,9 +47,10 @@ export const inviteRouter = createTRPCRouter({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const token = await ctx.prisma.registrationToken.create({
|
const token = await ctx.prisma.invite.create({
|
||||||
data: {
|
data: {
|
||||||
expires: input.expiration,
|
expires: input.expiration,
|
||||||
|
createdById: ctx.session.user.id,
|
||||||
token: randomBytes(20).toString('hex'),
|
token: randomBytes(20).toString('hex'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -52,10 +61,10 @@ export const inviteRouter = createTRPCRouter({
|
|||||||
expires: token.expires,
|
expires: token.expires,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
deleteRegistrationToken: publicProcedure
|
delete: adminProcedure
|
||||||
.input(z.object({ tokenId: z.string() }))
|
.input(z.object({ tokenId: z.string() }))
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
await ctx.prisma.registrationToken.delete({
|
await ctx.prisma.invite.delete({
|
||||||
where: {
|
where: {
|
||||||
id: input.tokenId,
|
id: input.tokenId,
|
||||||
},
|
},
|
||||||
@@ -13,25 +13,25 @@ import { COOKIE_COLOR_SCHEME_KEY, COOKIE_LOCALE_KEY } from '../../../../data/con
|
|||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
|
||||||
|
|
||||||
export const userRouter = createTRPCRouter({
|
export const userRouter = createTRPCRouter({
|
||||||
register: publicProcedure
|
createFromInvite: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
signUpFormSchema.and(
|
signUpFormSchema.and(
|
||||||
z.object({
|
z.object({
|
||||||
registerToken: z.string(),
|
inviteToken: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const token = await ctx.prisma.registrationToken.findUnique({
|
const token = await ctx.prisma.invite.findUnique({
|
||||||
where: {
|
where: {
|
||||||
token: input.registerToken,
|
token: input.inviteToken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!token || token.expires < new Date()) {
|
if (!token || token.expires < new Date()) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'FORBIDDEN',
|
code: 'FORBIDDEN',
|
||||||
message: 'Invalid registration token',
|
message: 'Invalid invite token',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await ctx.prisma.registrationToken.delete({
|
await ctx.prisma.invite.delete({
|
||||||
where: {
|
where: {
|
||||||
id: token.id,
|
id: token.id,
|
||||||
},
|
},
|
||||||
@@ -115,7 +115,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
getWithSettings: protectedProcedure.query(async ({ ctx, input }) => {
|
withSettings: protectedProcedure.query(async ({ ctx, input }) => {
|
||||||
const user = await ctx.prisma.user.findUnique({
|
const user = await ctx.prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: ctx.session?.user?.id,
|
id: ctx.session?.user?.id,
|
||||||
@@ -158,7 +158,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getAll: publicProcedure
|
all: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
limit: z.number().min(1).max(100).default(10),
|
limit: z.number().min(1).max(100).default(10),
|
||||||
@@ -199,7 +199,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
countPages: Math.ceil(countUsers / limit),
|
countPages: Math.ceil(countUsers / limit),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
createUser: publicProcedure.input(createNewUserSchema).mutation(async ({ ctx, input }) => {
|
create: publicProcedure.input(createNewUserSchema).mutation(async ({ ctx, input }) => {
|
||||||
const salt = bcrypt.genSaltSync(10);
|
const salt = bcrypt.genSaltSync(10);
|
||||||
const hashedPassword = hashPassword(input.password, salt);
|
const hashedPassword = hashPassword(input.password, salt);
|
||||||
await ctx.prisma.user.create({
|
await ctx.prisma.user.create({
|
||||||
|
|||||||
@@ -51,4 +51,4 @@ export const dashboardNamespaces = [
|
|||||||
|
|
||||||
export const loginNamespaces = ['authentication/login', 'zod'];
|
export const loginNamespaces = ['authentication/login', 'zod'];
|
||||||
|
|
||||||
export const registerNamespaces = ['authentication/register', 'zod'];
|
export const inviteNamespaces = ['authentication/invite', 'zod'];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const createRegistrationTokenSchema = z.object({
|
export const createInviteSchema = z.object({
|
||||||
expiration: z
|
expiration: z
|
||||||
.date()
|
.date()
|
||||||
.min(dayjs().add(5, 'minutes').toDate())
|
.min(dayjs().add(5, 'minutes').toDate())
|
||||||
Reference in New Issue
Block a user