Merge pull request #18 from Myzel394/add-api

Add api
This commit is contained in:
Myzel394 2023-03-11 15:35:44 +01:00 committed by GitHub
commit 434eaf06d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 24 deletions

View File

@ -96,8 +96,8 @@
}, },
"values": { "values": {
"scopes": { "scopes": {
"basic_profile": "Basic Profile", "read_profile": "Read Profile",
"full_profile": "Full Profile", "update_profile": "Update Profile",
"read_preferences": "Read Preferences", "read_preferences": "Read Preferences",
"update_preferences": "Update Preferences", "update_preferences": "Update Preferences",
@ -108,7 +108,16 @@
"delete_alias": "Delete Aliases", "delete_alias": "Delete Aliases",
"read_report": "Read Reports", "read_report": "Read Reports",
"delete_report": "Delete Reports" "delete_report": "Delete Reports",
"read_admin_cron_report": "Read Cron Reports (Admin)",
"read_admin_settings": "Read Settings (Admin)",
"update_admin_settings": "Update Settings (Admin)",
"read_admin_reserved_alias": "Read Reserved Aliases (Admin)",
"create_admin_reserved_alias": "Create Reserved Aliases (Admin)",
"update_admin_reserved_alias": "Update Reserved Aliases (Admin)",
"delete_admin_reserved_alias": "Delete Reserved Aliases (Admin)"
} }
} }
} }

View File

@ -1,4 +1,4 @@
import {AliasNote} from "~/server-types" import {APIKeyScope, AliasNote} from "~/server-types"
export const LOCAL_REGEX = /^[a-zA-Z0-9!#$%&*+/=?^_`.{|}~-]{1,64}$/g export const LOCAL_REGEX = /^[a-zA-Z0-9!#$%&*+/=?^_`.{|}~-]{1,64}$/g
export const URL_REGEX = export const URL_REGEX =
@ -16,9 +16,9 @@ export const DEFAULT_ALIAS_NOTE: AliasNote = {
export const ERROR_SNACKBAR_SHOW_DURATION = 5000 export const ERROR_SNACKBAR_SHOW_DURATION = 5000
export const SUCCESS_SNACKBAR_SHOW_DURATION = 2000 export const SUCCESS_SNACKBAR_SHOW_DURATION = 2000
export const AUTHENTICATION_PATHS = ["/auth/login", "/auth/signup", "/auth/complete-account"] export const AUTHENTICATION_PATHS = ["/auth/login", "/auth/signup", "/auth/complete-account"]
export const API_KEY_SCOPES = [ export const API_KEY_SCOPES: APIKeyScope[] = [
"basic_profile", "read:profile",
"full_profile", "update:profile",
"read:preferences", "read:preferences",
"update:preferences", "update:preferences",
@ -31,3 +31,13 @@ export const API_KEY_SCOPES = [
"read:report", "read:report",
"delete:report", "delete:report",
] ]
export const ADMIN_API_KEY_SCOPES: APIKeyScope[] = [
"read:admin_cron_report",
"read:admin_settings",
"update:admin_settings",
"read:admin_reserved_alias",
"create:admin_reserved_alias",
"update:admin_reserved_alias",
"delete:admin_reserved_alias",
]

View File

@ -31,15 +31,17 @@ import {BiText} from "react-icons/bi"
import {CgProfile} from "react-icons/cg" import {CgProfile} from "react-icons/cg"
import {DatePicker} from "@mui/x-date-pickers" import {DatePicker} from "@mui/x-date-pickers"
import {useLoaderData} from "react-router-dom" import {useLoaderData} from "react-router-dom"
import {API_KEY_SCOPES} from "~/constants/values" import {ADMIN_API_KEY_SCOPES, API_KEY_SCOPES} from "~/constants/values"
import {FaMask} from "react-icons/fa" import {FaMask, FaServer} from "react-icons/fa"
import {MdAdd, MdCancel, MdDelete, MdEdit, MdTextSnippet} from "react-icons/md" import {MdAdd, MdCancel, MdDelete, MdEdit, MdTextSnippet} from "react-icons/md"
import {GoSettings} from "react-icons/go" import {GoSettings} from "react-icons/go"
import {TiEye} from "react-icons/ti" import {TiEye} from "react-icons/ti"
import {useMutation} from "@tanstack/react-query" import {useMutation} from "@tanstack/react-query"
import {AxiosError} from "axios" import {AxiosError} from "axios"
import {parseFastAPIError} from "~/utils" import {parseFastAPIError} from "~/utils"
import {useErrorSuccessSnacks} from "~/hooks" import {useErrorSuccessSnacks, useUser} from "~/hooks"
import {HiDocumentReport} from "react-icons/hi"
import {BsStarFill} from "react-icons/bs"
import addDays from "date-fns/addDays" import addDays from "date-fns/addDays"
import diffInDays from "date-fns/differenceInDays" import diffInDays from "date-fns/differenceInDays"
import set from "date-fns/set" import set from "date-fns/set"
@ -48,6 +50,9 @@ export interface CreateNewAPIKeyDialogProps {
open: boolean open: boolean
onClose: () => void onClose: () => void
onCreated: (key: APIKey & {key: string}) => void onCreated: (key: APIKey & {key: string}) => void
prefilledLabel: string
prefilledScopes: APIKeyScope[]
} }
const PRESET_DAYS: number[] = [1, 7, 30, 180, 360] const PRESET_DAYS: number[] = [1, 7, 30, 180, 360]
@ -57,6 +62,9 @@ const API_KEY_SCOPE_ICON_MAP: Record<string, ReactElement> = {
alias: <FaMask />, alias: <FaMask />,
report: <MdTextSnippet />, report: <MdTextSnippet />,
preferences: <GoSettings />, preferences: <GoSettings />,
admin_cron_report: <HiDocumentReport />,
admin_settings: <FaServer />,
admin_reserved_alias: <BsStarFill />,
} }
const API_KEY_SCOPE_TYPE_ICON_MAP: Record<string, ReactElement> = { const API_KEY_SCOPE_TYPE_ICON_MAP: Record<string, ReactElement> = {
@ -76,12 +84,15 @@ const normalizeTime = (date: Date) =>
export default function CreateNewAPIKeyDialog({ export default function CreateNewAPIKeyDialog({
open, open,
prefilledLabel,
prefilledScopes,
onClose, onClose,
onCreated, onCreated,
}: CreateNewAPIKeyDialogProps): ReactElement { }: CreateNewAPIKeyDialogProps): ReactElement {
const {t} = useTranslation(["settings-api-keys", "common"]) const {t} = useTranslation(["settings-api-keys", "common"])
const serverSettings = useLoaderData() as ServerSettings const serverSettings = useLoaderData() as ServerSettings
const {showSuccess} = useErrorSuccessSnacks() const {showSuccess} = useErrorSuccessSnacks()
const user = useUser()
const scheme = yup.object().shape({ const scheme = yup.object().shape({
label: yup.string().required().label(t("create.form.label.label")), label: yup.string().required().label(t("create.form.label.label")),
@ -108,9 +119,9 @@ export default function CreateNewAPIKeyDialog({
const formik = useFormik<CreateAPIKeyData & {detail: string}>({ const formik = useFormik<CreateAPIKeyData & {detail: string}>({
validationSchema: scheme, validationSchema: scheme,
initialValues: { initialValues: {
label: "", label: prefilledLabel,
expiresAt: addDays(new Date(), 30), expiresAt: addDays(new Date(), 30),
scopes: [], scopes: [...prefilledScopes],
detail: "", detail: "",
}, },
onSubmit: async (values, {setErrors}) => { onSubmit: async (values, {setErrors}) => {
@ -121,6 +132,7 @@ export default function CreateNewAPIKeyDialog({
} }
}, },
}) })
const availableScopes = [...API_KEY_SCOPES, ...(user.isAdmin ? ADMIN_API_KEY_SCOPES : [])]
return ( return (
<Dialog open={open} onClose={onClose}> <Dialog open={open} onClose={onClose}>
@ -185,24 +197,20 @@ export default function CreateNewAPIKeyDialog({
</Box> </Box>
)} )}
> >
{API_KEY_SCOPES.map(scope => ( {availableScopes.map(scope => (
<MenuItem key={scope} value={scope}> <MenuItem key={scope} value={scope}>
<ListItem> <ListItem>
<ListItemIcon> <ListItemIcon>
<Badge <Badge
badgeContent={ badgeContent={
API_KEY_SCOPE_TYPE_ICON_MAP[ API_KEY_SCOPE_TYPE_ICON_MAP[
scope scope.split(":")[0]
.replace(":", "_")
.split("_")[0]
] ]
} }
> >
{ {
API_KEY_SCOPE_ICON_MAP[ API_KEY_SCOPE_ICON_MAP[
scope scope.split(":")[1]
.replace(":", "_")
.split("_")[1]
] ]
} }
</Badge> </Badge>

View File

@ -1,23 +1,46 @@
import {ReactElement, useState} from "react" import {ReactElement, useLayoutEffect, useState} from "react"
import {useTranslation} from "react-i18next" import {useTranslation} from "react-i18next"
import {useQuery} from "@tanstack/react-query" import {useQuery} from "@tanstack/react-query"
import {APIKey, PaginationResult} from "~/server-types" import {APIKey, APIKeyScope, PaginationResult} from "~/server-types"
import {AxiosError} from "axios" import {AxiosError} from "axios"
import {getAPIKeys} from "~/apis" import {getAPIKeys} from "~/apis"
import {QueryResult, SimplePage} from "~/components" import {QueryResult, SimplePage} from "~/components"
import {Button, List} from "@mui/material" import {Button, List} from "@mui/material"
import {MdAdd} from "react-icons/md" import {MdAdd} from "react-icons/md"
import {useQueryParams} from "~/hooks"
import {isArray} from "lodash"
import {API_KEY_SCOPES} from "~/constants/values"
import {useNavigate} from "react-router-dom"
import APIKeyListItem from "~/route-widgets/SettingsAPIKeysRoute/APIKeyListItem" import APIKeyListItem from "~/route-widgets/SettingsAPIKeysRoute/APIKeyListItem"
import CreateNewAPIKeyDialog from "../route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog" import CreateNewAPIKeyDialog from "../route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog"
import EmptyStateScreen from "~/route-widgets/SettingsAPIKeysRoute/EmptyStateScreen" import EmptyStateScreen from "~/route-widgets/SettingsAPIKeysRoute/EmptyStateScreen"
export default function SettingsAPIKeysRoute(): ReactElement { export default function SettingsAPIKeysRoute(): ReactElement {
const {t} = useTranslation("settings-api-keys") const {t} = useTranslation("settings-api-keys")
const navigate = useNavigate()
const rawParams = useQueryParams<{action?: any; scopes?: any; label?: any}>()
const params = {
action: rawParams.action === "create-new" ? "create-new" : undefined,
scopes: isArray(rawParams.scopes?.split(","))
? rawParams.scopes
.split(",")
.filter((scope: APIKeyScope) => API_KEY_SCOPES.includes(scope))
: [],
label: rawParams.label,
}
const queryKey = ["get_api_keys"] const queryKey = ["get_api_keys"]
const query = useQuery<PaginationResult<APIKey>, AxiosError>(queryKey, () => getAPIKeys()) const query = useQuery<PaginationResult<APIKey>, AxiosError>(queryKey, () => getAPIKeys())
const [createdAPIKey, setCreatedAPIKey] = useState<(APIKey & {key: string}) | null>(null) const [createdAPIKey, setCreatedAPIKey] = useState<(APIKey & {key: string}) | null>(null)
const [createNew, setCreateNew] = useState<boolean>(false) const [createNew, setCreateNew] = useState<boolean>(params.action === "create-new")
useLayoutEffect(() => {
if (params.action === "create-new") {
navigate(location.pathname, {replace: true})
}
}, [params.action, navigate])
return ( return (
<> <>
@ -60,6 +83,8 @@ export default function SettingsAPIKeysRoute(): ReactElement {
<CreateNewAPIKeyDialog <CreateNewAPIKeyDialog
key={createNew.toString()} key={createNew.toString()}
open={createNew} open={createNew}
prefilledScopes={params.scopes}
prefilledLabel={params.label}
onClose={() => setCreateNew(false)} onClose={() => setCreateNew(false)}
onCreated={key => { onCreated={key => {
query.refetch() query.refetch()

View File

@ -143,8 +143,8 @@ export interface AliasList {
} }
export type APIKeyScope = export type APIKeyScope =
| "full_profile" | "read:profile"
| "basic_profile" | "update:profile"
| "read:preferences" | "read:preferences"
| "update:preferences" | "update:preferences"
| "read:alias" | "read:alias"
@ -153,6 +153,13 @@ export type APIKeyScope =
| "delete:alias" | "delete:alias"
| "read:report" | "read:report"
| "delete:report" | "delete:report"
| "read:admin_cron_report"
| "read:admin_settings"
| "update:admin_settings"
| "read:admin_reserved_alias"
| "create:admin_reserved_alias"
| "update:admin_reserved_alias"
| "delete:admin_reserved_alias"
export interface APIKey { export interface APIKey {
id: string id: string