mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-08-22 16:47:16 -05:00
feat: add parsable error handling to api methods
This commit is contained in:
@@ -34,7 +34,6 @@ export default defineNuxtConfig({
|
||||
githubApiToken: '',
|
||||
stripeSecretKey: '',
|
||||
stripeNotificationEmail: '',
|
||||
hcaptchaSiteKey: '',
|
||||
hcaptchaSecretKey: '',
|
||||
grpcHost: '',
|
||||
grpcApiKey: '',
|
||||
@@ -57,7 +56,7 @@ export default defineNuxtConfig({
|
||||
public: {
|
||||
baseUrl: 'https://pretendo.network',
|
||||
apiBase: 'https://api.pretendo.cc',
|
||||
hCaptchaSitekey: '',
|
||||
hcaptchaSiteKey: '',
|
||||
cookieSecure: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,10 +8,7 @@ export default defineEventHandler(async (event): Promise<ApiAccountCheckoutLink>
|
||||
const stripe = useStripe(event);
|
||||
const config = useRuntimeConfig(event);
|
||||
if (!stripe || !papr) {
|
||||
throw createError({
|
||||
status: 400,
|
||||
message: 'Stripe integration not configured'
|
||||
});
|
||||
throw createApiError('INTEGRATION_DISABLED');
|
||||
}
|
||||
|
||||
const body = await readZodBody(event, CheckoutSchema);
|
||||
@@ -30,10 +27,7 @@ export default defineEventHandler(async (event): Promise<ApiAccountCheckoutLink>
|
||||
|
||||
// ensure PNID always has latest customer ID
|
||||
if (auth.accessLevel >= 2) {
|
||||
throw createError({
|
||||
status: 400,
|
||||
message: 'Staff members do not need to purchase tiers'
|
||||
});
|
||||
throw createApiError('STAFF_NO_DONATE');
|
||||
}
|
||||
await papr.Pnid.updateOne({ pid: auth.pid }, {
|
||||
$set: {
|
||||
|
||||
@@ -5,10 +5,7 @@ export default defineEventHandler(async (event): Promise<ApiAccountDiscordLink>
|
||||
enforceLoggedIn(event);
|
||||
const discord = useDiscord(event);
|
||||
if (!discord) {
|
||||
throw createError({
|
||||
status: 400,
|
||||
message: 'Discord integration not configured'
|
||||
});
|
||||
throw createApiError('INTEGRATION_DISABLED');
|
||||
}
|
||||
|
||||
const redirectUrl = discord.makeCallbackUrl();
|
||||
|
||||
@@ -4,10 +4,7 @@ export default defineEventHandler(async (event): Promise<void> => {
|
||||
const auth = enforceLoggedIn(event);
|
||||
const discord = useDiscord(event);
|
||||
if (!discord) {
|
||||
throw createError({
|
||||
status: 400,
|
||||
message: 'Discord integration not configured'
|
||||
});
|
||||
throw createApiError('INTEGRATION_DISABLED');
|
||||
}
|
||||
|
||||
const grpc = useApiGrpcWithToken(event, auth.token);
|
||||
|
||||
@@ -4,10 +4,7 @@ export default defineEventHandler(async (event): Promise<ApiAccountTiers> => {
|
||||
enforceLoggedIn(event);
|
||||
const stripe = useStripe(event);
|
||||
if (!stripe) {
|
||||
throw createError({
|
||||
status: 400,
|
||||
message: 'Stripe integration not configured'
|
||||
});
|
||||
throw createApiError('INTEGRATION_DISABLED');
|
||||
}
|
||||
|
||||
const prices = await stripe.prices.list().autoPagingToArray({ limit: 10 });
|
||||
|
||||
@@ -7,10 +7,7 @@ export default defineEventHandler(async (event): Promise<void> => {
|
||||
|
||||
const captchaResult = await hcaptchaVerify(event, body.captchaResponse);
|
||||
if (!captchaResult) {
|
||||
throw createError({
|
||||
status: 400,
|
||||
message: 'Invalid captcha'
|
||||
});
|
||||
throw createApiError('INVALID_CAPTCHA');
|
||||
}
|
||||
|
||||
await grpc.forgotPassword({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ServerError } from 'nice-grpc';
|
||||
import { ClientError } from 'nice-grpc';
|
||||
import { LoginSchema } from '#shared/api-types';
|
||||
import type { ApiAuthLogin } from '#shared/api-types';
|
||||
|
||||
@@ -18,18 +18,12 @@ export default defineEventHandler(async (event): Promise<ApiAuthLogin> => {
|
||||
refreshToken: res.refreshToken
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof ServerError) {
|
||||
if (error instanceof ClientError) {
|
||||
if (error.details === 'INVALID_ARGUMENT: User not found') {
|
||||
throw createError({
|
||||
status: 400,
|
||||
message: 'User not found'
|
||||
});
|
||||
throw createApiError('INVALID_USERNAME');
|
||||
}
|
||||
if (error.details === 'INVALID_ARGUMENT: Password is incorrect') {
|
||||
throw createError({
|
||||
status: 400,
|
||||
message: 'Password was incorrect'
|
||||
});
|
||||
throw createApiError('INVALID_PASSWORD');
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
|
||||
18
server/utils/errors.ts
Normal file
18
server/utils/errors.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { apiErrorCodeStatus, getTextForApiErrorCode } from '~~/shared/errors';
|
||||
import type { ApiErrorCodes, ApiError } from '~~/shared/errors';
|
||||
|
||||
export function createApiError(code: ApiErrorCodes) {
|
||||
const err = createApiErrorBase(code);
|
||||
return createError({
|
||||
statusCode: apiErrorCodeStatus[err.code],
|
||||
message: err.message,
|
||||
data: err
|
||||
});
|
||||
}
|
||||
|
||||
export function createApiErrorBase(code: ApiErrorCodes): ApiError {
|
||||
return {
|
||||
code,
|
||||
message: getTextForApiErrorCode(code)
|
||||
};
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export async function hcaptchaVerify(event: H3Event, captchaResponse: string | n
|
||||
if (!captchaResponse) {
|
||||
return false;
|
||||
} // No captcha filled in, invalid
|
||||
const captchaVerify = await hcaptcha.verify(config.hcaptchaSiteKey, captchaResponse, undefined, config.hcaptchaSiteKey);
|
||||
const captchaVerify = await hcaptcha.verify(config.hcaptchaSecretKey, captchaResponse, undefined, config.public.hcaptchaSiteKey);
|
||||
|
||||
if (!captchaVerify.success) {
|
||||
return false;
|
||||
|
||||
47
shared/errors.ts
Normal file
47
shared/errors.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
const apiErrorCodes = {
|
||||
UNPARSABLE_ERROR: 'Fatal exception!',
|
||||
UNHANDLED_ERROR: 'Something went wrong',
|
||||
INVALID_INPUT: 'Invalid input',
|
||||
INTEGRATION_DISABLED: 'Integration with this service is disabled',
|
||||
STAFF_NO_DONATE: 'Staff members do not need to purchase tiers',
|
||||
INVALID_CAPTCHA: 'Invalid captcha, try again',
|
||||
INVALID_USERNAME: 'Could not find user',
|
||||
INVALID_PASSWORD: 'Incorrect password'
|
||||
} as const;
|
||||
|
||||
export type ApiErrorCodes = keyof typeof apiErrorCodes;
|
||||
|
||||
export const apiErrorCodeStatus: Record<ApiErrorCodes, number> = {
|
||||
UNPARSABLE_ERROR: 500,
|
||||
UNHANDLED_ERROR: 500,
|
||||
INVALID_INPUT: 400,
|
||||
INTEGRATION_DISABLED: 500,
|
||||
STAFF_NO_DONATE: 400,
|
||||
INVALID_CAPTCHA: 400,
|
||||
INVALID_USERNAME: 400,
|
||||
INVALID_PASSWORD: 400
|
||||
};
|
||||
|
||||
export function getTextForApiErrorCode(code: ApiErrorCodes): string {
|
||||
return apiErrorCodes[code];
|
||||
}
|
||||
|
||||
export type ApiError = {
|
||||
code: ApiErrorCodes;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export function getApiError(error: any): ApiError {
|
||||
if (error?.code) {
|
||||
return error as ApiError;
|
||||
}
|
||||
|
||||
if (error?.data?.code) {
|
||||
return error.data as ApiError;
|
||||
}
|
||||
|
||||
return {
|
||||
code: 'UNPARSABLE_ERROR',
|
||||
message: getTextForApiErrorCode('UNPARSABLE_ERROR')
|
||||
};
|
||||
}
|
||||
10
src/composables/errors.ts
Normal file
10
src/composables/errors.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { FetchError } from 'ofetch';
|
||||
import { getApiError as baseGetApiError } from '~~/shared/errors';
|
||||
import type { ApiError } from '~~/shared/errors';
|
||||
|
||||
export function getApiError(error: any): ApiError {
|
||||
if (error instanceof FetchError) {
|
||||
return baseGetApiError(error.data);
|
||||
}
|
||||
return baseGetApiError(error);
|
||||
}
|
||||
41
src/plugins/types.d.ts
vendored
41
src/plugins/types.d.ts
vendored
@@ -4,4 +4,45 @@ declare module '#app' {
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'nuxt/schema' {
|
||||
interface RuntimeConfig {
|
||||
githubApiToken: string;
|
||||
|
||||
stripeSecretKey: string;
|
||||
stripeNotificationEmail: string;
|
||||
|
||||
hcaptchaSecretKey: string;
|
||||
|
||||
grpcHost: string;
|
||||
grpcApiKey: string;
|
||||
|
||||
mongoConnectionString: string;
|
||||
|
||||
smtpHost: string;
|
||||
smtpPort: number;
|
||||
smtpUser: string;
|
||||
smtpPassword: string;
|
||||
smtpSecure: true;
|
||||
smtpFromEmail: string;
|
||||
smtpFromName: string;
|
||||
|
||||
discordBotToken: string;
|
||||
discordClientId: string;
|
||||
discordClientSecret: string;
|
||||
discordGuildId: string;
|
||||
discordTesterRoleId: string;
|
||||
discordSupporterRoleId: string;
|
||||
|
||||
discourseSsoSecret: string;
|
||||
}
|
||||
|
||||
interface PublicRuntimeConfig {
|
||||
baseUrl: string;
|
||||
apiBase: string;
|
||||
cookieSecure: boolean;
|
||||
|
||||
hcaptchaSiteKey: string;
|
||||
}
|
||||
}
|
||||
|
||||
export { };
|
||||
|
||||
Reference in New Issue
Block a user