UNPKG

studiocms

Version:

Astro Native CMS for AstroDB. Built from the ground up by the Astro community.

169 lines (161 loc) 5.64 kB
import { eq } from 'astro:db'; import { GhostUserDefaults } from '../../../consts.js'; import { Effect } from '../../../effect.js'; import { SDKCoreError, StudioCMS_SDK_Error } from '../errors.js'; import { tsDiffTracking, tsOAuthAccounts, tsPageData, tsPermissions, tsSessionTable, tsUserResetTokens, } from '../tables.js'; import type { CombinedRank, SingleRank, tsPermissionsSelect, tsUsersSelect, } from '../types/index.js'; import { AstroDB } from './db.js'; /** * SDKCore_Users provides core user-related effects for the StudioCMS SDK. * * @remarks * This service includes utilities for verifying user ranks, combining rank data, * and clearing all references to a user from the database. It leverages the Effect system * for composable error handling and dependency injection. * * @example * ```typescript * const usersService = SDKCore_Users; * const result = usersService.verifyRank(users, permissions, 'admin'); * ``` * * @effect * - `verifyRank`: Filters users by rank based on permissions. * - `combineRanks`: Combines a given rank with user data. * - `clearUserReferences`: Removes all references to a user from related tables. * * @dependencies * - Depends on AstroDB for database operations. * * @accessors * - Provides accessors for each effect. */ export class SDKCore_Users extends Effect.Service<SDKCore_Users>()('studiocms/sdk/SDKCore_Users', { effect: Effect.gen(function* () { const dbService = yield* AstroDB; /** * Verifies the rank of users based on the provided permissions and rank. * * @param users - An array of user objects to be verified. * @param permissions - An array of permission objects that include user ranks. * @param rank - The rank to be verified against the permissions. * @returns An array of objects containing the id and name of users with the specified rank. */ const verifyRank = ( users: tsUsersSelect[], permissions: tsPermissionsSelect[], rank: string ): Effect.Effect<SingleRank[], SDKCoreError, never> => Effect.try({ try: () => { const filteredUsers = permissions.filter((user) => user.rank === rank); const permitted: SingleRank[] = []; for (const user of filteredUsers) { const foundUser = users.find((u) => u.id === user.user); if (foundUser) { permitted.push({ id: foundUser.id, name: foundUser.name }); } } return permitted; }, catch: (error) => new SDKCoreError({ type: 'UNKNOWN', cause: new StudioCMS_SDK_Error(`verifyRank Error: ${error}`), }), }); /** * Combines a given rank with an array of user ranks. * * @param rank - The rank to be combined with each user. * @param users - An array of user ranks to be combined with the given rank. * @returns An array of combined ranks, where each element includes the given rank and the properties of a user rank. */ const combineRanks = ( rank: string, users: SingleRank[] ): Effect.Effect<CombinedRank[], SDKCoreError, never> => Effect.try({ try: () => users.map((user) => ({ rank, ...user })) as CombinedRank[], catch: (error) => new SDKCoreError({ type: 'UNKNOWN', cause: new StudioCMS_SDK_Error(`combineRanks Error: ${error}`), }), }); /** * Clears all references to a specific user from various database tables. * * This function performs the following operations within a database transaction: * - Deletes user reset tokens associated with the given user ID. * - Deletes permissions associated with the given user ID. * - Deletes OAuth accounts associated with the given user ID. * - Deletes session records associated with the given user ID. * - Updates the `tsDiffTracking` table to replace the user's ID with a default "ghost user" ID. * - Updates the `tsPageData` table to replace the author's ID with a default "ghost user" ID. * * If any database operation fails, the transaction is rolled back, and an error is returned. * * @param userId - The ID of the user whose references should be cleared. * @returns An `Effect` that resolves to `true` if the operation succeeds, or fails with an `SDKCoreError` if an error occurs. * * @throws SDKCoreError - If a database error occurs during the operation. */ const clearUserReferences = (userId: string): Effect.Effect<boolean, SDKCoreError, never> => Effect.gen(function* () { yield* dbService.execute((db) => db.delete(tsUserResetTokens).where(eq(tsUserResetTokens.userId, userId)) ); yield* dbService.execute((db) => db.delete(tsPermissions).where(eq(tsPermissions.user, userId)) ); yield* dbService.execute((db) => db.delete(tsOAuthAccounts).where(eq(tsOAuthAccounts.userId, userId)) ); yield* dbService.execute((db) => db.delete(tsSessionTable).where(eq(tsSessionTable.userId, userId)) ); yield* dbService.execute((db) => db .update(tsDiffTracking) .set({ userId: GhostUserDefaults.id }) .where(eq(tsDiffTracking.userId, userId)) ); yield* dbService.execute((db) => db .update(tsPageData) .set({ authorId: GhostUserDefaults.id }) .where(eq(tsPageData.authorId, userId)) ); return true; }).pipe( Effect.catchTags({ LibSQLClientError: (cause) => Effect.fail( new SDKCoreError({ type: 'LibSQLDatabaseError', cause: new StudioCMS_SDK_Error(`Error collecting page data: ${cause}`), }) ), }) ); return { verifyRank, combineRanks, clearUserReferences, }; }), dependencies: [AstroDB.Default], }) {}