import { userIdentity, type UserIdentity } from '$lib/Data/AniList/identity'; import { addUserBadge, getUserBadges, removeAllUserBadges, removeUserBadge, setShadowHidden, setShadowHiddenBadge, updateUserBadge, type Badge as DatabaseBadge } from '$lib/Database/SB/User/badges'; import type { WithIndex } from '../$types'; import type { Resolvers, Badge } from './$types'; import type { RequestEvent } from '@sveltejs/kit'; import { getUserPreferences, setBiography, setCSS, setPinnedBadgeWallCategories, toggleHideAWCBadges, toggleHideMissingBadges, toggleHololiveStreamPinning, togglePinnedBadgeWallCategory, type UserPreferences } from '$lib/Database/SB/User/preferences'; import privilegedUser from '$lib/Utility/privilegedUser'; type Context = RequestEvent>, string | null>; const toGraphQLBadges = (databaseBadges: DatabaseBadge[]): Badge[] => databaseBadges.map((databaseBadge) => ({ id: databaseBadge.id ?? 0, post: databaseBadge.post ?? '', image: databaseBadge.image ?? '', time: databaseBadge.time ?? new Date().toISOString(), hidden: databaseBadge.hidden ?? false, shadow_hidden: databaseBadge.shadow_hidden ?? false, click_count: databaseBadge.click_count ?? 0, category: databaseBadge.category ?? null, description: databaseBadge.description ?? null, source: databaseBadge.source ?? null, designer: databaseBadge.designer ?? null })); const auth = async (context: Context) => { const userCookie = context.cookies.get('user'); if (!userCookie) return Error('Unauthorised'); const user = JSON.parse(userCookie); return await userIdentity({ tokenType: user['token_type'], expiresIn: user['expires_in'], accessToken: user['access_token'], refreshToken: user['refresh_token'] }); }; const authenticatedBadgesOperation = async ( context: Context, operation: (identity: UserIdentity, authorised: boolean) => Promise ) => { const identity = await auth(context); if (identity instanceof Error) throw new Error('Unauthorized'); const authorised = privilegedUser(identity.id); await operation(identity, authorised); const databaseBadges = await getUserBadges(identity.id); const badges = toGraphQLBadges(databaseBadges); return { id: identity.id, badges, preferences: null, badgesCount: badges.length }; }; const authenticatedPreferencesOperation = async ( context: Context, operation: (identity: UserIdentity, authorised: boolean) => Promise ) => { const identity = await auth(context); if (identity instanceof Error) throw new Error('Unauthorized'); const authorised = privilegedUser(identity.id); return { id: identity.id, badges: [] as Badge[], preferences: await operation(identity, authorised), badgesCount: 0 }; }; const ensureOwnerOrPrivileged = ( identity: UserIdentity, authorised: boolean, targetUserId: number ) => { if (!authorised && identity.id !== targetUserId) throw new Error('Unauthorized'); }; const ensureBadgeOwnerOrPrivileged = async ( identity: UserIdentity, authorised: boolean, badgeId: number ) => { if (authorised) return; const ownsBadge = (await getUserBadges(identity.id)).some((badge) => badge.id === badgeId); if (!ownsBadge) throw new Error('Unauthorized'); }; export const resolvers: WithIndex = { Query: { User: async (_, args) => { if (!args.id) return null; const databaseBadges = await getUserBadges(args.id); const badges = toGraphQLBadges(databaseBadges); return { id: args.id, badges, preferences: await getUserPreferences(args.id), badgesCount: badges.length }; }, badges: async (_, args) => { if (!args.id) return []; const databaseBadges = await getUserBadges(args.id, args.page || 0, args.size || 0); return toGraphQLBadges(databaseBadges); } }, Mutation: { shadowHideBadges: async (_, args, context) => await authenticatedBadgesOperation(context, async (identity, authorised) => { ensureOwnerOrPrivileged(identity, authorised, args.userId); await setShadowHidden(args.userId, authorised); }), shadowHideBadge: async (_, args, context) => await authenticatedBadgesOperation(context, async (identity, authorised) => { await ensureBadgeOwnerOrPrivileged(identity, authorised, args.id); await setShadowHiddenBadge(args.id, args.state == null ? true : args.state); }), hideBadge: async (_, args, context) => await authenticatedBadgesOperation(context, async (identity) => { const allBadges = await getUserBadges(identity.id); const category = args.category || ''; await Promise.all( allBadges .filter((badge) => badge.category === category) .map(async (badge) => { await updateUserBadge(identity.id, badge.id as number, { ...badge, hidden: allBadges .filter((badge) => badge.category === category) .filter((badge) => badge.hidden).length > allBadges.filter((badge) => badge.category === category).length / 2 ? false : true }); }) ); }), updateBadge: async (_, args, context) => await authenticatedBadgesOperation(context, async (identity) => { const badge = { post: args.post || undefined, image: args.image || undefined, description: args.description || null, time: args.time || undefined, category: args.category || null, hidden: args.hidden || false, source: args.source || null, designer: args.designer || null }; if ((await getUserBadges(identity.id)).find((badge) => badge.id === args.id)) { await updateUserBadge(identity.id, args.id as number, badge); } else { await addUserBadge(identity.id, badge); } }), deleteBadge: async (_, args, context) => await authenticatedBadgesOperation( context, async (identity) => await removeUserBadge(identity.id, args.id) ), pruneUserBadges: async (_, __, context) => await authenticatedBadgesOperation( context as Context, async (identity) => await removeAllUserBadges(identity.id) ), toggleHideMissingBadges: async (_, _args, context) => await authenticatedPreferencesOperation( context as Context, async (identity) => await toggleHideMissingBadges(identity.id) ), toggleHideAWCBadges: async (_, _args, context) => await authenticatedPreferencesOperation( context as Context, async (identity) => await toggleHideAWCBadges(identity.id) ), setBadgeWallCSS: async (_, args, context) => await authenticatedPreferencesOperation( context as Context, async (identity) => await setCSS(identity.id, args.css) ), togglePinnedBadgeWallCategory: async (_, args, context) => await authenticatedPreferencesOperation( context as Context, async (identity) => await togglePinnedBadgeWallCategory(identity.id, args.category) ), setPinnedBadgeWallCategories: async (_, args, context) => await authenticatedPreferencesOperation( context as Context, async (identity) => await setPinnedBadgeWallCategories(identity.id, args.categories) ), setBiography: async (_, args, context) => await authenticatedPreferencesOperation( context as Context, async (identity) => await setBiography(identity.id, args.biography.slice(0, 3000)) ), togglePinnedHololiveStream: async (_, args, context) => await authenticatedPreferencesOperation( context as Context, async (identity) => await toggleHololiveStreamPinning(identity.id, args.stream) ) } };