diff --git a/src/actions/Types.ts b/src/actions/Types.ts deleted file mode 100644 index dcdff4409..000000000 --- a/src/actions/Types.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ErrorMessage, ErrorCode } from '@/services/error' - -export type ActionReturnError = { - success: false, - errorCode: ErrorCode, - httpCode: number, - error?: ErrorMessage[], -} - -export type ActionReturn = ( - ActionReturnError -) | { - success: true, -} & ( - DataGuarantee extends true ? { - data: ReturnType - } : { - data?: ReturnType - } -) - -export type Action = (formData: FormData) => ( - Promise> -) diff --git a/src/actions/admission/create.ts b/src/actions/admission/create.ts deleted file mode 100644 index 667035e50..000000000 --- a/src/actions/admission/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { AdmissionMethods } from '@/services/admission/methods' -import { action } from '@/actions/action' - -export const createAdmissionTrialAction = action(AdmissionMethods.createTrial) diff --git a/src/actions/api-keys/create.ts b/src/actions/api-keys/create.ts deleted file mode 100644 index 5a423e788..000000000 --- a/src/actions/api-keys/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { ApiKeyMethods } from '@/services/api-keys/methods' - -export const createApiKeyAction = action(ApiKeyMethods.create) diff --git a/src/actions/api-keys/destroy.ts b/src/actions/api-keys/destroy.ts deleted file mode 100644 index 9733a0653..000000000 --- a/src/actions/api-keys/destroy.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { ApiKeyMethods } from '@/services/api-keys/methods' - -export const destroyApiKeyAction = action(ApiKeyMethods.destroy) diff --git a/src/actions/api-keys/read.ts b/src/actions/api-keys/read.ts deleted file mode 100644 index 8ed3511a3..000000000 --- a/src/actions/api-keys/read.ts +++ /dev/null @@ -1,6 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { ApiKeyMethods } from '@/services/api-keys/methods' - -export const readApiKeysAction = action(ApiKeyMethods.readMany) -export const readApiKeyAction = action(ApiKeyMethods.read) diff --git a/src/actions/api-keys/update.ts b/src/actions/api-keys/update.ts deleted file mode 100644 index 82b1a6f8d..000000000 --- a/src/actions/api-keys/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { ApiKeyMethods } from '@/services/api-keys/methods' - -export const updateApiKeyAction = action(ApiKeyMethods.update) diff --git a/src/actions/applications/create.ts b/src/actions/applications/create.ts deleted file mode 100644 index 38e82751a..000000000 --- a/src/actions/applications/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { ApplicationMethods } from '@/services/applications/methods' -import { action } from '@/actions/action' - -export const createApplicationAction = action(ApplicationMethods.create) diff --git a/src/actions/applications/destroy.ts b/src/actions/applications/destroy.ts deleted file mode 100644 index 4c406f12a..000000000 --- a/src/actions/applications/destroy.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { ApplicationMethods } from '@/services/applications/methods' - -export const destroyApplicationAction = action(ApplicationMethods.destroy) diff --git a/src/actions/applications/periods/create.ts b/src/actions/applications/periods/create.ts deleted file mode 100644 index 7ea51d868..000000000 --- a/src/actions/applications/periods/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { ApplicationPeriodMethods } from '@/services/applications/periods/methods' - -export const createApplicationPeriodAction = action(ApplicationPeriodMethods.create) diff --git a/src/actions/applications/periods/destroy.ts b/src/actions/applications/periods/destroy.ts deleted file mode 100644 index 7af055abc..000000000 --- a/src/actions/applications/periods/destroy.ts +++ /dev/null @@ -1,6 +0,0 @@ -'use server' -import { ApplicationPeriodMethods } from '@/services/applications/periods/methods' -import { action } from '@/actions/action' - -export const destroyApplicationPeriodAction = action(ApplicationPeriodMethods.destroy) -export const removeAllApplicationTextsAction = action(ApplicationPeriodMethods.removeAllApplicationTexts) diff --git a/src/actions/applications/periods/read.ts b/src/actions/applications/periods/read.ts deleted file mode 100644 index 78d1e8521..000000000 --- a/src/actions/applications/periods/read.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use server' -import { ApplicationPeriodMethods } from '@/services/applications/periods/methods' -import { action } from '@/actions/action' - -export const readApplicationPeriodsAction = action(ApplicationPeriodMethods.readAll) -export const readApplicationPeriodAction = action(ApplicationPeriodMethods.read) -export const readNumberOfApplicationsAction = action(ApplicationPeriodMethods.readNumberOfApplications) diff --git a/src/actions/applications/periods/update.ts b/src/actions/applications/periods/update.ts deleted file mode 100644 index 0ab295162..000000000 --- a/src/actions/applications/periods/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { ApplicationPeriodMethods } from '@/services/applications/periods/methods' -import { action } from '@/actions/action' - -export const updateApplicationPeriodAction = action(ApplicationPeriodMethods.update) diff --git a/src/actions/applications/read.ts b/src/actions/applications/read.ts deleted file mode 100644 index d87f4f4d2..000000000 --- a/src/actions/applications/read.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { ApplicationMethods } from '@/services/applications/methods' - -export const readApplicationsForUserAction = action(ApplicationMethods.readForUser) diff --git a/src/actions/applications/update.ts b/src/actions/applications/update.ts deleted file mode 100644 index 6b576cfc3..000000000 --- a/src/actions/applications/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { ApplicationMethods } from '@/services/applications/methods' -import { action } from '@/actions/action' - -export const updateApplicationAction = action(ApplicationMethods.update) diff --git a/src/actions/auth/auth.ts b/src/actions/auth/auth.ts deleted file mode 100644 index f391b6325..000000000 --- a/src/actions/auth/auth.ts +++ /dev/null @@ -1,8 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { AuthMethods } from '@/services/auth/methods' - -export const verifyResetPasswordTokenAction = action(AuthMethods.verifyResetPasswordToken) -export const resetPasswordAction = action(AuthMethods.resetPassword) -export const sendResetPasswordEmailAction = action(AuthMethods.sendResetPasswordEmail) -export const verifyEmailAction = action(AuthMethods.verifyEmail) diff --git a/src/actions/cabin/index.ts b/src/actions/cabin/index.ts deleted file mode 100644 index 360c65457..000000000 --- a/src/actions/cabin/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { CabinBookingMethods } from '@/services/cabin/booking/methods' -import { CabinPricePeriodMethods } from '@/services/cabin/pricePeriod/methods' -import { CabinProductMethods } from '@/services/cabin/product/methods' -import { CabinReleasePeriodMethods } from '@/services/cabin/releasePeriod/methods' - -export const createReleasePeriodAction = action(CabinReleasePeriodMethods.create) -export const readReleasePeriodsAction = action(CabinReleasePeriodMethods.readMany) -export const updateReleasePeriodAction = action(CabinReleasePeriodMethods.update) -export const destroyReleasePeriodAction = action(CabinReleasePeriodMethods.destroy) - -export const createPricePeriodAction = action(CabinPricePeriodMethods.create) -export const destoryPricePeriodAction = action(CabinPricePeriodMethods.destroy) -export const readPricePeriodsAction = action(CabinPricePeriodMethods.readMany) -export const readPublicPricePeriodsAction = action(CabinPricePeriodMethods.readPublicPeriods) -export const readUnreleasedPricePeriodsAction = action(CabinPricePeriodMethods.readUnreleasedPeriods) - -export const createCabinBookingUserAttachedAction = action(CabinBookingMethods.createCabinBookingUserAttached) -export const createBedBookingUserAttachedAction = action(CabinBookingMethods.createBedBookingUserAttached) -export const createCabinBookingNoUserAction = action(CabinBookingMethods.createCabinBookingNoUser) -export const createBedBookingNoUserAction = action(CabinBookingMethods.createBedBookingNoUser) -export const readCabinAvailabilityAction = action(CabinBookingMethods.readAvailability) -export const readCabinBookingsAction = action(CabinBookingMethods.readMany) -export const readCabinBookingAction = action(CabinBookingMethods.read) - -export const readCabinProductsAction = action(CabinProductMethods.readMany) -export const readCabinProductsActiveAction = action(CabinProductMethods.readActive) -export const readCabinProductAction = action(CabinProductMethods.read) -export const createCabinProductAction = action(CabinProductMethods.create) -export const createCabinProductPriceAction = action(CabinProductMethods.createPrice) diff --git a/src/actions/career/companies/create.ts b/src/actions/career/companies/create.ts deleted file mode 100644 index a0da9c443..000000000 --- a/src/actions/career/companies/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { CompanyMethods } from '@/services/career/companies/methods' - -export const createCompanyAction = action(CompanyMethods.create) diff --git a/src/actions/career/companies/destroy.ts b/src/actions/career/companies/destroy.ts deleted file mode 100644 index b92d1f595..000000000 --- a/src/actions/career/companies/destroy.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { CompanyMethods } from '@/services/career/companies/methods' - -export const destroyCompanyAction = action(CompanyMethods.destroy) diff --git a/src/actions/career/companies/read.ts b/src/actions/career/companies/read.ts deleted file mode 100644 index 0716b6876..000000000 --- a/src/actions/career/companies/read.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { CompanyMethods } from '@/services/career/companies/methods' - -export const readCompanyPageAction = action(CompanyMethods.readPage) diff --git a/src/actions/career/companies/update.ts b/src/actions/career/companies/update.ts deleted file mode 100644 index fc1639e68..000000000 --- a/src/actions/career/companies/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { CompanyMethods } from '@/services/career/companies/methods' - -export const updateComanyAction = action(CompanyMethods.update) diff --git a/src/actions/career/jobAds/create.ts b/src/actions/career/jobAds/create.ts deleted file mode 100644 index 146939c51..000000000 --- a/src/actions/career/jobAds/create.ts +++ /dev/null @@ -1,6 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { JobadMethods } from '@/services/career/jobAds/methods' - -export const createJobAdAction = action(JobadMethods.create) - diff --git a/src/actions/career/jobAds/destroy.ts b/src/actions/career/jobAds/destroy.ts deleted file mode 100644 index 2f3583458..000000000 --- a/src/actions/career/jobAds/destroy.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { JobadMethods } from '@/services/career/jobAds/methods' - -export const destroyJobAdAction = action(JobadMethods.destroy) diff --git a/src/actions/career/jobAds/read.ts b/src/actions/career/jobAds/read.ts deleted file mode 100644 index 4cb4f75b6..000000000 --- a/src/actions/career/jobAds/read.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { JobadMethods } from '@/services/career/jobAds/methods' - -export const readJobAdAction = action(JobadMethods.read) -export const readActiveJobAdsAction = action(JobadMethods.readActive) -export const readInactiveJobAdsPageAction = action(JobadMethods.readInactivePage) diff --git a/src/actions/career/jobAds/update.ts b/src/actions/career/jobAds/update.ts deleted file mode 100644 index 666d1b5a8..000000000 --- a/src/actions/career/jobAds/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { JobadMethods } from '@/services/career/jobAds/methods' - -export const updateJobAdAction = action(JobadMethods.update) diff --git a/src/actions/cms/articleCategories/create.ts b/src/actions/cms/articleCategories/create.ts deleted file mode 100644 index db0ac7686..000000000 --- a/src/actions/cms/articleCategories/create.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use server' -import { createZodActionError } from '@/actions/error' -import { createArticleCategory } from '@/services/cms/articleCategories/create' -import { safeServerCall } from '@/actions/safeServerCall' -import { createArticleCategoryValidation } from '@/services/cms/articleCategories/validation' -import type { CreateArticleCategoryTypes } from '@/services/cms/articleCategories/validation' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedArticleCategory } from '@/cms/articleCategories/Types' - -export async function createArticleCategoryAction( - rawData: FormData | CreateArticleCategoryTypes['Type'] -): Promise> { - //TODO: check permission - const parse = createArticleCategoryValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createArticleCategory(data)) -} diff --git a/src/actions/cms/articleCategories/destroy.ts b/src/actions/cms/articleCategories/destroy.ts deleted file mode 100644 index 0f1d2777a..000000000 --- a/src/actions/cms/articleCategories/destroy.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use server' -import { destroyArticleCategory } from '@/services/cms/articleCategories/destroy' -import { safeServerCall } from '@/actions/safeServerCall' -import type { ExpandedArticleCategory } from '@/cms/articleCategories/Types' -import type { ActionReturn } from '@/actions/Types' - -export async function destroyArticleCategoryAction(id: number): Promise> { - // TODO: Cheek for visibility type edit of user. - return await safeServerCall(() => destroyArticleCategory(id)) -} diff --git a/src/actions/cms/articleCategories/read.ts b/src/actions/cms/articleCategories/read.ts deleted file mode 100644 index 565d08268..000000000 --- a/src/actions/cms/articleCategories/read.ts +++ /dev/null @@ -1,18 +0,0 @@ -'use server' -import { readArticleCategories, readArticleCategory } from '@/services/cms/articleCategories/read' -import { safeServerCall } from '@/actions/safeServerCall' -import type { ActionReturn } from '@/actions/Types' -import type { - ExpandedArticleCategoryWithCover, - ArticleCategoryWithCover, -} from '@/cms/articleCategories/Types' - -export async function readArticleCategoriesAction(): Promise> { - //TODO: only read categories that user has visibility - return await safeServerCall(() => readArticleCategories()) -} - -export async function readArticleCategoryAction(name: string): Promise> { - //TODO: only read if right visibility - return await safeServerCall(() => readArticleCategory(name)) -} diff --git a/src/actions/cms/articleCategories/update.ts b/src/actions/cms/articleCategories/update.ts deleted file mode 100644 index bb04c9d2d..000000000 --- a/src/actions/cms/articleCategories/update.ts +++ /dev/null @@ -1,29 +0,0 @@ -'use server' -import { createZodActionError } from '@/actions/error' -import { updateArticleCategory } from '@/services/cms/articleCategories/update' -import { safeServerCall } from '@/actions/safeServerCall' -import { updateArticleCategoryValidation } from '@/services/cms/articleCategories/validation' -import type { UpdateArticleCategoryTypes } from '@/services/cms/articleCategories/validation' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedArticleCategory } from '@/cms/articleCategories/Types' - -export async function updateArticleCategoryVisibilityAction( - // disable eslint rule temporarily until function is implemented - // eslint-disable-next-line @typescript-eslint/no-unused-vars - id: number, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - visibility: unknown -): Promise> { - throw new Error('Not implemented') -} - -export async function updateArticleCategoryAction( - id: number, - rawData: FormData | UpdateArticleCategoryTypes['Type'] -): Promise> { - const parse = updateArticleCategoryValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => updateArticleCategory(id, data)) -} diff --git a/src/actions/cms/articleSections/create.ts b/src/actions/cms/articleSections/create.ts deleted file mode 100644 index 43bb6fe0e..000000000 --- a/src/actions/cms/articleSections/create.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use server' -import { createArticleSection } from '@/services/cms/articleSections/create' -import { safeServerCall } from '@/actions/safeServerCall' -import { createZodActionError } from '@/actions/error' -import { createArticleSectionValidation } from '@/services/cms/articleSections/validation' -import type { CreateArticleSectionTypes } from '@/services/cms/articleSections/validation' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedArticleSection } from '@/cms/articleSections/Types' - -export async function createArticleSectionAction( - rawData: FormData | CreateArticleSectionTypes['Type'], -): Promise> { - //TODO: Auth on general cms permission - const parse = createArticleSectionValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createArticleSection(data)) -} diff --git a/src/actions/cms/articleSections/destroy.ts b/src/actions/cms/articleSections/destroy.ts deleted file mode 100644 index 625c14f2a..000000000 --- a/src/actions/cms/articleSections/destroy.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use server' -import { destroyArticleSection } from '@/services/cms/articleSections/destroy' -import { safeServerCall } from '@/actions/safeServerCall' -import type { ActionReturn } from '@/actions/Types' -import type { ArticleSection } from '@prisma/client' - -export async function destroyArticleSectionAction(nameOrId: string): Promise> { - //Auth by visibility - return await safeServerCall(() => destroyArticleSection(nameOrId)) -} diff --git a/src/actions/cms/articleSections/read.ts b/src/actions/cms/articleSections/read.ts deleted file mode 100644 index 9b52c0ef1..000000000 --- a/src/actions/cms/articleSections/read.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use server' -import { readArticleSection } from '@/services/cms/articleSections/read' -import { safeServerCall } from '@/actions/safeServerCall' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedArticleSection } from '@/cms/articleSections/Types' - -export async function readArticleSectionAction(name: string): Promise> { - //TODO: Auth by visibility - return await safeServerCall(() => readArticleSection(name)) -} diff --git a/src/actions/cms/articleSections/update.ts b/src/actions/cms/articleSections/update.ts deleted file mode 100644 index 08594e9df..000000000 --- a/src/actions/cms/articleSections/update.ts +++ /dev/null @@ -1,31 +0,0 @@ -'use server' -import { addArticleSectionPart, removeArticleSectionPart, updateArticleSection } from '@/services/cms/articleSections/update' -import { safeServerCall } from '@/actions/safeServerCall' -import type { ArticleSection, Position } from '@prisma/client' -import type { ArticleSectionPart, ExpandedArticleSection } from '@/cms/articleSections/Types' -import type { ActionReturn } from '@/actions/Types' - - -export async function updateArticleSectionAction(name: string, changes: { - imageSize?: number, - imagePosition?: Position, -}): Promise> { - //Todo: Auth by visibilty - return await safeServerCall(() => updateArticleSection(name, changes)) -} - -export async function addArticleSectionPartAction( - name: string, - part: ArticleSectionPart -): Promise> { - //Todo: Auth by visibilty - return await safeServerCall(() => addArticleSectionPart(name, part)) -} - -export async function removeArticleSectionPartAction( - name: string, - part: ArticleSectionPart -): Promise> { - //TODO: Auth by visibility - return await safeServerCall(() => removeArticleSectionPart(name, part)) -} diff --git a/src/actions/cms/articles/create.ts b/src/actions/cms/articles/create.ts deleted file mode 100644 index 34de74837..000000000 --- a/src/actions/cms/articles/create.ts +++ /dev/null @@ -1,21 +0,0 @@ -'use server' -import { createArticle } from '@/services/cms/articles/create' -import { safeServerCall } from '@/actions/safeServerCall' -import { createZodActionError } from '@/actions/error' -import { createArticleValidation } from '@/services/cms/articles/validation' -import type { CreateArticleTypes } from '@/services/cms/articles/validation' -import type { ExpandedArticle } from '@/cms/articles/Types' -import type { ActionReturn } from '@/actions/Types' - -export async function createArticleAction( - rawData: FormData | CreateArticleTypes['Type'], - categoryId?: number, -): Promise> { - //TODO: auth on permission or visibility to categoryId - - const parse = createArticleValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createArticle(data, categoryId)) -} diff --git a/src/actions/cms/articles/destroy.ts b/src/actions/cms/articles/destroy.ts deleted file mode 100644 index 24c846f1d..000000000 --- a/src/actions/cms/articles/destroy.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use server' -import { destroyArticle } from '@/services/cms/articles/destroy' -import { safeServerCall } from '@/actions/safeServerCall' -import type { ActionReturn } from '@/actions/Types' -import type { Article } from '@prisma/client' - -export async function destroyArticleAction(id: number): Promise> { - //TODO: auth - return await safeServerCall(() => destroyArticle(id)) -} diff --git a/src/actions/cms/articles/read.ts b/src/actions/cms/articles/read.ts deleted file mode 100644 index 03db6231c..000000000 --- a/src/actions/cms/articles/read.ts +++ /dev/null @@ -1,13 +0,0 @@ -'use server' -import { readArticle } from '@/services/cms/articles/read' -import { safeServerCall } from '@/actions/safeServerCall' -import type { ExpandedArticle } from '@/cms/articles/Types' -import type { ActionReturn } from '@/actions/Types' - -export async function readArticleAction(idOrName: number | { - name: string, - category: string -}): Promise> { - //TODO: auth - return await safeServerCall(() => readArticle(idOrName)) -} diff --git a/src/actions/cms/articles/update.ts b/src/actions/cms/articles/update.ts deleted file mode 100644 index f86905561..000000000 --- a/src/actions/cms/articles/update.ts +++ /dev/null @@ -1,39 +0,0 @@ -'use server' -import { createZodActionError } from '@/actions/error' -import { addSectionToArticle, moveSectionOrder, updateArticle } from '@/services/cms/articles/update' -import { safeServerCall } from '@/actions/safeServerCall' -import { updateArticleValidation } from '@/services/cms/articles/validation' -import type { UpdateArticleTypes } from '@/services/cms/articles/validation' -import type { ArticleSectionPart } from '@/services/cms/articleSections/Types' -import type { ActionReturn } from '@/actions/Types' -import type { ArticleSection } from '@prisma/client' -import type { ExpandedArticle } from '@/cms/articles/Types' - -export async function updateArticleAction( - id: number, - rawData: FormData | UpdateArticleTypes['Type'] -): Promise> { - //TODO: auth on visability - const parse = updateArticleValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => updateArticle(id, data)) -} - -export async function addSectionToArticleAction( - id: number, - include: Partial> -): Promise> { - //TODO: auth on visability - return await safeServerCall(() => addSectionToArticle(id, include)) -} - -export async function moveSectionOrderAction( - id: number, - sectionId: number, - direction: 'UP' | 'DOWN' -): Promise> { - //TODO: auth on visability - return await safeServerCall(() => moveSectionOrder(id, sectionId, direction)) -} diff --git a/src/actions/cms/images/create.ts b/src/actions/cms/images/create.ts deleted file mode 100644 index 46d7108b2..000000000 --- a/src/actions/cms/images/create.ts +++ /dev/null @@ -1,21 +0,0 @@ -'use server' -import { createCmsImageActionValidation } from './validation' -import { safeServerCall } from '@/actions/safeServerCall' -import { createCmsImage } from '@/services/cms/images/create' -import { createZodActionError } from '@/actions/error' -import type { CreateCmsImageActionTypes } from './validation' -import type { Image } from '@prisma/client' -import type { ExpandedCmsImage } from '@/services/cms/images/Types' -import type { ActionReturn } from '@/actions/Types' - -export async function createCmsImageAction( - rawData: FormData | CreateCmsImageActionTypes['Type'], - image?: Image, -): Promise> { - //TODO: Auth route (very few people should be able to create stand alone cmsImages...) - const parse = createCmsImageActionValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createCmsImage(data, image)) -} diff --git a/src/actions/cms/images/read.ts b/src/actions/cms/images/read.ts deleted file mode 100644 index b4f15f607..000000000 --- a/src/actions/cms/images/read.ts +++ /dev/null @@ -1,42 +0,0 @@ -'use server' -import { createActionError } from '@/actions/error' -import { readCmsImage, readSpecialCmsImage } from '@/services/cms/images/read' -import { createCmsImage } from '@/services/cms/images/create' -import { safeServerCall } from '@/actions/safeServerCall' -import { SpecialCmsImage } from '@prisma/client' -import type { ExpandedCmsImage } from '@/cms/images/Types' -import type { ActionReturn } from '@/actions/Types' - -/** - * A action to read a cms image including the image associated with it - * @param name - name of the cms image the image - * @returns - */ -export async function readCmsImageAction(name: string): Promise> { - //TODO: auth on visibilty - return await safeServerCall(() => readCmsImage(name)) -} - -/** - * Action to reads a special cmsImage, if it does not exist it creates it - * @param special SpecialCmsImage - * @returns ActionReturn - */ -export async function readSpecialCmsImageAction(special: SpecialCmsImage): Promise> { - if (!Object.values(SpecialCmsImage).includes(special)) { - return createActionError('BAD PARAMETERS', `${special} is not special`) - } - const specialRes = await safeServerCall(() => readSpecialCmsImage(special)) - if (!specialRes.success) { - if (specialRes.errorCode === 'NOT FOUND') { - return await safeServerCall(() => createCmsImage({ - name: special, - special, - })) - } - return specialRes - } - const cmsImage = specialRes.data - //TODO: Auth on visibilty - return { success: true, data: cmsImage } -} diff --git a/src/actions/cms/images/update.ts b/src/actions/cms/images/update.ts deleted file mode 100644 index f3175c2a1..000000000 --- a/src/actions/cms/images/update.ts +++ /dev/null @@ -1,18 +0,0 @@ -'use server' -import { updateCmsImage, updateCmsImageConfig } from '@/services/cms/images/update' -import { safeServerCall } from '@/actions/safeServerCall' -import type { ActionReturn } from '@/actions/Types' -import type { CmsImage, ImageSize } from '@prisma/client' - -export async function updateCmsImageAction(cmsImageId: number, imageId: number): Promise> { - //TODO: Auth on visibility (or permission if special) - return await safeServerCall(() => updateCmsImage(cmsImageId, imageId)) -} - -export async function updateCmsImageConfigAction( - cmsImageId: number, - config: {imageSize: ImageSize} -): Promise> { - //TODO: Auth on visibility (or permission if special) - return await safeServerCall(() => updateCmsImageConfig(cmsImageId, config)) -} diff --git a/src/actions/cms/images/validation.ts b/src/actions/cms/images/validation.ts deleted file mode 100644 index d793e0682..000000000 --- a/src/actions/cms/images/validation.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { baseCmsImageValidation } from '@/services/cms/images/validation' -import type { ValidationTypes } from '@/services/Validation' - -export const createCmsImageActionValidation = baseCmsImageValidation.createValidation({ - keys: ['name'], - transformer: data => data, -}) -export type CreateCmsImageActionTypes = ValidationTypes diff --git a/src/actions/cms/links/create.ts b/src/actions/cms/links/create.ts deleted file mode 100644 index 28ea570d0..000000000 --- a/src/actions/cms/links/create.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use server' -import { createCmsLink } from '@/services/cms/links/create' -import { safeServerCall } from '@/actions/safeServerCall' -import { createCmsLinkValidation } from '@/services/cms/links/validation' -import { createZodActionError } from '@/actions/error' -import type { CreateCmsLinkTypes } from '@/services/cms/links/validation' -import type { ActionReturn } from '@/actions/Types' -import type { CmsLink } from '@prisma/client' - -export async function createCmsLinkAction( - rawData: FormData | CreateCmsLinkTypes['Type'] -): Promise> { - //TODO: Auth on permission to create cms - const parse = createCmsLinkValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createCmsLink(data)) -} diff --git a/src/actions/cms/links/read.ts b/src/actions/cms/links/read.ts deleted file mode 100644 index 6b2d71b78..000000000 --- a/src/actions/cms/links/read.ts +++ /dev/null @@ -1,6 +0,0 @@ -'use server' - -import { action } from '@/actions/action' -import { readSpecialCmsLink } from '@/services/cms/links/read' - -export const readSpecialCmsLinkAction = action(readSpecialCmsLink) diff --git a/src/actions/cms/links/update.ts b/src/actions/cms/links/update.ts deleted file mode 100644 index ba5e728ec..000000000 --- a/src/actions/cms/links/update.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use server' -import { createZodActionError } from '@/actions/error' -import { updateCmsLink } from '@/services/cms/links/update' -import { safeServerCall } from '@/actions/safeServerCall' -import { updateCmsLinkValidation } from '@/services/cms/links/validation' -import type { UpdateCmsLinkTypes } from '@/services/cms/links/validation' -import type { CmsLink } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' - -export async function updateCmsLinkAction( - id: number, - rawData: FormData | UpdateCmsLinkTypes['Type'] -): Promise> { - //TODO: auth on visibility - const parse = updateCmsLinkValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - if (data.url && data.url.includes('.') && !data.url.startsWith('http://') && !data.url.startsWith('https://')) { - data.url = `https://${data.url}` - } - return await safeServerCall(() => updateCmsLink(id, data)) -} diff --git a/src/actions/cms/paragraphs/create.ts b/src/actions/cms/paragraphs/create.ts deleted file mode 100644 index 5eff7631e..000000000 --- a/src/actions/cms/paragraphs/create.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use server' -import { createCmsParagraphActionValidation } from './validation' -import { createCmsParagraph } from '@/services/cms/paragraphs/create' -import { safeServerCall } from '@/actions/safeServerCall' -import { createZodActionError } from '@/actions/error' -import type { CreateCmsParagraphActionTypes } from './validation' -import type { CmsParagraph } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' - -export async function createCmsParagraphAction( - rawData: FormData | CreateCmsParagraphActionTypes['Type'] -): Promise> { - //TDOD: Auth on cms permission (few should be able to create a paragraph standalone) - const parse = createCmsParagraphActionValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createCmsParagraph(data)) -} diff --git a/src/actions/cms/paragraphs/update.ts b/src/actions/cms/paragraphs/update.ts deleted file mode 100644 index cc0b4392f..000000000 --- a/src/actions/cms/paragraphs/update.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use server' -import { updateCmsParagraphContents } from '@/services/cms/paragraphs/update' -import { safeServerCall } from '@/actions/safeServerCall' -import type { CmsParagraph } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' - -export async function updateCmsParagraphAction(id: number, contentMd: string): Promise> { - //TODO: Auth on visibility - return await safeServerCall(() => updateCmsParagraphContents(id, contentMd)) -} diff --git a/src/actions/cms/paragraphs/validation.ts b/src/actions/cms/paragraphs/validation.ts deleted file mode 100644 index c3ba2f115..000000000 --- a/src/actions/cms/paragraphs/validation.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { baseCmsParagraphValidation } from '@/services/cms/paragraphs/validation' -import type { ValidationTypes } from '@/services/Validation' - -export const createCmsParagraphActionValidation = baseCmsParagraphValidation.createValidation({ - keys: ['name'], - transformer: data => data -}) - -export type CreateCmsParagraphActionTypes = ValidationTypes diff --git a/src/actions/dots/create.ts b/src/actions/dots/create.ts deleted file mode 100644 index 460b05600..000000000 --- a/src/actions/dots/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { dotMethods } from '@/services/dots/methods' - -export const createDotAction = action(dotMethods.create) diff --git a/src/actions/dots/read.ts b/src/actions/dots/read.ts deleted file mode 100644 index 13ec99611..000000000 --- a/src/actions/dots/read.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { dotMethods } from '@/services/dots/methods' - -export const readDotPageAction = action(dotMethods.readPage) - -export const readDotWrappersForUserAction = action(dotMethods.readWrappersForUser) diff --git a/src/actions/education/schools/create.ts b/src/actions/education/schools/create.ts deleted file mode 100644 index bc74cf281..000000000 --- a/src/actions/education/schools/create.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use server' -import { createActionError, createZodActionError } from '@/actions/error' -import { getUser } from '@/auth/getUser' -import { createSchoolValidation } from '@/education/schools/validation' -import { safeServerCall } from '@/actions/safeServerCall' -import { createSchool } from '@/services/education/schools/create' -import type { SchoolFiltered } from '@/education/schools/Types' -import type { ActionReturn } from '@/actions/Types' -import type { CreateSchoolTypes } from '@/education/schools/validation' - -export async function createSchoolAction( - rawdata: FormData | CreateSchoolTypes['Type'] -): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCHOOLS_ADMIN']] - }) - if (!authorized) return createActionError(status) - - const parse = createSchoolValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createSchool(data)) -} diff --git a/src/actions/education/schools/destroy.ts b/src/actions/education/schools/destroy.ts deleted file mode 100644 index 5b61aa7d5..000000000 --- a/src/actions/education/schools/destroy.ts +++ /dev/null @@ -1,15 +0,0 @@ -'use server' -import { createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { destroySchool } from '@/services/education/schools/destroy' -import type { ActionReturn } from '@/actions/Types' - -export async function destroySchoolAction(id: number): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCHOOLS_ADMIN']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => destroySchool(id)) -} diff --git a/src/actions/education/schools/read.ts b/src/actions/education/schools/read.ts deleted file mode 100644 index 72ae3ba3d..000000000 --- a/src/actions/education/schools/read.ts +++ /dev/null @@ -1,50 +0,0 @@ -'use server' -import { createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { readSchool, readSchools, readSchoolsPage, readStandardSchools } from '@/services/education/schools/read' -import { getUser } from '@/auth/getUser' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedSchool, SchoolCursor, SchoolFiltered } from '@/education/schools/Types' - -export async function readSchoolsPageAction( - pageReadInput: ReadPageInput -): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCHOOLS_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readSchoolsPage(pageReadInput)) -} - -export async function readStandardSchoolsAction(): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCHOOLS_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readStandardSchools()) -} - -export async function readSchoolsAction({ - onlyNonStandard -}: { - onlyNonStandard: boolean -}): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCHOOLS_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readSchools({ onlyNonStandard })) -} - -export async function readSchoolAction(shortname: string): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCHOOLS_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readSchool(shortname)) -} diff --git a/src/actions/education/schools/update.ts b/src/actions/education/schools/update.ts deleted file mode 100644 index 6cb7279eb..000000000 --- a/src/actions/education/schools/update.ts +++ /dev/null @@ -1,25 +0,0 @@ -'use server' -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { updateSchoolValidation } from '@/education/schools/validation' -import { updateSchool } from '@/services/education/schools/update' -import type { ActionReturn } from '@/actions/Types' -import type { UpdateSchoolTypes } from '@/education/schools/validation' -import type { SchoolFiltered } from '@/education/schools/Types' - -export async function updateSchoolAction( - id: number, - rawdata: FormData | UpdateSchoolTypes['Type'] -): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCHOOLS_ADMIN']] - }) - if (!authorized) return createActionError(status) - - const parse = updateSchoolValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => updateSchool(id, data)) -} diff --git a/src/actions/error.ts b/src/actions/error.ts deleted file mode 100644 index 2f26be65c..000000000 --- a/src/actions/error.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { errorCodes, type ErrorCode, type ErrorMessage } from '@/services/error' -import type { AuthStatus } from '@/auth/getUser' -import type { SafeParseError } from 'zod' -import type { ActionReturnError } from './Types' - -export function createActionError(errorCode: ErrorCode | AuthStatus, error?: string | ErrorMessage[]): ActionReturnError { - if (errorCode === 'AUTHORIZED' || errorCode === 'AUTHORIZED_NO_USER') { - return { - success: false, - errorCode: 'UNKNOWN ERROR', - httpCode: 500, - error: typeof error === 'string' ? [{ message: error }] : error, - } - } - return { - success: false, - errorCode, - httpCode: errorCodes.find(e => e.name === errorCode)?.httpCode ?? 500, - error: typeof error === 'string' ? [{ message: error }] : error, - } -} - -export function createZodActionError(parse: SafeParseError): ActionReturnError { - return { - success: false, - httpCode: 400, - errorCode: 'BAD PARAMETERS', - error: parse.error.issues, - } -} - diff --git a/src/actions/events/create.ts b/src/actions/events/create.ts deleted file mode 100644 index b25456029..000000000 --- a/src/actions/events/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { EventMethods } from '@/services/events/methods' - -export const createEventAction = action(EventMethods.create) diff --git a/src/actions/events/destroy.ts b/src/actions/events/destroy.ts deleted file mode 100644 index ea7b77fff..000000000 --- a/src/actions/events/destroy.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { EventMethods } from '@/services/events/methods' - -export const destroyEventAction = action(EventMethods.destroy) diff --git a/src/actions/events/read.ts b/src/actions/events/read.ts deleted file mode 100644 index 25fc3a037..000000000 --- a/src/actions/events/read.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { EventMethods } from '@/services/events/methods' - -export const readCurrentEventsAction = action(EventMethods.readManyCurrent) -export const readEventAction = action(EventMethods.read) -export const readArchivedEventsPageAction = action(EventMethods.readManyArchivedPage) diff --git a/src/actions/events/registration/index.ts b/src/actions/events/registration/index.ts deleted file mode 100644 index 0a22860c5..000000000 --- a/src/actions/events/registration/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -'use server' - -import { action } from '@/actions/action' -import { EventRegistrationMethods } from '@/services/events/registration/methods' - - -export const createEventRegistrationAction = action(EventRegistrationMethods.create) -export const createGuestEventRegistrationAction = action(EventRegistrationMethods.createGuest) -export const readManyEventRegistrationAction = action(EventRegistrationMethods.readMany) -export const eventRegistrationReadManyDetailedAction = action(EventRegistrationMethods.readManyDetailed) -export const eventRegistrationUpdateNotesAction = action(EventRegistrationMethods.updateNotes) -export const eventRegistrationDestroyAction = action(EventRegistrationMethods.destroy) diff --git a/src/actions/events/tags/create.ts b/src/actions/events/tags/create.ts deleted file mode 100644 index 0fadff21f..000000000 --- a/src/actions/events/tags/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { EventTagMethods } from '@/services/events/tags/methods' - -export const createEventTagAction = action(EventTagMethods.create) diff --git a/src/actions/events/tags/destroy.ts b/src/actions/events/tags/destroy.ts deleted file mode 100644 index f5c5390c0..000000000 --- a/src/actions/events/tags/destroy.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { EventTagMethods } from '@/services/events/tags/methods' - -export const destroyEventTagAction = action(EventTagMethods.destroy) diff --git a/src/actions/events/tags/read.ts b/src/actions/events/tags/read.ts deleted file mode 100644 index 8afec51fa..000000000 --- a/src/actions/events/tags/read.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { EventTagMethods } from '@/services/events/tags/methods' - -export const readEventTagsAction = action(EventTagMethods.readAll) -export const readSpecialEventTagAction = action(EventTagMethods.readSpecial) -export const readEventTagAction = action(EventTagMethods.read) diff --git a/src/actions/events/tags/update.ts b/src/actions/events/tags/update.ts deleted file mode 100644 index 8c237f4ef..000000000 --- a/src/actions/events/tags/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { EventTagMethods } from '@/services/events/tags/methods' - -export const updateEventTagAction = action(EventTagMethods.update) diff --git a/src/actions/events/update.ts b/src/actions/events/update.ts deleted file mode 100644 index e8f80a787..000000000 --- a/src/actions/events/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { EventMethods } from '@/services/events/methods' - -export const updateEventAction = action(EventMethods.update) diff --git a/src/actions/groups/committees/create.ts b/src/actions/groups/committees/create.ts deleted file mode 100644 index a402e5abd..000000000 --- a/src/actions/groups/committees/create.ts +++ /dev/null @@ -1,25 +0,0 @@ -'use server' -import { createActionError, createZodActionError } from '@/actions/error' -import { getUser } from '@/auth/getUser' -import { createCommittee } from '@/services/groups/committees/create' -import { safeServerCall } from '@/actions/safeServerCall' -import { createCommitteeValidation } from '@/services/groups/committees/validation' -import type { ExpandedCommittee } from '@/services/groups/committees/Types' -import type { ActionReturn } from '@/actions/Types' -import type { CreateCommitteeTypes } from '@/services/groups/committees/validation' - -export async function createCommitteeAction( - rawData: FormData | CreateCommitteeTypes['Type'] -): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['COMMITTEE_CREATE']], - shouldRedirect: false, - }) - - if (!authorized) return createActionError(status) - - const parse = createCommitteeValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - - return await safeServerCall(() => createCommittee(parse.data)) -} diff --git a/src/actions/groups/committees/read.ts b/src/actions/groups/committees/read.ts deleted file mode 100644 index ac6629449..000000000 --- a/src/actions/groups/committees/read.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { CommitteeMethods } from '@/services/groups/committees/methods' - -export const readCommitteesAction = action(CommitteeMethods.readCommittees) -export const readCommitteeAction = action(CommitteeMethods.readCommittee) -export const readCommitteeArticleAction = action(CommitteeMethods.readCommitteArticle) -export const readCommitteeParagraphAction = action(CommitteeMethods.readCommitteeParagraph) -export const readCommitteeMembersAction = action(CommitteeMethods.readCommitteeMembers) - diff --git a/src/actions/groups/committees/update.ts b/src/actions/groups/committees/update.ts deleted file mode 100644 index bcaedb055..000000000 --- a/src/actions/groups/committees/update.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use server' -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { updateCommittee } from '@/services/groups/committees/update' -import { updateCommitteeValidation } from '@/services/groups/committees/validation' -import type { UpdateCommitteeTypes } from '@/services/groups/committees/validation' - - -export async function updateCommitteeAction( - id: number, - rawdata: FormData | UpdateCommitteeTypes['Type'] -) { - const { status, authorized } = await getUser({ - requiredPermissions: [['COMMITTEE_UPDATE']] - }) - if (!authorized) return createActionError(status) - - const parse = updateCommitteeValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => updateCommittee(id, data)) -} diff --git a/src/actions/groups/interestGroups/create.ts b/src/actions/groups/interestGroups/create.ts deleted file mode 100644 index a15258e5d..000000000 --- a/src/actions/groups/interestGroups/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { InterestGroupMethods } from '@/services/groups/interestGroups/methods' - -export const createInterestGroupAction = action(InterestGroupMethods.create) diff --git a/src/actions/groups/interestGroups/destroy.ts b/src/actions/groups/interestGroups/destroy.ts deleted file mode 100644 index d884b2427..000000000 --- a/src/actions/groups/interestGroups/destroy.ts +++ /dev/null @@ -1,6 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { InterestGroupMethods } from '@/services/groups/interestGroups/methods' - - -export const destroyInterestGroupAction = action(InterestGroupMethods.destroy) diff --git a/src/actions/groups/interestGroups/read.ts b/src/actions/groups/interestGroups/read.ts deleted file mode 100644 index 67ce4cff3..000000000 --- a/src/actions/groups/interestGroups/read.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { InterestGroupMethods } from '@/services/groups/interestGroups/methods' - -export const readInterestGroupsAction = action(InterestGroupMethods.readMany) diff --git a/src/actions/groups/interestGroups/update.ts b/src/actions/groups/interestGroups/update.ts deleted file mode 100644 index 72da82e01..000000000 --- a/src/actions/groups/interestGroups/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { InterestGroupMethods } from '@/services/groups/interestGroups/methods' - -export const updateInterestGroupAction = action(InterestGroupMethods.update) diff --git a/src/actions/groups/memberships/create.ts b/src/actions/groups/memberships/create.ts deleted file mode 100644 index 117dbb0f6..000000000 --- a/src/actions/groups/memberships/create.ts +++ /dev/null @@ -1,28 +0,0 @@ -'use server' - -import { createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { createMembershipsForGroup } from '@/services/groups/memberships/create' -import type { ActionReturn } from '@/actions/Types' - -/** - * WARNING: This action will lead to error if used with group types not in CanEasalyManageMembership - */ -export async function createMembershipsForGroupAction({ - groupId, - users -}: { - groupId: number, - users: { - userId: number, - admin: boolean - }[] -}): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['GROUP_ADMIN']] - }) - if (!authorized) return createActionError(status) - - return safeServerCall(() => createMembershipsForGroup(groupId, users)) -} diff --git a/src/actions/groups/memberships/destory.ts b/src/actions/groups/memberships/destory.ts deleted file mode 100644 index a2c6c0266..000000000 --- a/src/actions/groups/memberships/destory.ts +++ /dev/null @@ -1,28 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { destoryMembershipOfUser } from '@/services/groups/memberships/destroy' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedMembership } from '@/services/groups/memberships/Types' - -/** - * WARNING: Do not use this action, usually you want updateMemebershipInactivate - * @param - * @returns - */ -export async function destroyMembership({ - groupId, - userId, - orderArg, -}: { - groupId: number, - userId: number, - orderArg: number -}): Promise> { - //TODO: make function to check that. user is admin of group - - return await safeServerCall(() => destoryMembershipOfUser({ - groupId, - userId, - orderArg - })) -} diff --git a/src/actions/groups/memberships/update.ts b/src/actions/groups/memberships/update.ts deleted file mode 100644 index fcce5eb25..000000000 --- a/src/actions/groups/memberships/update.ts +++ /dev/null @@ -1,27 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { updateMembership } from '@/services/groups/memberships/update' -import type { ExpandedMembership } from '@/services/groups/memberships/Types' -import type { ActionReturn } from '@/actions/Types' - -export async function updateMembershipAdminAcion(membership: { - groupId: number - userId: number -}, admin: boolean): Promise> { - //TODO: make function to check that user is admin of group - return await safeServerCall(() => updateMembership({ - ...membership, - orderArg: 'ACTIVE' - }, { admin })) -} - -export async function updateMembershipActiveAction(membership: { - groupId: number - userId: number -}, active: boolean): Promise> { - //TODO: make function to check that user is admin of group - return await safeServerCall(() => updateMembership({ - ...membership, - orderArg: 'ACTIVE' - }, { active })) -} diff --git a/src/actions/groups/read.ts b/src/actions/groups/read.ts deleted file mode 100644 index 97d8d0146..000000000 --- a/src/actions/groups/read.ts +++ /dev/null @@ -1,8 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { GroupMethods } from '@/services/groups/methods' - -export const readGroupsAction = action(GroupMethods.readGroups) -export const readGroupExpandedAction = action(GroupMethods.readGroupExpanded) -export const readGroupsExpandedAction = action(GroupMethods.readGroupsExpanded) -export const readGroupsStructuredAction = action(GroupMethods.readGroupsStructured) diff --git a/src/actions/groups/studyProgrammes/create.ts b/src/actions/groups/studyProgrammes/create.ts deleted file mode 100644 index 7d5fdaa55..000000000 --- a/src/actions/groups/studyProgrammes/create.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use server' - -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { createStudyProgramme } from '@/services/groups/studyProgrammes/create' -import { createStudyProgrammeValidation } from '@/services/groups/studyProgrammes/validation' -import type { ActionReturn } from '@/actions/Types' -import type { StudyProgramme } from '@prisma/client' - - -export async function createStudyProgrammeAction(rawdata: FormData): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['STUDY_PROGRAMME_CREATE']], - }) - if (!authorized) return createActionError(status) - - const parse = createStudyProgrammeValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - - return await safeServerCall(() => createStudyProgramme(parse.data)) -} diff --git a/src/actions/groups/studyProgrammes/read.ts b/src/actions/groups/studyProgrammes/read.ts deleted file mode 100644 index 387de398c..000000000 --- a/src/actions/groups/studyProgrammes/read.ts +++ /dev/null @@ -1,18 +0,0 @@ -'use server' - -import { createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { readStudyProgrammes } from '@/services/groups/studyProgrammes/read' -import type { ActionReturn } from '@/actions/Types' -import type { StudyProgramme } from '@prisma/client' - - -export async function readStudyProgrammesAction(): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['STUDY_PROGRAMME_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readStudyProgrammes()) -} diff --git a/src/actions/groups/studyProgrammes/update.ts b/src/actions/groups/studyProgrammes/update.ts deleted file mode 100644 index 3e32c05ac..000000000 --- a/src/actions/groups/studyProgrammes/update.ts +++ /dev/null @@ -1,25 +0,0 @@ -'use server' - -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { updateStudyProgramme } from '@/services/groups/studyProgrammes/update' -import { updateStudyProgrammeValidation } from '@/services/groups/studyProgrammes/validation' -import type { ActionReturn } from '@/actions/Types' -import type { StudyProgramme } from '@prisma/client' - - -export async function updateStudyProgrammeAction(id: number, rawdata: FormData): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['STUDY_PROGRAMME_UPDATE']], - }) - if (!authorized) return createActionError(status) - - const parse = updateStudyProgrammeValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - - return await safeServerCall(() => updateStudyProgramme({ - ...parse.data, - id, - })) -} diff --git a/src/actions/images/collections/create.ts b/src/actions/images/collections/create.ts deleted file mode 100644 index eb1791e30..000000000 --- a/src/actions/images/collections/create.ts +++ /dev/null @@ -1,23 +0,0 @@ -'use server' -import { createActionError, createZodActionError } from '@/actions/error' -import { createImageCollection } from '@/services/images/collections/create' -import { safeServerCall } from '@/actions/safeServerCall' -import { createImageCollectionValidation } from '@/services/images/collections/validation' -import { getUser } from '@/auth/getUser' -import type { ImageCollection } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' -import type { CreateImageCollectionTypes } from '@/services/images/collections/validation' - -export async function createImageCollectionAction( - rawdata: FormData | CreateImageCollectionTypes['Type'] -): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['IMAGE_COLLECTION_CREATE']] - }) - if (!authorized) return createActionError(status) - - const parse = createImageCollectionValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - return await safeServerCall(() => createImageCollection(data)) -} diff --git a/src/actions/images/collections/destroy.ts b/src/actions/images/collections/destroy.ts deleted file mode 100644 index 7ae1e63ac..000000000 --- a/src/actions/images/collections/destroy.ts +++ /dev/null @@ -1,11 +0,0 @@ -'use server' -import { destroyImageCollection } from '@/services/images/collections/destroy' -import { safeServerCall } from '@/actions/safeServerCall' -import type { ImageCollection } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' - -export async function destroyImageCollectionAction(collectionId: number): Promise> { - //TODO: Visibility check - - return await safeServerCall(() => destroyImageCollection(collectionId)) -} diff --git a/src/actions/images/collections/update.ts b/src/actions/images/collections/update.ts deleted file mode 100644 index bfa141a3d..000000000 --- a/src/actions/images/collections/update.ts +++ /dev/null @@ -1,28 +0,0 @@ -'use server' -import { createZodActionError } from '@/actions/error' -import { updateImageCollection } from '@/services/images/collections/update' -import { safeServerCall } from '@/actions/safeServerCall' -import { updateImageCollectionValidation } from '@/services/images/collections/validation' -import type { ImageCollection } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' -import type { UpdateImageCollectionTypes } from '@/services/images/collections/validation' - -/** - * A action that updates an image collection - * @param collectionId - the id of the collection to update - * @param coverImageId - the id of the image to set as the cover image (optional) - * @param rawdata - the data to update the collection with - * @returns - the updated collection in ActionReturn - */ -export async function updateImageCollectionAction( - collectionId: number, - coverImageId: number | undefined, - rawdata: FormData | UpdateImageCollectionTypes['Type'] -): Promise> { - const parse = updateImageCollectionValidation.typeValidate(rawdata) - - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => updateImageCollection(collectionId, coverImageId, data)) -} diff --git a/src/actions/images/create.ts b/src/actions/images/create.ts deleted file mode 100644 index 34b11067d..000000000 --- a/src/actions/images/create.ts +++ /dev/null @@ -1,6 +0,0 @@ -'use server' -import { ImageMethods } from '@/services/images/methods' -import { action } from '@/actions/action' - -export const createImageAction = action(ImageMethods.create) -export const createImagesAction = action(ImageMethods.createMany) diff --git a/src/actions/images/destroy.ts b/src/actions/images/destroy.ts deleted file mode 100644 index 0b4814bdd..000000000 --- a/src/actions/images/destroy.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { ImageMethods } from '@/services/images/methods' - -export const destroyImageAction = action(ImageMethods.destroy) diff --git a/src/actions/images/read.ts b/src/actions/images/read.ts deleted file mode 100644 index c12881f6a..000000000 --- a/src/actions/images/read.ts +++ /dev/null @@ -1,16 +0,0 @@ -'use server' -import { ImageMethods } from '@/services/images/methods' -import { action } from '@/actions/action' - -/** - * Read one image. -*/ -export const readImageAction = action(ImageMethods.read) -/** - * Read one page of images. - */ -export const readImagesPageAction = action(ImageMethods.readPage) -/** - * Read one special image. - */ -export const readSpecialImageAction = action(ImageMethods.readSpecial) diff --git a/src/actions/images/update.ts b/src/actions/images/update.ts deleted file mode 100644 index 9f33da997..000000000 --- a/src/actions/images/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { ImageMethods } from '@/services/images/methods' -import { action } from '@/actions/action' - -export const updateImageAction = action(ImageMethods.update) diff --git a/src/actions/licenses/create.ts b/src/actions/licenses/create.ts deleted file mode 100644 index 69c3d44f7..000000000 --- a/src/actions/licenses/create.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { LicenseMethods } from '@/services/licenses/methods' - -export const createLicenseAction = action(LicenseMethods.create) diff --git a/src/actions/licenses/destroy.ts b/src/actions/licenses/destroy.ts deleted file mode 100644 index c7d3ba569..000000000 --- a/src/actions/licenses/destroy.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { LicenseMethods } from '@/services/licenses/methods' - -export const destroyLicenseAction = action(LicenseMethods.destroy) diff --git a/src/actions/licenses/read.ts b/src/actions/licenses/read.ts deleted file mode 100644 index 58c6b3d16..000000000 --- a/src/actions/licenses/read.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { LicenseMethods } from '@/services/licenses/methods' - -export const readAllLicensesAction = action(LicenseMethods.readAll) diff --git a/src/actions/licenses/update.ts b/src/actions/licenses/update.ts deleted file mode 100644 index 496480670..000000000 --- a/src/actions/licenses/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -'use server' -import { LicenseMethods } from '@/services/licenses/methods' -import { action } from '@/actions/action' - -export const updateLicenseAction = action(LicenseMethods.update) diff --git a/src/actions/lockers/locations.ts b/src/actions/lockers/locations.ts deleted file mode 100644 index 358cc6785..000000000 --- a/src/actions/lockers/locations.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use server' - -import { action } from '@/actions/action' -import { LockerLocationMethods } from '@/services/lockers/locations/methods' - -export const createLockerLocationAction = action(LockerLocationMethods.create) -export const readAllLockerLocationsAction = action(LockerLocationMethods.readAll) diff --git a/src/actions/lockers/lockers.ts b/src/actions/lockers/lockers.ts deleted file mode 100644 index e5c2f4c6e..000000000 --- a/src/actions/lockers/lockers.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use server' -import { LockerMethods } from '@/services/lockers/methods' -import { action } from '@/actions/action' - -export const createLockerAction = action(LockerMethods.create) -export const readLockerAction = action(LockerMethods.read) -export const readLockerPageAction = action(LockerMethods.readPage) diff --git a/src/actions/lockers/reservations.ts b/src/actions/lockers/reservations.ts deleted file mode 100644 index 6453e9d0c..000000000 --- a/src/actions/lockers/reservations.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use server' - -import { action } from '@/actions/action' -import { LockerReservationMethods } from '@/services/lockers/reservations/methods' - -export const updateLockerReservationAction = action(LockerReservationMethods.update) -export const createLockerReservationAction = action(LockerReservationMethods.create) diff --git a/src/actions/mail/alias/create.ts b/src/actions/mail/alias/create.ts deleted file mode 100644 index f8f47fc0a..000000000 --- a/src/actions/mail/alias/create.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use server' - -import { createMailAliasValidation } from '@/services/mail/alias/validation' -import { createZodActionError, createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { createMailAlias } from '@/services/mail/alias/create' -import { getUser } from '@/auth/getUser' -import type { ActionReturn } from '@/actions//Types' -import type { MailAlias } from '@prisma/client' - - -export async function createMailAliasAction(rawdata: FormData): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILALIAS_CREATE']] - }) - if (!authorized) return createActionError(status) - - const parse = createMailAliasValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => createMailAlias(parse.data)) -} - diff --git a/src/actions/mail/alias/destory.ts b/src/actions/mail/alias/destory.ts deleted file mode 100644 index bc2871e82..000000000 --- a/src/actions/mail/alias/destory.ts +++ /dev/null @@ -1,20 +0,0 @@ -'use server' -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { destroyMailAlias } from '@/services/mail/alias/destroy' -import { destoryMailAliasValidation } from '@/services/mail/alias/validation' -import type { ActionReturn } from '@/actions/Types' -import type { MailAlias } from '@prisma/client' - -export async function destroyMailAliasAction(id: number): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILALIAS_DESTORY']], - }) - if (!authorized) return createActionError(status) - - const parse = destoryMailAliasValidation.typeValidate({ id }) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => destroyMailAlias(parse.data.id)) -} diff --git a/src/actions/mail/alias/read.ts b/src/actions/mail/alias/read.ts deleted file mode 100644 index 29def0ef4..000000000 --- a/src/actions/mail/alias/read.ts +++ /dev/null @@ -1,17 +0,0 @@ -'use server' - -import { createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { readMailAliases } from '@/services/mail/alias/read' -import { getUser } from '@/auth/getUser' -import type { ActionReturn } from '@/actions/Types' -import type { MailAlias } from '@prisma/client' - -export async function readMailAliasesAction(): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILALIAS_READ']] - }) - if (!authorized) return createActionError(status) - - return safeServerCall(() => readMailAliases()) -} diff --git a/src/actions/mail/alias/update.ts b/src/actions/mail/alias/update.ts deleted file mode 100644 index 47e547600..000000000 --- a/src/actions/mail/alias/update.ts +++ /dev/null @@ -1,21 +0,0 @@ -'use server' - -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { createActionError, createZodActionError } from '@/actions/error' -import { updateMailAliasValidation } from '@/services/mail/alias/validation' -import { updateMailAlias } from '@/services/mail/alias/update' -import type { ActionReturn } from '@/actions/Types' -import type { MailAlias } from '@prisma/client' - -export async function updateMailAliasAction(rawdata: FormData): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILALIAS_UPDATE']], - }) - if (!authorized) return createActionError(status) - - const parsed = updateMailAliasValidation.typeValidate(rawdata) - if (!parsed.success) return createZodActionError(parsed) - - return await safeServerCall(() => updateMailAlias(parsed.data)) -} diff --git a/src/actions/mail/create.ts b/src/actions/mail/create.ts deleted file mode 100644 index 197fa748c..000000000 --- a/src/actions/mail/create.ts +++ /dev/null @@ -1,76 +0,0 @@ -'use server' - -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { - createAliasMailingListValidation, - createMailingListExternalValidation, - createMailingListGroupValidation, - createMailingListUserValidation -} from '@/services/mail/validation' -import { - createAliasMailingListRelation, - createMailingListExternalRelation, - createMailingListGroupRelation, - createMailingListUserRelation -} from '@/services/mail/create' -import type { ActionReturn } from '@/actions/Types' -import type { - MailAliasMailingList, - MailingListGroup, - MailingListMailAddressExternal, - MailingListUser -} from '@prisma/client' - -export async function createAliasMailingListRelationAction(formdata: FormData): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_ALIAS_CREATE']] - }) - if (!authorized) return createActionError(status) - - const parse = createAliasMailingListValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => createAliasMailingListRelation(parse.data)) -} - -export async function createMailingListExternalRelationAction(formdata: FormData): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_EXTERNAL_ADDRESS_CREATE']] - }) - if (!authorized) return createActionError(status) - - const parse = createMailingListExternalValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => createMailingListExternalRelation(parse.data)) -} - -export async function createMailingListUserRelationAction(formdata: FormData): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_USER_CREATE']] - }) - if (!authorized) return createActionError(status) - - const parse = createMailingListUserValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => createMailingListUserRelation(parse.data)) -} - -export async function createMailingListGroupRelationAction(formdata: FormData): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_GROUP_CREATE']] - }) - if (!authorized) return createActionError(status) - - const parse = createMailingListGroupValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => createMailingListGroupRelation(parse.data)) -} diff --git a/src/actions/mail/destroy.ts b/src/actions/mail/destroy.ts deleted file mode 100644 index 878f8cabf..000000000 --- a/src/actions/mail/destroy.ts +++ /dev/null @@ -1,83 +0,0 @@ -'use server' - -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { - createAliasMailingListValidation, - createMailingListExternalValidation, - createMailingListGroupValidation, - createMailingListUserValidation -} from '@/services/mail/validation' -import { - destroyAliasMailingListRelation, - destroyMailingListExternalRelation, - destroyMailingListGroupRelation, - destroyMailingListUserRelation -} from '@/services/mail/destroy' -import type { - CreateAliasMailingListType, - CreateMailingListExternalType, - CreateMailingListGroupType, - CreateMailingListUserType -} from '@/services/mail/validation' -import type { ActionReturn } from '@/actions/Types' -import type { - MailAliasMailingList, - MailingListGroup, - MailingListMailAddressExternal, - MailingListUser -} from '@prisma/client' - - -export async function destroyAliasMailingListRelationAction(formdata: FormData | CreateAliasMailingListType['Type']): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_ALIAS_DESTROY']] - }) - if (!authorized) return createActionError(status) - - const parse = createAliasMailingListValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => destroyAliasMailingListRelation(parse.data)) -} - -export async function destroyMailingListExternalRelationAction(formdata: FormData | CreateMailingListExternalType['Type']): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_EXTERNAL_ADDRESS_DESTROY']] - }) - if (!authorized) return createActionError(status) - - const parse = createMailingListExternalValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => destroyMailingListExternalRelation(parse.data)) -} - -export async function destroyMailingListUserRelationAction(formdata: FormData | CreateMailingListUserType['Type']): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_USER_DESTROY']], - }) - if (!authorized) return createActionError(status) - - const parse = createMailingListUserValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => destroyMailingListUserRelation(parse.data)) -} - -export async function destroyMailingListGroupRelationAction(formdata: FormData | CreateMailingListGroupType['Type']): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_GROUP_DESTROY']] - }) - if (!authorized) return createActionError(status) - - const parse = createMailingListGroupValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => destroyMailingListGroupRelation(parse.data)) -} diff --git a/src/actions/mail/list/create.ts b/src/actions/mail/list/create.ts deleted file mode 100644 index 065f19b76..000000000 --- a/src/actions/mail/list/create.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use server' - -import { createMailingListValidation } from '@/services/mail/list/validation' -import { createZodActionError, createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { createMailingList } from '@/services/mail/list/create' -import { getUser } from '@/auth/getUser' -import type { ActionReturn } from '@/actions//Types' -import type { MailingList } from '@prisma/client' - - -export async function createMailingListAction(rawdata: FormData): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_CREATE']] - }) - if (!authorized) return createActionError(status) - - const parse = createMailingListValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => createMailingList(parse.data)) -} - diff --git a/src/actions/mail/list/destory.ts b/src/actions/mail/list/destory.ts deleted file mode 100644 index 8e805c492..000000000 --- a/src/actions/mail/list/destory.ts +++ /dev/null @@ -1,20 +0,0 @@ -'use server' -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { destroyMailingList } from '@/services/mail/list/destroy' -import { readMailingListValidation } from '@/services/mail/list/validation' -import type { ActionReturn } from '@/actions/Types' -import type { MailingList } from '@prisma/client' - -export async function destroyMailingListAction(id: number): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_DESTROY']], - }) - if (!authorized) return createActionError(status) - - const parse = readMailingListValidation.typeValidate({ id }) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => destroyMailingList(parse.data.id)) -} diff --git a/src/actions/mail/list/update.ts b/src/actions/mail/list/update.ts deleted file mode 100644 index 8e58e9d73..000000000 --- a/src/actions/mail/list/update.ts +++ /dev/null @@ -1,23 +0,0 @@ -'use server' - -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { updateMailingList } from '@/services/mail/list/update' -import { updateMailingListValidation } from '@/services/mail/list/validation' -import type { ActionReturn } from '@/actions/Types' -import type { MailingList } from '@prisma/client' - - -export async function updateMailingListAction(data: FormData): -Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILINGLIST_UPDATE']], - }) - if (!authorized) return createActionError(status) - - const parse = updateMailingListValidation.typeValidate(data) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => updateMailingList(parse.data)) -} diff --git a/src/actions/mail/mailAddressExternal/create.ts b/src/actions/mail/mailAddressExternal/create.ts deleted file mode 100644 index aa49e2907..000000000 --- a/src/actions/mail/mailAddressExternal/create.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use server' - -import { createMailAddressExternalValidation } from '@/services/mail/mailAddressExternal/validation' -import { createZodActionError, createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { createMailAddressExternal } from '@/services/mail/mailAddressExternal/create' -import { getUser } from '@/auth/getUser' -import type { ActionReturn } from '@/actions//Types' -import type { MailAddressExternal } from '@prisma/client' - - -export async function createMailAddressExternalAction(rawdata: FormData): - Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILADDRESS_EXTERNAL_CREATE']] - }) - if (!authorized) return createActionError(status) - - const parse = createMailAddressExternalValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => createMailAddressExternal(parse.data)) -} - diff --git a/src/actions/mail/mailAddressExternal/destroy.ts b/src/actions/mail/mailAddressExternal/destroy.ts deleted file mode 100644 index fd62b1775..000000000 --- a/src/actions/mail/mailAddressExternal/destroy.ts +++ /dev/null @@ -1,20 +0,0 @@ -'use server' -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { readMailAddressExternalValidation } from '@/services/mail/mailAddressExternal/validation' -import { destroyMailAddressExternal } from '@/services/mail/mailAddressExternal/destroy' -import type { ActionReturn } from '@/actions/Types' -import type { MailAddressExternal } from '@prisma/client' - -export async function destroyMailAddressExternalAction(id: number): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILADDRESS_EXTERNAL_DESTROY']], - }) - if (!authorized) return createActionError(status) - - const parse = readMailAddressExternalValidation.typeValidate({ id }) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => destroyMailAddressExternal(parse.data.id)) -} diff --git a/src/actions/mail/mailAddressExternal/update.ts b/src/actions/mail/mailAddressExternal/update.ts deleted file mode 100644 index e9271b710..000000000 --- a/src/actions/mail/mailAddressExternal/update.ts +++ /dev/null @@ -1,23 +0,0 @@ -'use server' - -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { updateMailAddressExternal } from '@/services/mail/mailAddressExternal/update' -import { updateMailAddressExternalValidation } from '@/services/mail/mailAddressExternal/validation' -import type { ActionReturn } from '@/actions/Types' -import type { MailAddressExternal } from '@prisma/client' - - -export async function updateMailAddressExternalAction(data: FormData): -Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['MAILADDRESS_EXTERNAL_UPDATE']], - }) - if (!authorized) return createActionError(status) - - const parse = updateMailAddressExternalValidation.typeValidate(data) - if (!parse.success) return createZodActionError(parse) - - return safeServerCall(() => updateMailAddressExternal(parse.data)) -} diff --git a/src/actions/mail/read.ts b/src/actions/mail/read.ts deleted file mode 100644 index 0e9303951..000000000 --- a/src/actions/mail/read.ts +++ /dev/null @@ -1,63 +0,0 @@ -'use server' - -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError } from '@/actions/error' -import { readMailTraversal } from '@/services/mail/read' -import { readMailAliases } from '@/services/mail/alias/read' -import { readMailingLists } from '@/services/mail/list/read' -import { readMailAddressExternal } from '@/services/mail/mailAddressExternal/read' -import { getUser } from '@/auth/getUser' -import type { UserFiltered } from '@/services/users/Types' -import type { MailListTypes } from '@/services/mail/Types' -import type { MailingList, MailAlias, MailAddressExternal } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' - - -export async function readMailFlowAction(filter: MailListTypes, id: number) { - const { authorized, status } = await getUser({ - requiredPermissions: [ - ['MAILINGLIST_READ'], - ['MAILALIAS_READ'], - ['MAILADDRESS_EXTERNAL_READ'], - ['GROUP_READ'], - ], - }) - - if (!authorized) return createActionError(status) - - return safeServerCall(() => readMailTraversal({ - filter, - id, - })) -} - -export async function readMailOptions(): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [ - ['MAILINGLIST_READ'], - ['MAILALIAS_READ'], - ['MAILADDRESS_EXTERNAL_READ'], - ], - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(async () => { - const results = await Promise.all([ - readMailAliases(), - readMailingLists(), - readMailAddressExternal(), - ]) - - return { - alias: results[0], - mailingList: results[1], - mailaddressExternal: results[2], - users: [] - } - }) -} diff --git a/src/actions/news/create.ts b/src/actions/news/create.ts deleted file mode 100644 index 11f866d5e..000000000 --- a/src/actions/news/create.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { createZodActionError } from '@/actions/error' -import { createNews } from '@/services/news/create' -import { createNewsArticleValidation } from '@/services/news/validation' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedNewsArticle } from '@/services/news/Types' -import type { CreateNewsArticleTypes } from '@/services/news/validation' - -export async function createNewsAction( - rawdata: FormData | CreateNewsArticleTypes['Type'] -): Promise> { - //TODO: check for can create news permission - const parse = createNewsArticleValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createNews(data)) -} diff --git a/src/actions/news/destroy.ts b/src/actions/news/destroy.ts deleted file mode 100644 index 75865d72a..000000000 --- a/src/actions/news/destroy.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { destroyNews } from '@/services/news/destroy' -import type { ActionReturn } from '@/actions/Types' -import type { SimpleNewsArticle } from '@/services/news/Types' - -export async function destroyNewsAction(id: number): Promise>> { - //TODO: check auth - return await safeServerCall(() => destroyNews(id)) -} diff --git a/src/actions/news/read.ts b/src/actions/news/read.ts deleted file mode 100644 index 761b994a6..000000000 --- a/src/actions/news/read.ts +++ /dev/null @@ -1,26 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { readNews, readNewsCurrent, readOldNewsPage } from '@/services/news/read' -import type { ExpandedNewsArticle, NewsCursor, SimpleNewsArticle } from '@/services/news/Types' -import type { ActionReturn } from '@/actions/Types' -import type { ReadPageInput } from '@/lib/paging/Types' - -export async function readOldNewsPageAction( - readPageImput: ReadPageInput -): Promise> { - //TODO: only read news with right visibility - return await safeServerCall(() => readOldNewsPage(readPageImput)) -} - -export async function readNewsCurrentAction(): Promise> { - //TODO: only read news with right visibility - return await safeServerCall(() => readNewsCurrent()) -} - -export async function readNewsAction(idOrName: number | { - articleName: string - order: number -}): Promise> { - //TODO: only read news if right visibility - return await safeServerCall(() => readNews(idOrName)) -} diff --git a/src/actions/news/update.ts b/src/actions/news/update.ts deleted file mode 100644 index 584ec7d51..000000000 --- a/src/actions/news/update.ts +++ /dev/null @@ -1,50 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { createZodActionError, createActionError } from '@/actions/error' -import { updateNews } from '@/services/news/update' -import { updateNewsArticleValidation } from '@/services/news/validation' -import { NotificationMethods } from '@/services/notifications/methods' -import type { SimpleNewsArticle } from '@/services/news/Types' -import type { ActionReturn } from '@/actions/Types' -import type { UpdateNewsArticleTypes } from '@/services/news/validation' - -export async function updateNewsAction( - id: number, - rawdata: FormData | UpdateNewsArticleTypes['Type'] -): Promise>> { - //TODO: auth - const parse = updateNewsArticleValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - return await safeServerCall(() => updateNews(id, data)) -} - -export async function publishNewsAction( - // disable eslint rule temporarily until todo is resolved - // eslint-disable-next-line @typescript-eslint/no-unused-vars - id: number, - // disable eslint rule temporarily until todo is resolved - // eslint-disable-next-line @typescript-eslint/no-unused-vars - shouldPublish: boolean -): Promise>> { - NotificationMethods.createSpecial.newClient().execute({ - params: { - special: 'NEW_NEWS_ARTICLE', - }, - data: { - title: 'Ny nyhetsartikkel', // TODO: Add info about the article - message: 'En ny nyhetsartikkel er publisert', - }, - session: null, - bypassAuth: true, - }) - - return createActionError('UNKNOWN ERROR', 'Not implemented') -} - -// disable eslint rule temporarily until todo is resolved -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export async function updateVisibilityAction(id: number, visible: unknown): Promise> { - //TODO: add visible field to news - return createActionError('UNKNOWN ERROR', 'Not implemented') -} diff --git a/src/actions/notifications/index.ts b/src/actions/notifications/index.ts deleted file mode 100644 index 2764584fe..000000000 --- a/src/actions/notifications/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -'use server' - -import { action } from '@/actions/action' -import { NotificationChannelMethods } from '@/services/notifications/channel/methods' -import { NotificationMethods } from '@/services/notifications/methods' -import { NotificationSubscriptionMethods } from '@/services/notifications/subscription/methods' - -export const createNotificationChannelAction = action(NotificationChannelMethods.create) -export const updateNotificationChannelAction = action(NotificationChannelMethods.update) -export const readNotificationChannelsAction = action(NotificationChannelMethods.readMany) - -export const createNotificationAction = action(NotificationMethods.create) - -export const readNotificationSubscriptionsAction = action(NotificationSubscriptionMethods.read) -export const updateNotificationSubscriptionsAction = action(NotificationSubscriptionMethods.update) diff --git a/src/actions/ombul/create.ts b/src/actions/ombul/create.ts deleted file mode 100644 index a63cf0c69..000000000 --- a/src/actions/ombul/create.ts +++ /dev/null @@ -1,28 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError, createZodActionError } from '@/actions/error' -import { getUser } from '@/auth/getUser' -import { createOmbul } from '@/services/ombul/create' -import { createOmbulValidation } from '@/services/ombul/validation' -import type { ActionReturn } from '@/actions/Types' -import type { Ombul } from '@prisma/client' -import type { CreateOmbulTypes } from '@/services/ombul/validation' - -/** - * Create a new Ombul. - * @param rawData includes a pdf file with the ombul issue optionaly year and issueNumber - * @param CoverImageId is the id of the Image that will be used as the cover of the ombul - */ -export async function createOmbulAction(rawdata: FormData | CreateOmbulTypes['Type']): Promise> { - //Auth route - const { status, authorized } = await getUser({ - requiredPermissions: [['OMBUL_CREATE']] - }) - if (!authorized) return createActionError(status) - - const parse = createOmbulValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createOmbul(data)) -} diff --git a/src/actions/ombul/destroy.ts b/src/actions/ombul/destroy.ts deleted file mode 100644 index de5a9cb2a..000000000 --- a/src/actions/ombul/destroy.ts +++ /dev/null @@ -1,17 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError } from '@/actions/error' -import { getUser } from '@/auth/getUser' -import { destroyOmbul } from '@/services/ombul/destroy' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedOmbul } from '@/services/ombul/Types' - -export async function destroyOmbulAction(id: number): Promise> { - const { status, authorized } = await getUser({ - requiredPermissions: [['OMBUL_DESTROY']] - }) - - if (!authorized) return createActionError(status) - - return await safeServerCall(() => destroyOmbul(id)) -} diff --git a/src/actions/ombul/read.ts b/src/actions/ombul/read.ts deleted file mode 100644 index 2f4accad8..000000000 --- a/src/actions/ombul/read.ts +++ /dev/null @@ -1,42 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError } from '@/actions/error' -import { getUser } from '@/auth/getUser' -import { readLatestOmbul, readOmbul, readOmbuls } from '@/services/ombul/read' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedOmbul } from '@/services/ombul/Types' -import type { Ombul } from '@prisma/client' - -export async function readLatestOmbulAction(): Promise> { - //Auth route - const { status, authorized } = await getUser({ - requiredPermissions: [['OMBUL_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readLatestOmbul()) -} - -export async function readOmbulAction(idOrNameAndYear: number | { - name: string, - year: number, -}): Promise> { - //Auth route - const { status, authorized } = await getUser({ - requiredPermissions: [['OMBUL_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readOmbul(idOrNameAndYear)) -} - -export async function readOmbulsAction(): Promise> { - //Auth route - const { status, authorized } = await getUser({ - requiredPermissions: [['OMBUL_READ']] - }) - if (!authorized) { - return createActionError(status) - } - return await safeServerCall(() => readOmbuls()) -} diff --git a/src/actions/ombul/update.ts b/src/actions/ombul/update.ts deleted file mode 100644 index 1e7420ce0..000000000 --- a/src/actions/ombul/update.ts +++ /dev/null @@ -1,56 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError, createZodActionError } from '@/actions/error' -import { getUser } from '@/auth/getUser' -import { updateOmbul, updateOmbulFile } from '@/services/ombul/update' -import { updateOmbulFileValidation, updateOmbulValidation } from '@/services/ombul/validation' -import type { UpdateOmbulFileTypes, UpdateOmbulTypes } from '@/services/ombul/validation' -import type { ExpandedOmbul } from '@/services/ombul/Types' -import type { ActionReturn } from '@/actions/Types' - -/** - * A action to update an ombul - * @param id - The id of the ombul to update - * @param rawdata - The new data for the ombul including: name, year, issueNumber, description, - * @returns The updated ombul - */ -export async function updateOmbulAction( - id: number, - rawdata: FormData | UpdateOmbulTypes['Type'] -): Promise> { - // Auth route - const { status, authorized } = await getUser({ - requiredPermissions: [['OMBUL_UPDATE']] - }) - if (!authorized) return createActionError(status) - - //Parse the data - const parse = updateOmbulValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => updateOmbul(id, data)) -} - -/** - * A action that updates the ombul file (i.e. the pdf file) of an ombul - * @param id - The id of the ombul to update - * @param rawData - The new data for the new ombul file with field name 'file' - * @returns The updated ombul - */ -export async function updateOmbulFileAction( - id: number, - rawData: FormData | UpdateOmbulFileTypes['Type'] -): Promise> { - // auth route - const { status, authorized } = await getUser({ - requiredPermissions: [['OMBUL_UPDATE']] - }) - if (!authorized) return createActionError(status) - - const parse = updateOmbulFileValidation.typeValidate(rawData) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => updateOmbulFile(id, data)) -} diff --git a/src/actions/omegaOrder/create.ts b/src/actions/omegaOrder/create.ts deleted file mode 100644 index 9a867af52..000000000 --- a/src/actions/omegaOrder/create.ts +++ /dev/null @@ -1,15 +0,0 @@ -'use server' -import { createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { createOmegaOrder } from '@/services/omegaOrder/create' -import type { ActionReturn } from '@/actions/Types' - -export async function createOmegaOrderAction(): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['OMEGA_ORDER_CREATE']] - }) - if (!authorized) return createActionError(status) - - return safeServerCall(createOmegaOrder) -} diff --git a/src/actions/omegaOrder/read.ts b/src/actions/omegaOrder/read.ts deleted file mode 100644 index 6b0ac6e76..000000000 --- a/src/actions/omegaOrder/read.ts +++ /dev/null @@ -1,16 +0,0 @@ -'use server' -import { createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' -import type { ActionReturn } from '@/actions/Types' -import type { OmegaOrder } from '@prisma/client' - -export async function readCurrentOmegaOrderAction(): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['OMEGA_ORDER_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readCurrentOmegaOrder()) -} diff --git a/src/actions/omegaid/generate.ts b/src/actions/omegaid/generate.ts deleted file mode 100644 index 1adb2ef61..000000000 --- a/src/actions/omegaid/generate.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use server' - -import { createActionError } from '@/actions/error' -import { getUser } from '@/auth/getUser' -import { generateOmegaId } from '@/services/omegaid/generate' -import type { ActionReturn } from '@/actions/Types' - - -export async function generateOmegaIdAction(): Promise> { - const { user, authorized, status } = await getUser({ - userRequired: true, - }) - - if (!authorized) return createActionError(status) - - const token = generateOmegaId(user) - - return { - success: true, - data: token, - } -} diff --git a/src/actions/omegaid/read.ts b/src/actions/omegaid/read.ts deleted file mode 100644 index d34847475..000000000 --- a/src/actions/omegaid/read.ts +++ /dev/null @@ -1,14 +0,0 @@ -'use server' - -import { ServerError } from '@/services/error' - - -export async function readOmegaJWTPublicKey(): Promise { - const key = process.env.JWT_PUBLIC_KEY - - if (!key) { - throw new ServerError('INVALID CONFIGURATION', 'The JWT_PUBLIC_KEY must be set') - } - - return key -} diff --git a/src/actions/omegaquotes/create.ts b/src/actions/omegaquotes/create.ts deleted file mode 100644 index 90bce23d0..000000000 --- a/src/actions/omegaquotes/create.ts +++ /dev/null @@ -1,27 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError, createZodActionError } from '@/actions/error' -import { getUser } from '@/auth/getUser' -import { createQuote } from '@/services/omegaquotes/create' -import { createOmegaquotesValidation } from '@/services/omegaquotes/validation' -import type { CreateOmegaguotesTypes } from '@/services/omegaquotes/validation' -import type { ActionReturn } from '@/actions/Types' -import type { OmegaQuote } from '@prisma/client' - -export async function createQuoteAction( - rawdata: FormData | CreateOmegaguotesTypes['Type'] -): Promise> { - const { user, status, authorized } = await getUser({ - requiredPermissions: [['OMEGAQUOTES_WRITE']], - userRequired: true, - }) - if (!authorized) return createActionError(status) - - const parse = createOmegaquotesValidation.typeValidate(rawdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - const results = await safeServerCall(() => createQuote(user.id, data)) - - return results -} diff --git a/src/actions/omegaquotes/read.ts b/src/actions/omegaquotes/read.ts deleted file mode 100644 index 384fefb07..000000000 --- a/src/actions/omegaquotes/read.ts +++ /dev/null @@ -1,20 +0,0 @@ -'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError } from '@/actions/error' -import { getUser } from '@/auth/getUser' -import { readQuotesPage } from '@/services/omegaquotes/read' -import type { ActionReturn } from '@/actions/Types' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { OmegaquoteCursor, OmegaquoteFiltered } from '@/services/omegaquotes/Types' - -export async function readQuotesPageAction( - readPageInput: ReadPageInput -): Promise> { - //TODO: REFACTOR when new permission system is working - const { status, authorized } = await getUser({ - requiredPermissions: [['OMEGAQUOTES_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readQuotesPage(readPageInput)) -} diff --git a/src/actions/permissions/index.ts b/src/actions/permissions/index.ts deleted file mode 100644 index 7028e1ba8..000000000 --- a/src/actions/permissions/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use server' - -import { action } from '@/actions/action' -import { PermissionMethods } from '@/services/permissions/methods' - -export const readPermissionOfGroupAction = action(PermissionMethods.readPermissionsOfGroup) -export const readPermissionMatrixAction = action(PermissionMethods.readPermissionMatrix) -export const readDefaultPermissionsAction = action(PermissionMethods.readDefaultPermissions) -export const updateDefaultPermissionsAction = action(PermissionMethods.updateDefaultPermissions) -export const updateGroupPermissionAction = action(PermissionMethods.updateGroupPermission) diff --git a/src/actions/safeServerCall.ts b/src/actions/safeServerCall.ts deleted file mode 100644 index abc48d409..000000000 --- a/src/actions/safeServerCall.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createActionError, createZodActionError } from './error' -import { ParseError, Smorekopp } from '@/services/error' -import type { ActionReturn } from './Types' - -/** - * A function that calls a server function. If all goes well, it returns a ActionReturn with the data. - * If an error is thrown it returns ActionReturn of success false and the error. - * The function handles ServerErrors class, and treats all other errors as unknown. - * @param call - A async server function to call. - * @returns - A promise that resolves to an ActionReturn. - */ -export async function safeServerCall(call: () => Promise): Promise> { - try { - const data = await call() - return { - success: true, - data - } - } catch (error) { - if (error instanceof ParseError) { - return createZodActionError(error.parseError) - } - if (error instanceof Smorekopp) { - return createActionError(error.errorCode, error.errors) - } - return createActionError('UNKNOWN ERROR', 'unknown error') - } -} diff --git a/src/actions/screens/authers.ts b/src/actions/screens/authers.ts deleted file mode 100644 index 9113a0e7e..000000000 --- a/src/actions/screens/authers.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export const adminScreenAuther = RequirePermission.staticFields({ permission: 'SCREEN_ADMIN' }) -export const readScreenAuther = RequirePermission.staticFields({ permission: 'SCREEN_READ' }) diff --git a/src/actions/screens/create.ts b/src/actions/screens/create.ts deleted file mode 100644 index 639adea54..000000000 --- a/src/actions/screens/create.ts +++ /dev/null @@ -1,20 +0,0 @@ -'use server' -import { adminScreenAuther } from './authers' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError, createZodActionError } from '@/actions/error' -import { createScreenValidation, type CreateScreenTypes } from '@/services/screens/validation' -import { createScreen } from '@/services/screens/create' -import { Session } from '@/auth/Session' -import type { Screen } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' - -export async function createScreenAction(formdata: CreateScreenTypes['Type']): Promise> { - const authRes = adminScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) - if (!authRes.authorized) return createActionError(authRes.status) - - const parse = createScreenValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createScreen(data)) -} diff --git a/src/actions/screens/destroy.ts b/src/actions/screens/destroy.ts deleted file mode 100644 index 14e6d75b6..000000000 --- a/src/actions/screens/destroy.ts +++ /dev/null @@ -1,14 +0,0 @@ -'use server' -import { adminScreenAuther } from './authers' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError } from '@/actions/error' -import { destroyScreen } from '@/services/screens/destroy' -import { Session } from '@/auth/Session' -import type { ActionReturn } from '@/actions/Types' - -export async function destroyScreenAction(id: number): Promise> { - const authRes = adminScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) - if (!authRes.authorized) return createActionError(authRes.status) - - return await safeServerCall(() => destroyScreen(id)) -} diff --git a/src/actions/screens/pages/create.ts b/src/actions/screens/pages/create.ts deleted file mode 100644 index af970e631..000000000 --- a/src/actions/screens/pages/create.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use server' -import { createScreenValidation } from '@/services/screens/validation' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { createActionError, createZodActionError } from '@/actions/error' -import { createPage } from '@/services/screens/pages/create' -import type { ScreenPage } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' -import type { CreateScreenTypes } from '@/services/screens/validation' - -export async function createPageAction(formdata: CreateScreenTypes['Type']): Promise> { - const { status, authorized } = await getUser({ - requiredPermissions: [['SCREEN_ADMIN']] - }) - if (!authorized) return createActionError(status) - - const parse = createScreenValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => createPage(data)) -} diff --git a/src/actions/screens/pages/destroy.ts b/src/actions/screens/pages/destroy.ts deleted file mode 100644 index 3c21afdd1..000000000 --- a/src/actions/screens/pages/destroy.ts +++ /dev/null @@ -1,16 +0,0 @@ -'use server' -import { createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { destroyPage } from '@/services/screens/pages/destroy' -import type { ActionReturn } from '@/actions/Types' - - -export async function destroyPageAction(id: number): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCREEN_ADMIN']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => destroyPage(id)) -} diff --git a/src/actions/screens/pages/read.ts b/src/actions/screens/pages/read.ts deleted file mode 100644 index 1a0cfbccb..000000000 --- a/src/actions/screens/pages/read.ts +++ /dev/null @@ -1,26 +0,0 @@ -'use server' -import { getUser } from '@/auth/getUser' -import { createActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { readPage, readPages } from '@/services/screens/pages/read' -import type { ScreenPage } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' -import type { ExpandedScreenPage } from '@/services/screens/pages/Types' - -export async function readPageAction(id: number): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCREEN_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readPage(id)) -} - -export async function readPagesAction(): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCREEN_READ']] - }) - if (!authorized) return createActionError(status) - - return await safeServerCall(() => readPages()) -} diff --git a/src/actions/screens/pages/update.ts b/src/actions/screens/pages/update.ts deleted file mode 100644 index f70ec9b38..000000000 --- a/src/actions/screens/pages/update.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use server' -import { createActionError, createZodActionError } from '@/actions/error' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { updatePage } from '@/services/screens/pages/update' -import { updatePageValidation } from '@/services/screens/pages/validation' -import type { UpdatePageTypes } from '@/services/screens/pages/validation' -import type { ActionReturn } from '@/actions/Types' -import type { ScreenPage } from '@prisma/client' - -export async function updatePageAction(id: number, formdata: UpdatePageTypes['Type']): Promise> { - const { authorized, status } = await getUser({ - requiredPermissions: [['SCREEN_ADMIN']] - }) - if (!authorized) return createActionError(status) - - const parse = updatePageValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => updatePage(id, data)) -} diff --git a/src/actions/screens/read.ts b/src/actions/screens/read.ts deleted file mode 100644 index d1f900bc8..000000000 --- a/src/actions/screens/read.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use server' -import { readScreenAuther } from './authers' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError } from '@/actions/error' -import { readScreen, readScreens } from '@/services/screens/read' -import { Session } from '@/auth/Session' -import type { Screen } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' - -export async function readScreenAction(id: number): Promise> { - const authRes = readScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) - if (!authRes.authorized) return createActionError(authRes.status) - - return await safeServerCall(() => readScreen(id)) -} - -export async function readScreensAction(): Promise> { - const authRes = readScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) - if (!authRes.authorized) return createActionError(authRes.status) - - return await safeServerCall(() => readScreens()) -} diff --git a/src/actions/screens/update.ts b/src/actions/screens/update.ts deleted file mode 100644 index 541707f4c..000000000 --- a/src/actions/screens/update.ts +++ /dev/null @@ -1,32 +0,0 @@ -'use server' -import { adminScreenAuther } from './authers' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError, createZodActionError } from '@/actions/error' -import { updateScreenValidation, type UpdateScreenTypes } from '@/services/screens/validation' -import { movePageInScreen, updateScreen } from '@/services/screens/update' -import { Session } from '@/auth/Session' -import type { Screen } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' -import type { ScreenPageMoveDirection } from '@/services/screens/Types' - -export async function updateScreenAction(id: number, formdata: UpdateScreenTypes['Type']): Promise> { - const authRes = adminScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) - if (!authRes.authorized) return createActionError(authRes.status) - - const parse = updateScreenValidation.typeValidate(formdata) - if (!parse.success) return createZodActionError(parse) - const data = parse.data - - return await safeServerCall(() => updateScreen(id, data)) -} - -export async function movePageInScreenAction( - id: {screen: number, page: number}, - direction: ScreenPageMoveDirection -): Promise> { - const authRes = adminScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) - if (!authRes.authorized) return createActionError(authRes.status) - - return await safeServerCall(() => movePageInScreen(id, direction)) -} - diff --git a/src/actions/shop/product.ts b/src/actions/shop/product.ts deleted file mode 100644 index 05fa7ec74..000000000 --- a/src/actions/shop/product.ts +++ /dev/null @@ -1,14 +0,0 @@ -'use server' - -import { action } from '@/actions/action' -import { ProductMethods } from '@/services/shop/product/methods' - -export const readProductsAction = action(ProductMethods.readMany) -export const readProductAction = action(ProductMethods.read) -export const createProductAction = action(ProductMethods.create) -export const updateProductAction = action(ProductMethods.update) - -export const createProductForShopAction = action(ProductMethods.createForShop) -export const updateProductForShopAction = action(ProductMethods.updateForShop) - -export const createShopProductConnectionAction = action(ProductMethods.createShopConnection) diff --git a/src/actions/shop/shop.ts b/src/actions/shop/shop.ts deleted file mode 100644 index 551af7665..000000000 --- a/src/actions/shop/shop.ts +++ /dev/null @@ -1,9 +0,0 @@ -'use server' - -import { action } from '@/actions/action' -import { ShopMethods } from '@/services/shop/shop/methods' - -export const readShopsAction = action(ShopMethods.readMany) -export const readShopAction = action(ShopMethods.read) -export const createShopAction = action(ShopMethods.create) - diff --git a/src/actions/users/create.ts b/src/actions/users/create.ts deleted file mode 100644 index 683d195ca..000000000 --- a/src/actions/users/create.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { UserMethods } from '@/services/users/methods' - -/** - * A action that creates a user by the given data. It will also hash the password - * @param rawdata - The user to create - * @returns - The created user - */ -export const createUserAction = action(UserMethods.create) diff --git a/src/actions/users/destroy.ts b/src/actions/users/destroy.ts deleted file mode 100644 index bf1a24a4e..000000000 --- a/src/actions/users/destroy.ts +++ /dev/null @@ -1,11 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { UserMethods } from '@/services/users/methods' - -/** - * Action to destroy a user by the given id - * @param id - The id of the user to destroy - * @returns - */ -export const destroyUserAction = action(UserMethods.destroy) - diff --git a/src/actions/users/read.ts b/src/actions/users/read.ts deleted file mode 100644 index b33bd5b5f..000000000 --- a/src/actions/users/read.ts +++ /dev/null @@ -1,25 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { UserMethods } from '@/services/users/methods' -import { GroupMethods } from '@/services/groups/methods' - -/** - * A action to read a page of users with the given details (filtering) - * @param readPageInput - This is a) the page to read and b) the details to filter by like - * name and groups - * @returns - */ -export const readUserPageAction = action(UserMethods.readPage) - -/** - * Action meant to read the profile of a user. - * A profile is a user with more information about them attached. - * @param username - The username of the user to read - * @returns - The profile of the user - */ -export const readUserProfileAction = action(UserMethods.readProfile) - -export const readUserAction = action(UserMethods.read) - -export const readGroupsForPageFilteringAction = action(GroupMethods.readGroupsExpanded) - diff --git a/src/actions/users/update.ts b/src/actions/users/update.ts deleted file mode 100644 index 6be51862e..000000000 --- a/src/actions/users/update.ts +++ /dev/null @@ -1,8 +0,0 @@ -'use server' -import { action } from '@/actions/action' -import { UserMethods } from '@/services/users/methods' - -export const updateUserAction = action(UserMethods.update) -export const registerNewEmailAction = action(UserMethods.registerNewEmail) -export const registerUser = action(UserMethods.register) -export const registerStudentCardInQueueAction = action(UserMethods.registerStudentCardInQueue) diff --git a/src/actions/visibility/Types.ts b/src/actions/visibility/Types.ts deleted file mode 100644 index 66d6e0cc2..000000000 --- a/src/actions/visibility/Types.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { ExpandedGroup, GroupsStructured } from '@/services/groups/Types' - -export type VisibilityRequiermentForAdmin = { - name: string - groups: (ExpandedGroup & { - selected: boolean - })[] -} - -export type VisibilityStructuredForAdmin = { - published: boolean - purpose: string -} & ( - { - type: 'REGULAR' - groups: GroupsStructured - regular: VisibilityRequiermentForAdmin[] - admin: VisibilityRequiermentForAdmin[] - } | { - type: 'SPECIAL' - message: string - regular: string - admin: string - } -) diff --git a/src/actions/visibility/update.ts b/src/actions/visibility/update.ts deleted file mode 100644 index 5cb0ae162..000000000 --- a/src/actions/visibility/update.ts +++ /dev/null @@ -1,11 +0,0 @@ -'use server' -import type { VisibilityLevelType } from '@/services/visibility/Types' -import type { ActionReturn } from '@/actions/Types' - -export async function updateVisibilityAction( - level: VisibilityLevelType, - formdata: FormData -): Promise> { - console.log(formdata) - return { success: true, data: undefined } -} diff --git a/src/app/(auth)/register-email/EmailregistrationForm.tsx b/src/app/(auth)/register-email/EmailregistrationForm.tsx index 18f710887..e5dd48cf1 100644 --- a/src/app/(auth)/register-email/EmailregistrationForm.tsx +++ b/src/app/(auth)/register-email/EmailregistrationForm.tsx @@ -1,11 +1,11 @@ 'use client' -import { registerNewEmailAction } from '@/actions/users/update' +import { registerNewEmailAction } from '@/services/users/actions' import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' -import { configureAction } from '@/actions/configureAction' +import { configureAction } from '@/services/configureAction' import { useSearchParams, useRouter } from 'next/navigation' import { useState } from 'react' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' export default function EmailRegistrationForm({ user diff --git a/src/app/(auth)/register-email/page.tsx b/src/app/(auth)/register-email/page.tsx index 0bb1bfc0f..79334b980 100644 --- a/src/app/(auth)/register-email/page.tsx +++ b/src/app/(auth)/register-email/page.tsx @@ -1,6 +1,6 @@ import EmailRegistrationForm from './EmailregistrationForm' -import { getUser } from '@/auth/getUser' -import { readUserAction } from '@/actions/users/read' +import { getUser } from '@/auth/session/getUser' +import { readUserAction } from '@/services/users/actions' import { notFound, redirect } from 'next/navigation' export default async function Registeremail() { diff --git a/src/app/(auth)/register/RegistrationForm.tsx b/src/app/(auth)/register/RegistrationForm.tsx index ae24c2736..91f1e511f 100644 --- a/src/app/(auth)/register/RegistrationForm.tsx +++ b/src/app/(auth)/register/RegistrationForm.tsx @@ -1,11 +1,11 @@ 'use client' -import { configureAction } from '@/actions/configureAction' -import { registerUser } from '@/actions/users/update' +import { configureAction } from '@/services/configureAction' +import { registerUser } from '@/services/users/actions' import Form from '@/components/Form/Form' import Checkbox from '@/components/UI/Checkbox' import { SelectString } from '@/components/UI/Select' import TextInput from '@/components/UI/TextInput' -import { UserConfig } from '@/services/users/config' +import { sexConfig } from '@/services/users/constants' import { SEX, type User } from '@prisma/client' import { signIn } from 'next-auth/react' import { useSearchParams } from 'next/navigation' @@ -24,7 +24,7 @@ export default function RegistrationForm({ const sexOptions = Object.values(SEX).map(sex => ({ value: sex, - label: UserConfig.sexConfig[sex].label + label: sexConfig[sex].label })) return
& { diff --git a/src/app/_components/Cms/ArticleSection/ArticleSection.tsx b/src/app/_components/Cms/ArticleSection/ArticleSection.tsx index 1aa864064..806204ec4 100644 --- a/src/app/_components/Cms/ArticleSection/ArticleSection.tsx +++ b/src/app/_components/Cms/ArticleSection/ArticleSection.tsx @@ -5,7 +5,7 @@ import AddPartToArticleSection from './AddPartToArticleSection' import CmsLink from '@/cms/CmsLink/CmsLink' import CmsImage from '@/cms/CmsImage/CmsImage' import CmsParagraph from '@/cms/CmsParagraph/CmsParagraph' -import type { ExpandedCmsImage } from '@/cms/images/Types' +import type { ExpandedCmsImage } from '@/cms/images/types' import type { ArticleSection as ArticleSectionT, CmsParagraph as CmsParagraphT, diff --git a/src/app/_components/Cms/ArticleSection/ImageControls.tsx b/src/app/_components/Cms/ArticleSection/ImageControls.tsx index 7834fced7..21805c64f 100644 --- a/src/app/_components/Cms/ArticleSection/ImageControls.tsx +++ b/src/app/_components/Cms/ArticleSection/ImageControls.tsx @@ -1,6 +1,6 @@ 'use client' import styles from './ImageControls.module.scss' -import { updateArticleSectionAction } from '@/cms/articleSections/update' +import { updateArticleSectionAction } from '@/cms/articleSections/actions' import { imageSizeIncrement, maxImageSize, minImageSize } from '@/cms/articleSections/ConfigVars' import useEditing from '@/hooks/useEditing' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' diff --git a/src/app/_components/Cms/ArticleSection/RemovePart.tsx b/src/app/_components/Cms/ArticleSection/RemovePart.tsx index 7b0c085c7..e13e7625e 100644 --- a/src/app/_components/Cms/ArticleSection/RemovePart.tsx +++ b/src/app/_components/Cms/ArticleSection/RemovePart.tsx @@ -1,6 +1,6 @@ 'use client' import styles from './RemovePart.module.scss' -import { removeArticleSectionPartAction } from '@/cms/articleSections/update' +import { removeArticleSectionPartAction } from '@/cms/articleSections/actions' import Form from '@/components/Form/Form' import useClickOutsideRef from '@/hooks/useClickOutsideRef' import useEditing from '@/hooks/useEditing' @@ -8,7 +8,7 @@ import { useRouter } from 'next/navigation' import { useState } from 'react' import { faX } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import type { ArticleSectionPart } from '@/cms/articleSections/Types' +import type { ArticleSectionPart } from '@/cms/articleSections/types' type PropTypes = { part: ArticleSectionPart, diff --git a/src/app/_components/Cms/CmsImage/ChangeImage.tsx b/src/app/_components/Cms/CmsImage/ChangeImage.tsx index 92ccd5b14..b154db70d 100644 --- a/src/app/_components/Cms/CmsImage/ChangeImage.tsx +++ b/src/app/_components/Cms/CmsImage/ChangeImage.tsx @@ -4,7 +4,7 @@ import ChangeImageForm from './ChangeImageForm' import Image from '@/components/Image/Image' import { ImageSelectionContext } from '@/contexts/ImageSelection' import Form from '@/components/Form/Form' -import { updateCmsImageConfigAction } from '@/actions/cms/images/update' +import { updateCmsImageConfigAction } from '@/services/cms/images/actions' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faTurnUp } from '@fortawesome/free-solid-svg-icons' import React, { useContext, useEffect, useState } from 'react' diff --git a/src/app/_components/Cms/CmsImage/ChangeImageForm.tsx b/src/app/_components/Cms/CmsImage/ChangeImageForm.tsx index f1734a248..86f300124 100644 --- a/src/app/_components/Cms/CmsImage/ChangeImageForm.tsx +++ b/src/app/_components/Cms/CmsImage/ChangeImageForm.tsx @@ -1,5 +1,5 @@ 'use client' -import { updateCmsImageAction } from '@/actions/cms/images/update' +import { updateCmsImageAction } from '@/services/cms/images/actions' import Form from '@/components/Form/Form' import { ImageSelectionContext } from '@/contexts/ImageSelection' import { useContext } from 'react' diff --git a/src/app/_components/Cms/CmsImage/CmsImage.tsx b/src/app/_components/Cms/CmsImage/CmsImage.tsx index 0b323b744..514ddb69c 100644 --- a/src/app/_components/Cms/CmsImage/CmsImage.tsx +++ b/src/app/_components/Cms/CmsImage/CmsImage.tsx @@ -1,9 +1,9 @@ import CmsImageEditor from './CmsImageEditor' import styles from './CmsImage.module.scss' import Image, { SrcImage } from '@/components/Image/Image' -import { readSpecialImageAction } from '@/actions/images/read' +import { readSpecialImageAction } from '@/services/images/actions' import React from 'react' -import type { ExpandedCmsImage } from '@/cms/images/Types' +import type { ExpandedCmsImage } from '@/cms/images/types' import type { PropTypes as ImagePropTypes } from '@/components/Image/Image' export type PropTypes = Omit< diff --git a/src/app/_components/Cms/CmsImage/CmsImageClient.tsx b/src/app/_components/Cms/CmsImage/CmsImageClient.tsx index 582da0c61..fa544f701 100644 --- a/src/app/_components/Cms/CmsImage/CmsImageClient.tsx +++ b/src/app/_components/Cms/CmsImage/CmsImageClient.tsx @@ -3,7 +3,7 @@ import CmsImageEditor from './CmsImageEditor' import styles from './CmsImage.module.scss' import { fallbackImage } from './CmsImage' import Image, { SrcImage } from '@/components/Image/Image' -import { readSpecialImageAction } from '@/actions/images/read' +import { readSpecialImageAction } from '@/services/images/actions' import { useState, useEffect } from 'react' import type { PropTypes } from './CmsImage' import type { Image as ImageT } from '@prisma/client' diff --git a/src/app/_components/Cms/CmsImage/CmsImageEditor.tsx b/src/app/_components/Cms/CmsImage/CmsImageEditor.tsx index ea9410b9f..2637386d4 100644 --- a/src/app/_components/Cms/CmsImage/CmsImageEditor.tsx +++ b/src/app/_components/Cms/CmsImage/CmsImageEditor.tsx @@ -7,8 +7,8 @@ import PopUp from '@/components/PopUp/PopUp' import EndlessScroll from '@/components/PagingWrappers/EndlessScroll' import CollectionCard from '@/components/Image/Collection/CollectionCard' import ImageList from '@/components/Image/ImageList/ImageList' -import ImageCollectionPagingProvider, { ImageCollectionPagingContext } from '@/contexts/paging/ImageCollectionPaging' -import ImagePagingProvider from '@/contexts/paging/ImagePaging' +import { ImageCollectionPagingProvider, ImageCollectionPagingContext } from '@/contexts/paging/ImageCollectionPaging' +import { ImagePagingProvider } from '@/contexts/paging/ImagePaging' import PopUpProvider from '@/contexts/PopUp' import ImageSelectionProvider from '@/contexts/ImageSelection' import useEditing from '@/hooks/useEditing' diff --git a/src/app/_components/Cms/CmsImage/SpecialCmsImage.tsx b/src/app/_components/Cms/CmsImage/SpecialCmsImage.tsx index 672435efb..0def3826d 100644 --- a/src/app/_components/Cms/CmsImage/SpecialCmsImage.tsx +++ b/src/app/_components/Cms/CmsImage/SpecialCmsImage.tsx @@ -1,5 +1,5 @@ import CmsImage from './CmsImage' -import { readSpecialCmsImageAction } from '@/actions/cms/images/read' +import { readSpecialCmsImageAction } from '@/services/cms/images/actions' import type { SpecialCmsImage as SpecialCmsImageT } from '@prisma/client' import type { PropTypes as CmsImageProps } from './CmsImage' diff --git a/src/app/_components/Cms/CmsImage/SpecialCmsImageClient.tsx b/src/app/_components/Cms/CmsImage/SpecialCmsImageClient.tsx index e0a20b7fd..f3ae1377d 100644 --- a/src/app/_components/Cms/CmsImage/SpecialCmsImageClient.tsx +++ b/src/app/_components/Cms/CmsImage/SpecialCmsImageClient.tsx @@ -1,6 +1,6 @@ 'use client' import CmsImageClient from './CmsImageClient' -import { readSpecialCmsImageAction } from '@/actions/cms/images/read' +import { readSpecialCmsImageAction } from '@/services/cms/images/actions' import useActionCall from '@/hooks/useActionCall' import { useCallback } from 'react' import type { PropTypes } from './SpecialCmsImage' diff --git a/src/app/_components/Cms/CmsLink/CmsLinkEditor.tsx b/src/app/_components/Cms/CmsLink/CmsLinkEditor.tsx index d8ceb9a0a..a683fea17 100644 --- a/src/app/_components/Cms/CmsLink/CmsLinkEditor.tsx +++ b/src/app/_components/Cms/CmsLink/CmsLinkEditor.tsx @@ -3,7 +3,7 @@ import styles from './CmsLinkEditor.module.scss' import TextInput from '@/components/UI/TextInput' import EditOverlay from '@/cms/EditOverlay' import Form from '@/components/Form/Form' -import { updateCmsLinkAction } from '@/cms/links/update' +import { updateCmsLinkAction } from '@/cms/links/actions' import PopUp from '@/components/PopUp/PopUp' import useEditing from '@/hooks/useEditing' import { useRouter } from 'next/navigation' diff --git a/src/app/_components/Cms/CmsParagraph/CmsParagraphEditor.tsx b/src/app/_components/Cms/CmsParagraph/CmsParagraphEditor.tsx index c5afb0969..373f1e248 100644 --- a/src/app/_components/Cms/CmsParagraph/CmsParagraphEditor.tsx +++ b/src/app/_components/Cms/CmsParagraph/CmsParagraphEditor.tsx @@ -2,9 +2,9 @@ import styles from './CmsParagraphEditor.module.scss' import EditOverlay from '@/components/Cms/EditOverlay' import Form from '@/components/Form/Form' -import { updateCmsParagraphAction } from '@/actions/cms/paragraphs/update' import PopUp from '@/components/PopUp/PopUp' import useEditing from '@/hooks/useEditing' +import { updateCmsParagraphAction } from '@/services/cms/paragraphs/actions' import { useState } from 'react' import { useRouter } from 'next/navigation' import 'easymde/dist/easymde.min.css' diff --git a/src/app/_components/Cms/CmsParagraph/SpecialCmsParagraph.tsx b/src/app/_components/Cms/CmsParagraph/SpecialCmsParagraph.tsx index ff32037d2..84bf5482b 100644 --- a/src/app/_components/Cms/CmsParagraph/SpecialCmsParagraph.tsx +++ b/src/app/_components/Cms/CmsParagraph/SpecialCmsParagraph.tsx @@ -1,5 +1,5 @@ import CmsParagraph from './CmsParagraph' -import { readSpecialCmsParagraphAction } from '@/actions/cms/paragraphs/read' +import { readSpecialCmsParagraphAction } from '@/services/cms/paragraphs/actions' import React from 'react' import type { PropTypes as PropTypesCmsParapraph } from './CmsParagraph' import type { SpecialCmsParagraph as SpecialCmsParagraphT } from '@prisma/client' diff --git a/src/app/_components/CommitteeImage/CommitteeImage.tsx b/src/app/_components/CommitteeImage/CommitteeImage.tsx index cdfb49809..11d14840b 100644 --- a/src/app/_components/CommitteeImage/CommitteeImage.tsx +++ b/src/app/_components/CommitteeImage/CommitteeImage.tsx @@ -3,7 +3,7 @@ import CmsImage from '@/cms/CmsImage/CmsImage' import Image from '@/components/Image/Image' import type { ReactNode } from 'react' import type { Image as ImageT } from '@prisma/client' -import type { ExpandedCmsImage } from '@/services/cms/images/Types' +import type { ExpandedCmsImage } from '@/cms/images/types' type PropTypes = { children?: ReactNode diff --git a/src/app/_components/Company/Company.tsx b/src/app/_components/Company/Company.tsx index b11bb0a94..e4e04063a 100644 --- a/src/app/_components/Company/Company.tsx +++ b/src/app/_components/Company/Company.tsx @@ -5,12 +5,11 @@ import TextInput from '@/UI/TextInput' import CmsImage from '@/cms/CmsImage/CmsImage' import CmsImageClient from '@/cms/CmsImage/CmsImageClient' import Form from '@/components/Form/Form' -import { updateComanyAction } from '@/actions/career/companies/update' -import { destroyCompanyAction } from '@/actions/career/companies/destroy' -import { CompanyAuthers } from '@/services/career/companies/authers' -import { configureAction } from '@/actions/configureAction' -import type { CompanyExpanded } from '@/services/career/companies/Types' -import type { SessionMaybeUser } from '@/auth/Session' +import { companyAuth } from '@/services/career/companies/auth' +import { destroyCompanyAction, updateComanyAction } from '@/services/career/companies/actions' +import { configureAction } from '@/services/configureAction' +import type { CompanyExpanded } from '@/services/career/companies/types' +import type { SessionMaybeUser } from '@/auth/session/Session' type PropTypes = { company: CompanyExpanded, @@ -39,8 +38,8 @@ export default function Company({ logoWidth = 300, squareLogo = true, }: PropTypes) { - const canUpdate = CompanyAuthers.update.dynamicFields({}).auth(session) - const canDestroy = CompanyAuthers.destroy.dynamicFields({}).auth(session) + const canUpdate = companyAuth.update.dynamicFields({}).auth(session) + const canDestroy = companyAuth.destroy.dynamicFields({}).auth(session) return (
{asClient ? diff --git a/src/app/_components/Company/CompanyList.tsx b/src/app/_components/Company/CompanyList.tsx index e4fb604e8..58d47bac4 100644 --- a/src/app/_components/Company/CompanyList.tsx +++ b/src/app/_components/Company/CompanyList.tsx @@ -3,7 +3,7 @@ import { companyListRenderer } from './CompanyListRenderer' import styles from './CompanyList.module.scss' import { CompanyPagingContext } from '@/contexts/paging/CompanyPaging' import EndlessScroll from '@/components/PagingWrappers/EndlessScroll' -import { useUser } from '@/auth/useUser' +import { useUser } from '@/auth/session/useUser' import type { ReactNode } from 'react' type PropTypes = { diff --git a/src/app/_components/Company/CompanyListFilter.tsx b/src/app/_components/Company/CompanyListFilter.tsx index 33b85f3dc..b278e27b6 100644 --- a/src/app/_components/Company/CompanyListFilter.tsx +++ b/src/app/_components/Company/CompanyListFilter.tsx @@ -2,7 +2,7 @@ import styles from './CompanyListFilter.module.scss' import TextInput from '@/UI/TextInput' import { useDebounce } from '@/hooks/useDebounce' -import { QueryParams } from '@/lib/query-params/queryParams' +import { QueryParams } from '@/lib/queryParams/queryParams' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faSearch } from '@fortawesome/free-solid-svg-icons' import { useRouter } from 'next/navigation' diff --git a/src/app/_components/Company/CompanyListRenderer.tsx b/src/app/_components/Company/CompanyListRenderer.tsx index 6ad8e939b..7096aef7b 100644 --- a/src/app/_components/Company/CompanyListRenderer.tsx +++ b/src/app/_components/Company/CompanyListRenderer.tsx @@ -1,6 +1,6 @@ import Company from './Company' -import type { SessionMaybeUser } from '@/auth/Session' -import type { CompanyExpanded } from '@/services/career/companies/Types' +import type { SessionMaybeUser } from '@/auth/session/Session' +import type { CompanyExpanded } from '@/services/career/companies/types' /** * Used to render schools server side and client side in consistent way diff --git a/src/app/_components/EditableTextField/EditableTextField.tsx b/src/app/_components/EditableTextField/EditableTextField.tsx index 5f3e488b8..5f57d24db 100644 --- a/src/app/_components/EditableTextField/EditableTextField.tsx +++ b/src/app/_components/EditableTextField/EditableTextField.tsx @@ -8,11 +8,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faPencil } from '@fortawesome/free-solid-svg-icons' import type { PropTypes as FormPropTypes } from '@/components/Form/Form' -type PropTypes = { +type PropTypes = { props?: Omit, 'children' | 'contentEditable'> editable: boolean, children: React.ReactNode, - formProps: Omit, 'title' | 'children' | 'submitText'> + formProps: Omit, 'title' | 'children' | 'submitText'> inputName: string, submitButton: { text: string, @@ -28,14 +28,14 @@ type PropTypes = { * @param submitButton - The props to pass to the submit button * @param props - further props to pass to the text element */ -export default function EditableTextField({ +export default function EditableTextField({ editable, children, formProps, submitButton, inputName, ...props -}: PropTypes +}: PropTypes ) { const [value, setValue] = useState('') const [noChange, setNoChange] = useState(true) diff --git a/src/app/_components/Event/EventCard.tsx b/src/app/_components/Event/EventCard.tsx index c2252482f..59db926d2 100644 --- a/src/app/_components/Event/EventCard.tsx +++ b/src/app/_components/Event/EventCard.tsx @@ -4,7 +4,7 @@ import ImageCard from '@/components/ImageCard/ImageCard' import React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCalendar, faLocationDot, faUsers } from '@fortawesome/free-solid-svg-icons' -import type { EventExpanded } from '@/services/events/Types' +import type { EventExpanded } from '@/services/events/types' export default function EventCard({ event }: { event: EventExpanded, diff --git a/src/app/_components/Event/EventTagsAdmin.tsx b/src/app/_components/Event/EventTagsAdmin.tsx index 4ab2318d9..28dffa82e 100644 --- a/src/app/_components/Event/EventTagsAdmin.tsx +++ b/src/app/_components/Event/EventTagsAdmin.tsx @@ -2,14 +2,12 @@ import EventTag from './EventTag' import styles from './EventTagsAdmin.module.scss' import Form from '@/components/Form/Form' import { SettingsHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' -import { createEventTagAction } from '@/actions/events/tags/create' import TextInput from '@/UI/TextInput' import Textarea from '@/UI/Textarea' import ColorInput from '@/UI/ColorInput' -import { updateEventTagAction } from '@/actions/events/tags/update' -import { QueryParams } from '@/lib/query-params/queryParams' -import { destroyEventTagAction } from '@/actions/events/tags/destroy' -import { configureAction } from '@/actions/configureAction' +import { QueryParams } from '@/lib/queryParams/queryParams' +import { destroyEventTagAction, updateEventTagAction, createEventTagAction } from '@/services/events/tags/actions' +import { configureAction } from '@/services/configureAction' import Link from 'next/link' import type { EventTag as EventTagT } from '@prisma/client' diff --git a/src/app/_components/Form/Form.tsx b/src/app/_components/Form/Form.tsx index 0e6605d33..aaac3a3d8 100644 --- a/src/app/_components/Form/Form.tsx +++ b/src/app/_components/Form/Form.tsx @@ -1,6 +1,6 @@ 'use client' import styles from './Form.module.scss' -import { SUCCESS_FEEDBACK_TIME } from './ConfigVars' +import { SUCCESS_FEEDBACK_TIME } from './constants' import { PopUpContext } from '@/contexts/PopUp' import SubmitButton from '@/components/UI/SubmitButton' import React, { Children, useContext, useEffect, useState } from 'react' @@ -9,17 +9,17 @@ import { useRouter } from 'next/navigation' import type { PopUpKeyType } from '@/contexts/PopUp' import type { Colors, Confirmation } from '@/components/UI/SubmitButton' import type { FormHTMLAttributes, ReactNode, DetailedHTMLProps } from 'react' -import type { Action } from '@/actions/Types' +import type { Action } from '@/services/actionTypes' import type { ErrorMessage } from '@/services/error' type FormType = DetailedHTMLProps, HTMLFormElement> -export type PropTypes = Omit & { +export type PropTypes = Omit & { children?: ReactNode, title?: string, submitText?: string, submitColor?: Colors, confirmation?: Confirmation, - action: Action, + action: Action, successCallback?: (data?: ReturnType) => void, refreshOnSuccess?: boolean, navigateOnSuccess?: string | ((data?: ReturnType) => string | null), @@ -62,7 +62,7 @@ const makeInputArray = (children: ReactNode): Inputs => } }) -export default function Form({ +export default function Form({ children, title, submitText = 'create', @@ -78,7 +78,7 @@ export default function Form({ closePopUpOnSuccess, buttonClassName, ...props -}: PropTypes) { +}: PropTypes) { const [generalErrors, setGeneralErrors] = useState() const [inputs, setInputs] = useState(makeInputArray(children)) const [success, setSuccess] = useState(false) diff --git a/src/app/_components/Form/ConfigVars.ts b/src/app/_components/Form/constants.ts similarity index 100% rename from src/app/_components/Form/ConfigVars.ts rename to src/app/_components/Form/constants.ts diff --git a/src/app/_components/Image/ImageList/ImageDisplay.tsx b/src/app/_components/Image/ImageList/ImageDisplay.tsx index bc625fbe7..6448ee795 100644 --- a/src/app/_components/Image/ImageList/ImageDisplay.tsx +++ b/src/app/_components/Image/ImageList/ImageDisplay.tsx @@ -6,13 +6,12 @@ import Image from '@/components/Image/Image' import useKeyPress from '@/hooks/useKeyPress' import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' -import { updateImageAction } from '@/actions/images/update' -import { destroyImageAction } from '@/actions/images/destroy' import { ImagePagingContext } from '@/contexts/paging/ImagePaging' import { ImageDisplayContext } from '@/contexts/ImageDisplayProvider' -import { updateImageCollectionAction } from '@/actions/images/collections/update' import LicenseChooser from '@/components/LicenseChooser/LicenseChooser' -import { configureAction } from '@/actions/configureAction' +import { updateImageCollectionAction } from '@/services/images/collections/actions' +import { destroyImageAction, updateImageAction } from '@/services/images/actions' +import { configureAction } from '@/services/configureAction' import { useRouter } from 'next/navigation' import { faChevronRight, faChevronLeft, faX, faCog } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' diff --git a/src/app/_components/Image/ImageUploader.tsx b/src/app/_components/Image/ImageUploader.tsx index 263a791a5..b8737125b 100644 --- a/src/app/_components/Image/ImageUploader.tsx +++ b/src/app/_components/Image/ImageUploader.tsx @@ -1,15 +1,15 @@ import Form from '@/components/Form/Form' -import { createImageAction } from '@/actions/images/create' import TextInput from '@/components/UI/TextInput' import FileInput from '@/components/UI/FileInput' import LicenseChooser from '@/components/LicenseChooser/LicenseChooser' -import { configureAction } from '@/actions/configureAction' +import { createImageAction } from '@/services/images/actions' +import { configureAction } from '@/services/configureAction' import type { PropTypes as FormPropTypes } from '@/components/Form/Form' type ResponseType = Awaited>; type T = Pick['data'] -type PropTypes = Omit, 'action' | 'submitText' | 'title'> & { +type PropTypes = Omit, 'action' | 'submitText' | 'title'> & { collectionId: number, } diff --git a/src/app/_components/LicenseChooser/LicenseChooser.tsx b/src/app/_components/LicenseChooser/LicenseChooser.tsx index 97930217a..17089067f 100644 --- a/src/app/_components/LicenseChooser/LicenseChooser.tsx +++ b/src/app/_components/LicenseChooser/LicenseChooser.tsx @@ -1,7 +1,7 @@ 'use client' import styles from './LicenseChooser.module.scss' import { SelectNumberPossibleNULL } from '@/UI/Select' -import { readAllLicensesAction } from '@/actions/licenses/read' +import { readAllLicensesAction } from '@/services/licenses/actions' import useActionCall from '@/hooks/useActionCall' import { useCallback, useEffect, useState } from 'react' diff --git a/src/app/_components/NavBar/NavBar.tsx b/src/app/_components/NavBar/NavBar.tsx index ab813fea5..59848acf6 100644 --- a/src/app/_components/NavBar/NavBar.tsx +++ b/src/app/_components/NavBar/NavBar.tsx @@ -6,7 +6,7 @@ import UserNavigation from './UserNavigation' import EditModeSwitch from '@/components/EditModeSwitch/EditModeSwitch' import SpecialCmsImage from '@/components/Cms/CmsImage/SpecialCmsImage' import Link from 'next/link' -import type { Profile } from '@/services/users/Types' +import type { Profile } from '@/services/users/types' export type PropTypes = { profile: Profile | null diff --git a/src/app/_components/NavBar/UserNavigation.tsx b/src/app/_components/NavBar/UserNavigation.tsx index 8537c34a0..fdba32d05 100644 --- a/src/app/_components/NavBar/UserNavigation.tsx +++ b/src/app/_components/NavBar/UserNavigation.tsx @@ -9,7 +9,7 @@ import { faCog, faDotCircle, faMoneyBill, faSignOut, faUser } from '@fortawesome import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import Link from 'next/link' import { useState } from 'react' -import type { Profile } from '@/services/users/Types' +import type { Profile } from '@/services/users/types' type PropTypes = { profile: Profile | null diff --git a/src/app/_components/NotificaionMethodSelector/NotificaionMethodSelector.tsx b/src/app/_components/NotificaionMethodSelector/NotificaionMethodSelector.tsx index e23693f73..818967e8d 100644 --- a/src/app/_components/NotificaionMethodSelector/NotificaionMethodSelector.tsx +++ b/src/app/_components/NotificaionMethodSelector/NotificaionMethodSelector.tsx @@ -3,7 +3,7 @@ import styles from './NotificaionMethodSelector.module.scss' import NotificationMethodCheckboxes from './NotificationMethodCheckboxes' import React from 'react' -import type { NotificationMethodTypes, NotificationMethodGeneral } from '@/services/notifications/Types' +import type { NotificationMethodTypes, NotificationMethodGeneral } from '@/services/notifications/types' export default function NotificationMethodSelector({ title, diff --git a/src/app/_components/NotificaionMethodSelector/NotificationMethodCheckboxes.tsx b/src/app/_components/NotificaionMethodSelector/NotificationMethodCheckboxes.tsx index 313335acf..21eaececf 100644 --- a/src/app/_components/NotificaionMethodSelector/NotificationMethodCheckboxes.tsx +++ b/src/app/_components/NotificaionMethodSelector/NotificationMethodCheckboxes.tsx @@ -1,9 +1,9 @@ 'use client' import Checkbox from '@/components/UI/Checkbox' -import { NotificationConfig } from '@/services/notifications/config' +import { notificationMethodsDisplayMap } from '@/services/notifications/constants' import { v4 as uuid } from 'uuid' import React, { useState } from 'react' -import type { NotificationMethodGeneral, NotificationMethodTypes } from '@/services/notifications/Types' +import type { NotificationMethodGeneral, NotificationMethodTypes } from '@/services/notifications/types' export default function NotificationMethodCheckboxes({ formPrefix, @@ -36,7 +36,7 @@ export default function NotificationMethodCheckboxes({ key={uuid()} name={formPrefix ? `${formPrefix}_${key}` : key} {...(onChange ? { checked: canEdit && value } : { defaultChecked: value })} - {...(label ? { label: NotificationConfig.methodsDisplayMap[key] } : {})} + {...(label ? { label: notificationMethodsDisplayMap[key] } : {})} disabled={!canEdit} onChange={handleChange.bind(key)} /> diff --git a/src/app/_components/OmegaId/identification/OmegaId.tsx b/src/app/_components/OmegaId/identification/OmegaId.tsx index c08ac0a7d..17bb5c9fb 100644 --- a/src/app/_components/OmegaId/identification/OmegaId.tsx +++ b/src/app/_components/OmegaId/identification/OmegaId.tsx @@ -1,7 +1,7 @@ 'use server' import OmegaIdElement from './OmegaIdElement' -import { generateOmegaIdAction } from '@/actions/omegaid/generate' +import { generateOmegaIdAction } from '@/services/omegaid/actions' export default async function OmegaId() { diff --git a/src/app/_components/OmegaId/identification/OmegaIdElement.tsx b/src/app/_components/OmegaId/identification/OmegaIdElement.tsx index 3e9b3d919..e5a72ef1e 100644 --- a/src/app/_components/OmegaId/identification/OmegaIdElement.tsx +++ b/src/app/_components/OmegaId/identification/OmegaIdElement.tsx @@ -1,7 +1,7 @@ 'use client' import styles from './OmegaIdElement.module.scss' -import { useUser } from '@/auth/useUser' -import { generateOmegaIdAction } from '@/actions/omegaid/generate' +import { useUser } from '@/auth/session/useUser' +import { generateOmegaIdAction } from '@/services/omegaid/actions' import { readJWTPayload } from '@/jwt/jwtReadUnsecure' import { compressOmegaId } from '@/services/omegaid/compress' import { useQRCode } from 'next-qrcode' diff --git a/src/app/_components/OmegaId/reader/ConfigVars.ts b/src/app/_components/OmegaId/reader/ConfigVars.ts index a2be938f8..6aeb07e68 100644 --- a/src/app/_components/OmegaId/reader/ConfigVars.ts +++ b/src/app/_components/OmegaId/reader/ConfigVars.ts @@ -1,6 +1,6 @@ import type { Html5QrcodeScannerConfig } from 'html5-qrcode/esm/html5-qrcode-scanner' -export const QRCodeReaderConfig: Html5QrcodeScannerConfig = { +export const qrCodeReaderConfig: Html5QrcodeScannerConfig = { fps: 4, disableFlip: true, qrbox: { diff --git a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx index 5ba805b44..5afb01464 100644 --- a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx +++ b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx @@ -1,5 +1,5 @@ 'use client' -import { QRCodeReaderConfig } from './ConfigVars' +import { qrCodeReaderConfig } from './ConfigVars' import styles from './OmegaIdReader.module.scss' import { parseJWT } from '@/jwt/parseJWTClient' import { decompressOmegaId } from '@/services/omegaid/compress' @@ -46,7 +46,7 @@ export default function OmegaIdReader({ const qrcodeRegionId = uuid() useEffect(() => { - const html5QrcodeScanner = new Html5QrcodeScanner(qrcodeRegionId, QRCodeReaderConfig, false) + const html5QrcodeScanner = new Html5QrcodeScanner(qrcodeRegionId, qrCodeReaderConfig, false) let lastReadTime = 0 let lastReadUserId = -1 diff --git a/src/app/_components/PagingWrappers/EndlessScroll.tsx b/src/app/_components/PagingWrappers/EndlessScroll.tsx index e02b33440..73dd81a3d 100644 --- a/src/app/_components/PagingWrappers/EndlessScroll.tsx +++ b/src/app/_components/PagingWrappers/EndlessScroll.tsx @@ -10,10 +10,10 @@ import React, { useRef } from 'react' import { useInView } from 'react-intersection-observer' -import type { PagingContextType } from '@/contexts/paging/PagingGenerator' +import type { PagingContext } from '@/contexts/paging/PagingGenerator' type PropTypes = { - pagingContext: PagingContextType, + pagingContext: PagingContext, renderer: (data: Data, i: number) => React.ReactNode, loadingInfoClassName?: string, } diff --git a/src/app/_components/Permission/DisplayAllPermissions.tsx b/src/app/_components/Permission/DisplayAllPermissions.tsx index 6bfcec162..c9eb819ba 100644 --- a/src/app/_components/Permission/DisplayAllPermissions.tsx +++ b/src/app/_components/Permission/DisplayAllPermissions.tsx @@ -1,5 +1,5 @@ import PermissionCategory from './PermissionCategory' -import { permissionCategories } from '@/services/permissions/config' +import { permissionCategories } from '@/services/permissions/constants' import type { PropTypes as PropTypesCategory } from './PermissionCategory' type PropTypes = Pick diff --git a/src/app/_components/Permission/Permission.tsx b/src/app/_components/Permission/Permission.tsx index c854c9fe0..f9751882d 100644 --- a/src/app/_components/Permission/Permission.tsx +++ b/src/app/_components/Permission/Permission.tsx @@ -1,5 +1,5 @@ import styles from './Permission.module.scss' -import { PermissionConfig } from '@/services/permissions/config' +import { permissionConfig } from '@/services/permissions/constants' import type { ReactNode } from 'react' import type { Permission as PermissionT } from '@prisma/client' @@ -18,7 +18,7 @@ type PropTypes = { * @returns */ export default function Permission({ permission, children, displayCategory = true, className }: PropTypes) { - const permissionInfo = PermissionConfig[permission] + const permissionInfo = permissionConfig[permission] return (
diff --git a/src/app/_components/Permission/PermissionCategory.tsx b/src/app/_components/Permission/PermissionCategory.tsx index 37b7c06ef..dbb9c5737 100644 --- a/src/app/_components/Permission/PermissionCategory.tsx +++ b/src/app/_components/Permission/PermissionCategory.tsx @@ -1,8 +1,8 @@ import Permission from './Permission' import styles from './PermissionCategory.module.scss' -import { PermissionConfig } from '@/services/permissions/config' +import { permissionConfig } from '@/services/permissions/constants' import { Permission as PermissionEnum } from '@prisma/client' -import type { PermissionCategory } from '@/services/permissions/Types' +import type { PermissionCategory } from '@/services/permissions/types' import type { ReactNode } from 'react' export type PropTypes = { @@ -17,7 +17,7 @@ export type PropTypes = { */ export default function PermissionCategory({ category, renderBesidePermission }: PropTypes) { const permissionsInCategory = Object.values(PermissionEnum).filter(permission => - PermissionConfig[permission].category === category + permissionConfig[permission].category === category ) return ( diff --git a/src/app/_components/School/School.tsx b/src/app/_components/School/School.tsx index 4ef208528..b55a62957 100644 --- a/src/app/_components/School/School.tsx +++ b/src/app/_components/School/School.tsx @@ -3,7 +3,7 @@ import CmsImageClient from '@/cms/CmsImage/CmsImageClient' import CmsLink from '@/cms/CmsLink/CmsLink' import CmsImage from '@/cms/CmsImage/CmsImage' import CmsParagraph from '@/cms/CmsParagraph/CmsParagraph' -import type { ExpandedSchool } from '@/education/schools/Types' +import type { ExpandedSchool } from '@/services/education/schools/types' type PropTypes = { school: ExpandedSchool diff --git a/src/app/_components/School/SchoolListRenderer.tsx b/src/app/_components/School/SchoolListRenderer.tsx index 7d9321892..204cdfd90 100644 --- a/src/app/_components/School/SchoolListRenderer.tsx +++ b/src/app/_components/School/SchoolListRenderer.tsx @@ -1,5 +1,5 @@ import School from './School' -import type { ExpandedSchool } from '@/services/education/schools/Types' +import type { ExpandedSchool } from '@/services/education/schools/types' /** * Used to render schools server side and client side in consistent manner diff --git a/src/app/_components/UI/CheckboxFieldPresent.tsx b/src/app/_components/UI/CheckboxFieldPresent.tsx index b4bc4d5ff..c792132dc 100644 --- a/src/app/_components/UI/CheckboxFieldPresent.tsx +++ b/src/app/_components/UI/CheckboxFieldPresent.tsx @@ -1,6 +1,6 @@ 'use client' import styles from './CheckboxFieldPresent.module.scss' -import { FIELD_IS_PRESENT_VALUE } from '@/lib/fields/config' +import { FIELD_IS_PRESENT_VALUE } from '@/lib/fields/constants' type PropTypes = { name: string diff --git a/src/app/_components/User/CreateUserForm.tsx b/src/app/_components/User/CreateUserForm.tsx index ca01be9f5..05b9bf370 100644 --- a/src/app/_components/User/CreateUserForm.tsx +++ b/src/app/_components/User/CreateUserForm.tsx @@ -1,6 +1,6 @@ 'use client' import styles from './CreateUserForm.module.scss' -import { createUserAction } from '@/actions/users/create' +import { createUserAction } from '@/services/users/actions' import TextInput from '@/components/UI/TextInput' import Form from '@/components/Form/Form' import React from 'react' diff --git a/src/app/_components/User/UserCard.tsx b/src/app/_components/User/UserCard.tsx index 7d060686d..7851db438 100644 --- a/src/app/_components/User/UserCard.tsx +++ b/src/app/_components/User/UserCard.tsx @@ -3,7 +3,7 @@ import styles from './UserCard.module.scss' import ProfilePicture from './ProfilePicture' import Link from 'next/link' import type { Image } from '@prisma/client' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' // TODO: Make nice and add picture export default function UserCard({ diff --git a/src/app/_components/User/UserList/UserList.tsx b/src/app/_components/User/UserList/UserList.tsx index 62f6dba8d..76774e663 100644 --- a/src/app/_components/User/UserList/UserList.tsx +++ b/src/app/_components/User/UserList/UserList.tsx @@ -5,16 +5,16 @@ import { UserPagingContext } from '@/contexts/paging/UserPaging' import EndlessScroll from '@/components/PagingWrappers/EndlessScroll' import UserRow from '@/components/User/UserList/UserRow' import useActionCall from '@/hooks/useActionCall' -import { readGroupsForPageFilteringAction } from '@/actions/users/read' import { UsersSelectionContext } from '@/contexts/UsersSelection' import { UserSelectionContext } from '@/contexts/UserSelection' +import { readGroupsForPageFilteringAction } from '@/services/users/actions' import { useContext, useEffect, useState } from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCheck } from '@fortawesome/free-solid-svg-icons' -import type { UserPagingReturn } from '@/services/users/Types' +import type { UserPagingReturn } from '@/services/users/types' import type { ChangeEvent, ReactNode } from 'react' import type { GroupType } from '@prisma/client' -import type { ExpandedGroup } from '@/services/groups/Types' +import type { ExpandedGroup } from '@/services/groups/types' type GroupSelectionType = Exclude diff --git a/src/app/_components/User/UserList/UserRow.tsx b/src/app/_components/User/UserList/UserRow.tsx index 6fc25ba01..bf0d71332 100644 --- a/src/app/_components/User/UserList/UserRow.tsx +++ b/src/app/_components/User/UserList/UserRow.tsx @@ -1,7 +1,7 @@ import styles from './UserRow.module.scss' import UserDisplayName from '@/components/User/UserDisplayName' import { useRouter } from 'next/navigation' -import type { UserPagingReturn } from '@/services/users/Types' +import type { UserPagingReturn } from '@/services/users/types' type PropTypes = { user: UserPagingReturn diff --git a/src/app/_components/VisiblityAdmin/VisibilityAdmin.tsx b/src/app/_components/VisiblityAdmin/VisibilityAdmin.tsx index b5645cfa5..bd940e11f 100644 --- a/src/app/_components/VisiblityAdmin/VisibilityAdmin.tsx +++ b/src/app/_components/VisiblityAdmin/VisibilityAdmin.tsx @@ -2,7 +2,7 @@ import styles from './VisibilityAdmin.module.scss' import VisibilityLevelAdmin from './VisibilityLevelAdmin' import useActionCall from '@/hooks/useActionCall' -import { readVisibilityForAdminAction } from '@/actions/visibility/read' +import { readVisibilityForAdminAction } from '@/services/visibility/actions' import { useCallback } from 'react' type PropTypes = { diff --git a/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.tsx b/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.tsx index 4f16a6ed4..6c7cd03ea 100644 --- a/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.tsx +++ b/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.tsx @@ -1,8 +1,7 @@ import styles from './VisibilityLevelAdmin.module.scss' import Form from '@/components/Form/Form' -import { updateVisibilityAction } from '@/actions/visibility/update' -import type { VisibilityLevelType } from '@/services/visibility/Types' -import type { VisibilityRequiermentForAdmin } from '@/actions/visibility/Types' +import { updateVisibilityAction } from '@/services/visibility/actions' +import type { VisibilityLevelType, VisibilityRequiermentForAdmin } from '@/services/visibility/types' type PropTypes = { data: VisibilityRequiermentForAdmin[] diff --git a/src/app/admin/(permissions)/default-permissions/page.tsx b/src/app/admin/(permissions)/default-permissions/page.tsx index e657ebb67..da8d052b6 100644 --- a/src/app/admin/(permissions)/default-permissions/page.tsx +++ b/src/app/admin/(permissions)/default-permissions/page.tsx @@ -1,4 +1,4 @@ -import { updateDefaultPermissionsAction, readDefaultPermissionsAction } from '@/actions/permissions/index' +import { updateDefaultPermissionsAction, readDefaultPermissionsAction } from '@/services/permissions/actions' import Form from '@/components/Form/Form' import DisplayAllPermissions from '@/components/Permission/DisplayAllPermissions' import React from 'react' diff --git a/src/app/admin/(permissions)/group-permissions/PermissionCheckbox.tsx b/src/app/admin/(permissions)/group-permissions/PermissionCheckbox.tsx index b20d6748a..52b6dd47d 100644 --- a/src/app/admin/(permissions)/group-permissions/PermissionCheckbox.tsx +++ b/src/app/admin/(permissions)/group-permissions/PermissionCheckbox.tsx @@ -2,7 +2,7 @@ import Checkbox from '@/components/UI/Checkbox' import { unwrapActionReturn } from '@/app/redirectToErrorPage' -import { updateGroupPermissionAction } from '@/actions/permissions' +import { updateGroupPermissionAction } from '@/services/permissions/actions' import { useState } from 'react' import type { Permission } from '@prisma/client' diff --git a/src/app/admin/(permissions)/group-permissions/page.tsx b/src/app/admin/(permissions)/group-permissions/page.tsx index 6b74b3bb8..cabf08862 100644 --- a/src/app/admin/(permissions)/group-permissions/page.tsx +++ b/src/app/admin/(permissions)/group-permissions/page.tsx @@ -1,14 +1,14 @@ import styles from './page.module.scss' import PermissionCheckbox from './PermissionCheckbox' -import { readPermissionMatrixAction } from '@/actions/permissions' +import { readPermissionMatrixAction } from '@/services/permissions/actions' import { unwrapActionReturn } from '@/app/redirectToErrorPage' -import { PermissionConfig } from '@/services/permissions/config' +import { permissionConfig } from '@/services/permissions/constants' import type { Permission } from '@prisma/client' export default async function PermissionGroups() { const permissionMatrix = unwrapActionReturn(await readPermissionMatrixAction()) - const permissionList = Object.keys(PermissionConfig) + const permissionList = Object.keys(permissionConfig) return
@@ -19,9 +19,9 @@ export default async function PermissionGroups() { )} diff --git a/src/app/admin/admission/[admission]/page.tsx b/src/app/admin/admission/[admission]/page.tsx index 327d7a921..7679f5447 100644 --- a/src/app/admin/admission/[admission]/page.tsx +++ b/src/app/admin/admission/[admission]/page.tsx @@ -1,7 +1,7 @@ import RegisterAdmissiontrial from './registration' +import { admissionDisplayNames, allAdmissions } from '@/services/admission/constants' import PageWrapper from '@/components/PageWrapper/PageWrapper' -import { readOmegaJWTPublicKey } from '@/actions/omegaid/read' -import { AdmissionConfig } from '@/services/admission/config' +import { readOmegaJWTPublicKey } from '@/services/omegaid/actions' import { type Admission as AdmissionType } from '@prisma/client' import { notFound } from 'next/navigation' @@ -12,15 +12,16 @@ type PropTypes = { } export default async function AdmissionTrials({ params }: PropTypes) { - if (!AdmissionConfig.array.includes((await params).admission)) { + if (!allAdmissions.includes((await params).admission)) { notFound() } + const admission = (await params).admission const publicKey = await readOmegaJWTPublicKey() return
    - {AdmissionConfig.array.map(trial => + {allAdmissions.map(trial =>
  • - {AdmissionConfig.displayNames[trial]} + {admissionDisplayNames[trial]}
  • )}
diff --git a/src/app/admin/api-keys/CreateApiKeyForm.tsx b/src/app/admin/api-keys/CreateApiKeyForm.tsx index 23cdbb0bb..c4ac02993 100644 --- a/src/app/admin/api-keys/CreateApiKeyForm.tsx +++ b/src/app/admin/api-keys/CreateApiKeyForm.tsx @@ -1,7 +1,7 @@ 'use client' import styles from './CreateApiKeyForm.module.scss' import Form from '@/components/Form/Form' -import { createApiKeyAction } from '@/actions/api-keys/create' +import { createApiKeyAction } from '@/services/apiKeys/actions' import TextInput from '@/components/UI/TextInput' import { PopUpContext } from '@/contexts/PopUp' import { useContext, useState } from 'react' diff --git a/src/app/admin/api-keys/[name]/UpdateApiKeyForm.tsx b/src/app/admin/api-keys/[name]/UpdateApiKeyForm.tsx index df0ddfbd5..f9f97cdfb 100644 --- a/src/app/admin/api-keys/[name]/UpdateApiKeyForm.tsx +++ b/src/app/admin/api-keys/[name]/UpdateApiKeyForm.tsx @@ -1,6 +1,6 @@ 'use client' -import { updateApiKeyAction } from '@/actions/api-keys/update' -import { configureAction } from '@/actions/configureAction' +import { updateApiKeyAction } from '@/services/apiKeys/actions' +import { configureAction } from '@/services/configureAction' import Form from '@/app/_components/Form/Form' import type { ReactNode } from 'react' diff --git a/src/app/admin/api-keys/[name]/page.tsx b/src/app/admin/api-keys/[name]/page.tsx index 18e3036b4..215d9c06f 100644 --- a/src/app/admin/api-keys/[name]/page.tsx +++ b/src/app/admin/api-keys/[name]/page.tsx @@ -1,16 +1,15 @@ import styles from './page.module.scss' import UpdateApiKeyForm from './UpdateApiKeyForm' -import { readApiKeyAction } from '@/actions/api-keys/read' +import { readApiKeyAction, destroyApiKeyAction } from '@/services/apiKeys/actions' import PageWrapper from '@/components/PageWrapper/PageWrapper' import Form from '@/components/Form/Form' import DateInput from '@/components/UI/DateInput' import TextInput from '@/components/UI/TextInput' import DisplayAllPermissions from '@/components/Permission/DisplayAllPermissions' import Slider from '@/components/UI/Slider' -import { destroyApiKeyAction } from '@/actions/api-keys/destroy' import Date from '@/app/_components/Date/Date' import Checkbox from '@/app/_components/UI/Checkbox' -import { configureAction } from '@/actions/configureAction' +import { configureAction } from '@/services/configureAction' type PropTypes = { params: Promise<{ diff --git a/src/app/admin/api-keys/page.tsx b/src/app/admin/api-keys/page.tsx index 27f7d30bb..70d11dde5 100644 --- a/src/app/admin/api-keys/page.tsx +++ b/src/app/admin/api-keys/page.tsx @@ -2,7 +2,7 @@ import styles from './page.module.scss' import CreateApiKeyForm from './CreateApiKeyForm' import PageWrapper from '@/components/PageWrapper/PageWrapper' import { AddHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' -import { readApiKeysAction } from '@/actions/api-keys/read' +import { readApiKeysAction } from '@/services/apiKeys/actions' import Date from '@/components/Date/Date' import { v4 as uuid } from 'uuid' import Link from 'next/link' diff --git a/src/app/admin/cabin-booking/[booking]/page.tsx b/src/app/admin/cabin-booking/[booking]/page.tsx index 41bf1e874..845a67e2b 100644 --- a/src/app/admin/cabin-booking/[booking]/page.tsx +++ b/src/app/admin/cabin-booking/[booking]/page.tsx @@ -1,4 +1,4 @@ -import { readCabinBookingAction } from '@/actions/cabin' +import { readCabinBookingAction } from '@/services/cabin/actions' import PageWrapper from '@/app/_components/PageWrapper/PageWrapper' import SimpleTable from '@/app/_components/Table/SimpleTable' import { unwrapActionReturn } from '@/app/redirectToErrorPage' diff --git a/src/app/admin/cabin-booking/page.tsx b/src/app/admin/cabin-booking/page.tsx index ccc991923..1001a8951 100644 --- a/src/app/admin/cabin-booking/page.tsx +++ b/src/app/admin/cabin-booking/page.tsx @@ -1,4 +1,4 @@ -import { readCabinBookingsAction } from '@/actions/cabin' +import { readCabinBookingsAction } from '@/services/cabin/actions' import PageWrapper from '@/app/_components/PageWrapper/PageWrapper' import SimpleTable from '@/app/_components/Table/SimpleTable' import { unwrapActionReturn } from '@/app/redirectToErrorPage' diff --git a/src/app/admin/cabin-periods/PageStateWrapper.tsx b/src/app/admin/cabin-periods/PageStateWrapper.tsx index 46763cbf5..1fc7b0ba6 100644 --- a/src/app/admin/cabin-periods/PageStateWrapper.tsx +++ b/src/app/admin/cabin-periods/PageStateWrapper.tsx @@ -6,8 +6,8 @@ import PopUp from '@/app/_components/PopUp/PopUp' import { displayDate } from '@/lib/dates/displayDate' import SimpleTable from '@/app/_components/Table/SimpleTable' import Form from '@/app/_components/Form/Form' -import { destoryPricePeriodAction, destroyReleasePeriodAction } from '@/actions/cabin' -import { configureAction } from '@/actions/configureAction' +import { destoryPricePeriodAction, destroyReleasePeriodAction } from '@/services/cabin/actions' +import { configureAction } from '@/services/configureAction' import { v4 as uuid } from 'uuid' import type { PricePeriod, ReleasePeriod } from '@prisma/client' diff --git a/src/app/admin/cabin-periods/PricePeriodForm.tsx b/src/app/admin/cabin-periods/PricePeriodForm.tsx index 0979f39c9..7b2fc1b3d 100644 --- a/src/app/admin/cabin-periods/PricePeriodForm.tsx +++ b/src/app/admin/cabin-periods/PricePeriodForm.tsx @@ -1,6 +1,6 @@ 'use client' -import { createPricePeriodAction } from '@/actions/cabin' +import { createPricePeriodAction } from '@/services/cabin/actions' import Form from '@/app/_components/Form/Form' import Checkbox from '@/app/_components/UI/Checkbox' import DateInput from '@/app/_components/UI/DateInput' diff --git a/src/app/admin/cabin-periods/ReleasePeriodForm.tsx b/src/app/admin/cabin-periods/ReleasePeriodForm.tsx index 2f1dc29dc..76ed1f232 100644 --- a/src/app/admin/cabin-periods/ReleasePeriodForm.tsx +++ b/src/app/admin/cabin-periods/ReleasePeriodForm.tsx @@ -1,6 +1,6 @@ 'use client' -import { createReleasePeriodAction } from '@/actions/cabin' +import { createReleasePeriodAction } from '@/services/cabin/actions' import Form from '@/app/_components/Form/Form' import DateInput from '@/app/_components/UI/DateInput' diff --git a/src/app/admin/cabin-periods/page.tsx b/src/app/admin/cabin-periods/page.tsx index 0c3f1937d..ff7ed5d89 100644 --- a/src/app/admin/cabin-periods/page.tsx +++ b/src/app/admin/cabin-periods/page.tsx @@ -1,7 +1,7 @@ 'use server' import PageStateWrapper from './PageStateWrapper' import PageWrapper from '@/app/_components/PageWrapper/PageWrapper' -import { readPricePeriodsAction, readReleasePeriodsAction } from '@/actions/cabin' +import { readPricePeriodsAction, readReleasePeriodsAction } from '@/services/cabin/actions' import { unwrapActionReturn } from '@/app/redirectToErrorPage' diff --git a/src/app/admin/cabin-product/UpdateCabinProductForm.tsx b/src/app/admin/cabin-product/UpdateCabinProductForm.tsx index 9bc1a0577..303945cd1 100644 --- a/src/app/admin/cabin-product/UpdateCabinProductForm.tsx +++ b/src/app/admin/cabin-product/UpdateCabinProductForm.tsx @@ -1,5 +1,5 @@ 'use client' -import { createCabinProductAction } from '@/actions/cabin' +import { createCabinProductAction } from '@/services/cabin/actions' import Form from '@/app/_components/Form/Form' import NumberInput from '@/app/_components/UI/NumberInput' import TextInput from '@/app/_components/UI/TextInput' diff --git a/src/app/admin/cabin-product/[product]/UpdateCabinProductPriceForm.tsx b/src/app/admin/cabin-product/[product]/UpdateCabinProductPriceForm.tsx index c33270214..f46849e6c 100644 --- a/src/app/admin/cabin-product/[product]/UpdateCabinProductPriceForm.tsx +++ b/src/app/admin/cabin-product/[product]/UpdateCabinProductPriceForm.tsx @@ -1,6 +1,6 @@ 'use client' -import { createCabinProductPriceAction } from '@/actions/cabin' -import { configureAction } from '@/actions/configureAction' +import { createCabinProductPriceAction } from '@/services/cabin/actions' +import { configureAction } from '@/services/configureAction' import Form from '@/app/_components/Form/Form' import NumberInput from '@/app/_components/UI/NumberInput' import { SelectNumber } from '@/app/_components/UI/Select' diff --git a/src/app/admin/cabin-product/[product]/page.tsx b/src/app/admin/cabin-product/[product]/page.tsx index 70389c778..79f7a1965 100644 --- a/src/app/admin/cabin-product/[product]/page.tsx +++ b/src/app/admin/cabin-product/[product]/page.tsx @@ -1,7 +1,7 @@ import styles from './page.module.scss' import { UpdateCabinProductPriceForm } from './UpdateCabinProductPriceForm' import { AddHeaderItemPopUp } from '@/app/_components/HeaderItems/HeaderItemPopUp' -import { readCabinProductAction, readUnreleasedPricePeriodsAction } from '@/actions/cabin' +import { readCabinProductAction, readUnreleasedPricePeriodsAction } from '@/services/cabin/actions' import PageWrapper from '@/app/_components/PageWrapper/PageWrapper' import { unwrapActionReturn } from '@/app/redirectToErrorPage' import { displayDate } from '@/lib/dates/displayDate' diff --git a/src/app/admin/cabin-product/page.tsx b/src/app/admin/cabin-product/page.tsx index 4141c8562..23b9d9aa2 100644 --- a/src/app/admin/cabin-product/page.tsx +++ b/src/app/admin/cabin-product/page.tsx @@ -1,6 +1,6 @@ import { UpdateCabinProductForm } from './UpdateCabinProductForm' import { AddHeaderItemPopUp } from '@/app/_components/HeaderItems/HeaderItemPopUp' -import { readCabinProductsAction } from '@/actions/cabin' +import { readCabinProductsAction } from '@/services/cabin/actions' import PageWrapper from '@/app/_components/PageWrapper/PageWrapper' import { unwrapActionReturn } from '@/app/redirectToErrorPage' import SimpleTable from '@/app/_components/Table/SimpleTable' diff --git a/src/app/admin/committees/CreateCommitteeForm.tsx b/src/app/admin/committees/CreateCommitteeForm.tsx index 1163433ea..339653ecc 100644 --- a/src/app/admin/committees/CreateCommitteeForm.tsx +++ b/src/app/admin/committees/CreateCommitteeForm.tsx @@ -2,7 +2,7 @@ import styles from './CreateCommitteeForm.module.scss' import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' -import { createCommitteeAction } from '@/actions/groups/committees/create' +import { createCommitteeAction } from '@/services/groups/committees/actions' import { ImageSelectionContext } from '@/contexts/ImageSelection' import { useContext } from 'react' diff --git a/src/app/admin/committees/page.tsx b/src/app/admin/committees/page.tsx index 44f2300a6..748d9e3c5 100644 --- a/src/app/admin/committees/page.tsx +++ b/src/app/admin/committees/page.tsx @@ -2,10 +2,10 @@ import styles from './page.module.scss' import CreateCommitteeForm from './CreateCommitteeForm' import ImageSelectionProvider from '@/contexts/ImageSelection' import ImageList from '@/components/Image/ImageList/ImageList' -import ImagePagingProvider from '@/contexts/paging/ImagePaging' -import { readSpecialImageCollectionAction } from '@/actions/images/collections/read' +import { ImagePagingProvider } from '@/contexts/paging/ImagePaging' +import { readSpecialImageCollectionAction } from '@/services/images/collections/actions' import PopUpProvider from '@/contexts/PopUp' -import { readSpecialImageAction } from '@/actions/images/read' +import { readSpecialImageAction } from '@/services/images/actions' import type { PageSizeImage } from '@/contexts/paging/ImagePaging' export default async function adminCommittee() { diff --git a/src/app/admin/dots/CreateDotForm.tsx b/src/app/admin/dots/CreateDotForm.tsx index 40eb96aea..b12757d06 100644 --- a/src/app/admin/dots/CreateDotForm.tsx +++ b/src/app/admin/dots/CreateDotForm.tsx @@ -1,15 +1,15 @@ 'use client' import styles from './CreateDotForm.module.scss' -import { createDotAction } from '@/actions/dots/create' +import { createDotAction } from '@/services/dots/actions' import Form from '@/components/Form/Form' import PopUp from '@/components/PopUp/PopUp' import NumberInput from '@/components/UI/NumberInput' import TextInput from '@/components/UI/TextInput' import UserList from '@/components/User/UserList/UserList' -import { useUser } from '@/auth/useUser' +import { useUser } from '@/auth/session/useUser' import { PopUpContext } from '@/contexts/PopUp' import { UserSelectionContext } from '@/contexts/UserSelection' -import { configureAction } from '@/actions/configureAction' +import { configureAction } from '@/services/configureAction' import { useContext } from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faPlus } from '@fortawesome/free-solid-svg-icons' diff --git a/src/app/admin/dots/DotList.tsx b/src/app/admin/dots/DotList.tsx index 49f7a9190..99b38642a 100644 --- a/src/app/admin/dots/DotList.tsx +++ b/src/app/admin/dots/DotList.tsx @@ -3,7 +3,7 @@ import styles from './DotList.module.scss' import EndlessScroll from '@/components/PagingWrappers/EndlessScroll' import { DotPagingContext } from '@/contexts/paging/DotPaging' import { UserSelectionContext } from '@/contexts/UserSelection' -import { QueryParams } from '@/lib/query-params/queryParams' +import { QueryParams } from '@/lib/queryParams/queryParams' import PopUp from '@/components/PopUp/PopUp' import UserList from '@/components/User/UserList/UserList' import { PopUpContext } from '@/contexts/PopUp' diff --git a/src/app/admin/dots/page.tsx b/src/app/admin/dots/page.tsx index 27a2bee3f..150d16d53 100644 --- a/src/app/admin/dots/page.tsx +++ b/src/app/admin/dots/page.tsx @@ -2,11 +2,11 @@ import styles from './page.module.scss' import CreateDotForm from './CreateDotForm' import DotList from './DotList' import UserSelectionProvider from '@/contexts/UserSelection' -import UserPagingProvider from '@/contexts/paging/UserPaging' +import { UserPagingProvider } from '@/contexts/paging/UserPaging' import PopUpProvider from '@/contexts/PopUp' -import DotPagingProvider from '@/contexts/paging/DotPaging' -import { QueryParams } from '@/lib/query-params/queryParams' -import type { SearchParamsServerSide } from '@/lib/query-params/Types' +import { DotPagingProvider } from '@/contexts/paging/DotPaging' +import { QueryParams } from '@/lib/queryParams/queryParams' +import type { SearchParamsServerSide } from '@/lib/queryParams/types' type PropTypes = SearchParamsServerSide diff --git a/src/app/admin/groups/GroupSelector.tsx b/src/app/admin/groups/GroupSelector.tsx index 445e5078b..f5b180d56 100644 --- a/src/app/admin/groups/GroupSelector.tsx +++ b/src/app/admin/groups/GroupSelector.tsx @@ -2,7 +2,7 @@ import styles from './GroupSelector.module.scss' import Link from 'next/link' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faArrowRight } from '@fortawesome/free-solid-svg-icons' -import type { ExpandedGroup } from '@/services/groups/Types' +import type { ExpandedGroup } from '@/services/groups/types' type PropTypes = { group: ExpandedGroup diff --git a/src/app/admin/groups/[id]/AddUsersToGroup.tsx b/src/app/admin/groups/[id]/AddUsersToGroup.tsx index 2a42e6ace..3743275db 100644 --- a/src/app/admin/groups/[id]/AddUsersToGroup.tsx +++ b/src/app/admin/groups/[id]/AddUsersToGroup.tsx @@ -2,7 +2,7 @@ import styles from './AddUsersToGroup.module.scss' import UserList from '@/components/User/UserList/UserList' import Form from '@/components/Form/Form' -import { createMembershipsForGroupAction } from '@/actions/groups/memberships/create' +import { createMembershipsForGroupAction } from '@/services/groups/memberships/actions' import { UsersSelectionContext } from '@/contexts/UsersSelection' import { useContext } from 'react' import { useRouter } from 'next/navigation' diff --git a/src/app/admin/groups/[id]/GroupMembers.tsx b/src/app/admin/groups/[id]/GroupMembers.tsx index e105c8fe5..e1f401121 100644 --- a/src/app/admin/groups/[id]/GroupMembers.tsx +++ b/src/app/admin/groups/[id]/GroupMembers.tsx @@ -5,7 +5,7 @@ import UserList from '@/components/User/UserList/UserList' import PopUp from '@/components/PopUp/PopUp' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCog } from '@fortawesome/free-solid-svg-icons' -import type { ExpandedGroup } from '@/services/groups/Types' +import type { ExpandedGroup } from '@/services/groups/types' type PropTypes = { group: ExpandedGroup diff --git a/src/app/admin/groups/[id]/MembershipAdminForUser.tsx b/src/app/admin/groups/[id]/MembershipAdminForUser.tsx index f74165084..6757cc642 100644 --- a/src/app/admin/groups/[id]/MembershipAdminForUser.tsx +++ b/src/app/admin/groups/[id]/MembershipAdminForUser.tsx @@ -1,10 +1,10 @@ 'use client' import styles from './MembershipAdminForUser.module.scss' import Form from '@/components/Form/Form' -import { updateMembershipActiveAction, updateMembershipAdminAcion } from '@/actions/groups/memberships/update' +import { updateMembershipActiveAction, updateMembershipAdminAcion } from '@/services/groups/memberships/actions' import { useRouter } from 'next/navigation' -import type { UserPagingReturn } from '@/services/users/Types' -import type { ExpandedGroup } from '@/services/groups/Types' +import type { UserPagingReturn } from '@/services/users/types' +import type { ExpandedGroup } from '@/services/groups/types' type PropTypes = { user: UserPagingReturn, diff --git a/src/app/admin/groups/[id]/page.tsx b/src/app/admin/groups/[id]/page.tsx index 8fa5f6a32..8d07d392a 100644 --- a/src/app/admin/groups/[id]/page.tsx +++ b/src/app/admin/groups/[id]/page.tsx @@ -1,11 +1,11 @@ import styles from './page.module.scss' import AddUsersToGroup from './AddUsersToGroup' import GroupMembers from './GroupMembers' -import UserPagingProvider from '@/contexts/paging/UserPaging' +import { UserPagingProvider } from '@/contexts/paging/UserPaging' import { CanEasilyManageMembership } from '@/services/groups/memberships/ConfigVars' import PopUp from '@/components/PopUp/PopUp' import UsersSelectionProvider from '@/contexts/UsersSelection' -import { readGroupExpandedAction } from '@/actions/groups/read' +import { readGroupExpandedAction } from '@/services/groups/actions' import { unwrapActionReturn } from '@/app/redirectToErrorPage' import Link from 'next/link' diff --git a/src/app/admin/groups/page.tsx b/src/app/admin/groups/page.tsx index fe678fc54..87cc27990 100644 --- a/src/app/admin/groups/page.tsx +++ b/src/app/admin/groups/page.tsx @@ -1,7 +1,7 @@ import styles from './page.module.scss' import GroupSelector from './GroupSelector' -import { readGroupsStructuredAction } from '@/actions/groups/read' -import { GroupTypeOrdering } from '@/services/groups/config' +import { GroupTypeOrdering } from '@/services/groups/constants' +import { readGroupsStructuredAction } from '@/services/groups/actions' import { notFound } from 'next/navigation' /** diff --git a/src/app/admin/licenses/page.tsx b/src/app/admin/licenses/page.tsx index 2748401ce..b400dac24 100644 --- a/src/app/admin/licenses/page.tsx +++ b/src/app/admin/licenses/page.tsx @@ -2,12 +2,14 @@ import styles from './page.module.scss' import { unwrapActionReturn } from '@/app/redirectToErrorPage' import { SettingsHeaderItemPopUp } from '@/app/_components/HeaderItems/HeaderItemPopUp' import Form from '@/app/_components/Form/Form' -import { destroyLicenseAction } from '@/actions/licenses/destroy' -import { createLicenseAction } from '@/actions/licenses/create' +import { + destroyLicenseAction, + createLicenseAction, + updateLicenseAction, + readAllLicensesAction +} from '@/services/licenses/actions' import TextInput from '@/UI/TextInput' -import { updateLicenseAction } from '@/actions/licenses/update' -import { readAllLicensesAction } from '@/actions/licenses/read' -import { configureAction } from '@/actions/configureAction' +import { configureAction } from '@/services/configureAction' import Link from 'next/link' export default async function Licenses() { diff --git a/src/app/admin/lockers/location/CreateLockerLocationForm.tsx b/src/app/admin/lockers/location/CreateLockerLocationForm.tsx index 7b0897f07..7a15626a9 100644 --- a/src/app/admin/lockers/location/CreateLockerLocationForm.tsx +++ b/src/app/admin/lockers/location/CreateLockerLocationForm.tsx @@ -1,6 +1,6 @@ 'use client' import Form from '@/components/Form/Form' -import { createLockerLocationAction } from '@/actions/lockers/locations' +import { createLockerLocationAction } from '@/services/lockers/actions' import TextInput from '@/components/UI/TextInput' import NumberInput from '@/components/UI/NumberInput' diff --git a/src/app/admin/lockers/locker/CreateLockerForm.tsx b/src/app/admin/lockers/locker/CreateLockerForm.tsx index 91538d1ea..708766e57 100644 --- a/src/app/admin/lockers/locker/CreateLockerForm.tsx +++ b/src/app/admin/lockers/locker/CreateLockerForm.tsx @@ -1,5 +1,5 @@ 'use client' -import { createLockerAction } from '@/actions/lockers/lockers' +import { createLockerAction } from '@/services/lockers/actions' import Form from '@/components/Form/Form' import { SelectString } from '@/components/UI/Select' import NumberInput from '@/components/UI/NumberInput' diff --git a/src/app/admin/lockers/locker/page.tsx b/src/app/admin/lockers/locker/page.tsx index 70b725fb4..b9b5094a6 100644 --- a/src/app/admin/lockers/locker/page.tsx +++ b/src/app/admin/lockers/locker/page.tsx @@ -1,6 +1,6 @@ import styles from './page.module.scss' import CreateLockerForm from './CreateLockerForm' -import { readAllLockerLocationsAction as readAllLockerLocationsAction } from '@/actions/lockers/locations' +import { readAllLockerLocationsAction as readAllLockerLocationsAction } from '@/services/lockers/actions' export default async function Locker() { diff --git a/src/app/admin/mail/[filter]/[id]/(editComponents)/group.tsx b/src/app/admin/mail/[filter]/[id]/(editComponents)/group.tsx index ae0e84bd9..cb1b3f3c7 100644 --- a/src/app/admin/mail/[filter]/[id]/(editComponents)/group.tsx +++ b/src/app/admin/mail/[filter]/[id]/(editComponents)/group.tsx @@ -1,9 +1,9 @@ 'use client' import Form from '@/components/Form/Form' import { SelectNumber } from '@/components/UI/Select' -import { createMailingListGroupRelationAction } from '@/actions/mail/create' -import { useUser } from '@/auth/useUser' -import type { MailFlowObject } from '@/services/mail/Types' +import { createMailingListGroupRelationAction } from '@/services/mail/actions' +import { useUser } from '@/auth/session/useUser' +import type { MailFlowObject } from '@/services/mail/types' import type { MailingList } from '@prisma/client' diff --git a/src/app/admin/mail/[filter]/[id]/(editComponents)/mailAddressExternal.tsx b/src/app/admin/mail/[filter]/[id]/(editComponents)/mailAddressExternal.tsx index 3000347dc..fad80f65d 100644 --- a/src/app/admin/mail/[filter]/[id]/(editComponents)/mailAddressExternal.tsx +++ b/src/app/admin/mail/[filter]/[id]/(editComponents)/mailAddressExternal.tsx @@ -3,13 +3,15 @@ import TextInput from '@/components/UI/TextInput' import Form from '@/components/Form/Form' import { SelectNumber } from '@/components/UI/Select' -import { createMailingListExternalRelationAction } from '@/actions/mail/create' -import { useUser } from '@/auth/useUser' -import { updateMailAddressExternalAction } from '@/actions/mail/mailAddressExternal/update' -import { destroyMailAddressExternalAction } from '@/actions/mail/mailAddressExternal/destroy' +import { createMailingListExternalRelationAction } from '@/services/mail/actions' +import { useUser } from '@/auth/session/useUser' +import { + updateMailAddressExternalAction, + destroyMailAddressExternalAction +} from '@/services/mail/mailAddressExternal/actions' import { useRouter } from 'next/navigation' import type { MailingList } from '@prisma/client' -import type { MailFlowObject } from '@/services/mail/Types' +import type { MailFlowObject } from '@/services/mail/types' export default function EditMailAddressExternal({ diff --git a/src/app/admin/mail/[filter]/[id]/(editComponents)/mailAlias.tsx b/src/app/admin/mail/[filter]/[id]/(editComponents)/mailAlias.tsx index 531ccd171..9fc36fab4 100644 --- a/src/app/admin/mail/[filter]/[id]/(editComponents)/mailAlias.tsx +++ b/src/app/admin/mail/[filter]/[id]/(editComponents)/mailAlias.tsx @@ -3,12 +3,11 @@ import TextInput from '@/components/UI/TextInput' import Form from '@/components/Form/Form' import { SelectNumber } from '@/components/UI/Select' -import { createAliasMailingListRelationAction } from '@/actions/mail/create' -import { useUser } from '@/auth/useUser' -import { updateMailAliasAction } from '@/actions/mail/alias/update' -import { destroyMailAliasAction } from '@/actions/mail/alias/destory' +import { createAliasMailingListRelationAction } from '@/services/mail/actions' +import { useUser } from '@/auth/session/useUser' +import { updateMailAliasAction, destroyMailAliasAction } from '@/services/mail/alias/actions' import { useRouter } from 'next/navigation' -import type { MailFlowObject } from '@/services/mail/Types' +import type { MailFlowObject } from '@/services/mail/types' import type { MailingList } from '@prisma/client' diff --git a/src/app/admin/mail/[filter]/[id]/(editComponents)/mailingList.tsx b/src/app/admin/mail/[filter]/[id]/(editComponents)/mailingList.tsx index 6dcffb325..529dfa5fb 100644 --- a/src/app/admin/mail/[filter]/[id]/(editComponents)/mailingList.tsx +++ b/src/app/admin/mail/[filter]/[id]/(editComponents)/mailingList.tsx @@ -8,13 +8,12 @@ import { createMailingListExternalRelationAction, createMailingListGroupRelationAction, createMailingListUserRelationAction -} from '@/actions/mail/create' -import { useUser } from '@/auth/useUser' -import { updateMailingListAction } from '@/actions/mail/list/update' -import { destroyMailingListAction } from '@/actions/mail/list/destory' +} from '@/services/mail/actions' +import { useUser } from '@/auth/session/useUser' +import { updateMailingListAction, destroyMailingListAction } from '@/services/mail/list/actions' import { useRouter } from 'next/navigation' import type { MailAddressExternal, MailAlias } from '@prisma/client' -import type { MailFlowObject } from '@/services/mail/Types' +import type { MailFlowObject } from '@/services/mail/types' export default function EditMailingList({ diff --git a/src/app/admin/mail/[filter]/[id]/(editComponents)/user.tsx b/src/app/admin/mail/[filter]/[id]/(editComponents)/user.tsx index a1e6c8d4a..4f309b19e 100644 --- a/src/app/admin/mail/[filter]/[id]/(editComponents)/user.tsx +++ b/src/app/admin/mail/[filter]/[id]/(editComponents)/user.tsx @@ -2,9 +2,9 @@ import Form from '@/components/Form/Form' import { SelectNumber } from '@/components/UI/Select' -import { createMailingListUserRelationAction } from '@/actions/mail/create' -import { useUser } from '@/auth/useUser' -import type { MailFlowObject } from '@/services/mail/Types' +import { createMailingListUserRelationAction } from '@/services/mail/actions' +import { useUser } from '@/auth/session/useUser' +import type { MailFlowObject } from '@/services/mail/types' import type { MailingList } from '@prisma/client' export default function EditUser({ diff --git a/src/app/admin/mail/[filter]/[id]/MailFlow.tsx b/src/app/admin/mail/[filter]/[id]/MailFlow.tsx index c2ea0f0e3..67fc3175c 100644 --- a/src/app/admin/mail/[filter]/[id]/MailFlow.tsx +++ b/src/app/admin/mail/[filter]/[id]/MailFlow.tsx @@ -2,15 +2,15 @@ import MailList from './mailList' import styles from './MailFlow.module.scss' +import { useUser } from '@/auth/session/useUser' import { destroyAliasMailingListRelationAction, destroyMailingListExternalRelationAction, destroyMailingListGroupRelationAction, destroyMailingListUserRelationAction -} from '@/actions/mail/destroy' -import { useUser } from '@/auth/useUser' -import type { ActionReturn } from '@/actions/Types' -import type { MailFlowObject, MailListTypes } from '@/services/mail/Types' +} from '@/services/mail/actions' +import type { ActionReturn } from '@/services/actionTypes' +import type { MailFlowObject, MailListTypes } from '@/services/mail/types' type DestroyFunction = null | ((id: number) => Promise>) diff --git a/src/app/admin/mail/[filter]/[id]/mailList.tsx b/src/app/admin/mail/[filter]/[id]/mailList.tsx index bf3b48b35..d3de552be 100644 --- a/src/app/admin/mail/[filter]/[id]/mailList.tsx +++ b/src/app/admin/mail/[filter]/[id]/mailList.tsx @@ -2,13 +2,13 @@ import MailListItem from './mailListItem' import styles from './mailList.module.scss' -import { createActionError } from '@/actions/error' +import { createActionError } from '@/services/actionError' import { v4 as uuid } from 'uuid' import { useState } from 'react' -import type { ActionReturn } from '@/actions/Types' -import type { MailListTypes, ViaArrayType } from '@/services/mail/Types' +import type { ActionReturn } from '@/services/actionTypes' +import type { MailListTypes, ViaArrayType } from '@/services/mail/types' import type { Group, MailAddressExternal, MailAlias, MailingList } from '@prisma/client' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' const typeDisplayName: Record = { alias: 'Alias', diff --git a/src/app/admin/mail/[filter]/[id]/mailListItem.tsx b/src/app/admin/mail/[filter]/[id]/mailListItem.tsx index 5b128b49f..9ed0ed8d6 100644 --- a/src/app/admin/mail/[filter]/[id]/mailListItem.tsx +++ b/src/app/admin/mail/[filter]/[id]/mailListItem.tsx @@ -1,13 +1,13 @@ 'use client' import styles from './mailListItem.module.scss' -import { MailListTypeArray } from '@/services/mail/Types' +import { MailListTypeArray } from '@/services/mail/types' import Link from 'next/link' import { notFound } from 'next/navigation' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faTrashCan } from '@fortawesome/free-solid-svg-icons' import { v4 as uuid } from 'uuid' -import type { ActionReturn } from '@/actions/Types' -import type { MailListTypes, ViaArrayType } from '@/services/mail/Types' +import type { ActionReturn } from '@/services/actionTypes' +import type { MailListTypes, ViaArrayType } from '@/services/mail/types' export default function MailListItem({ type, diff --git a/src/app/admin/mail/[filter]/[id]/page.tsx b/src/app/admin/mail/[filter]/[id]/page.tsx index a71472b85..1b864d80b 100644 --- a/src/app/admin/mail/[filter]/[id]/page.tsx +++ b/src/app/admin/mail/[filter]/[id]/page.tsx @@ -7,11 +7,11 @@ import EditMailingList from './(editComponents)/mailingList' import EditMailAddressExternal from './(editComponents)/mailAddressExternal' import EditUser from './(editComponents)/user' import EditGroup from './(editComponents)/group' -import { readMailOptions, readMailFlowAction } from '@/actions/mail/read' -import { MailListTypeArray } from '@/services/mail/Types' +import { readMailOptions, readMailFlowAction } from '@/services/mail/actions' +import { MailListTypeArray } from '@/services/mail/types' import PageWrapper from '@/components/PageWrapper/PageWrapper' import { notFound } from 'next/navigation' -import type { MailListTypes } from '@/services/mail/Types' +import type { MailListTypes } from '@/services/mail/types' type PropTypes = { params: Promise<{ diff --git a/src/app/admin/mail/createMailAliasForm.tsx b/src/app/admin/mail/createMailAliasForm.tsx index e948435c7..48b16efa9 100644 --- a/src/app/admin/mail/createMailAliasForm.tsx +++ b/src/app/admin/mail/createMailAliasForm.tsx @@ -1,6 +1,6 @@ 'use client' -import { createMailAliasAction } from '@/actions/mail/alias/create' +import { createMailAliasAction } from '@/services/mail/alias/actions' import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' import { useRouter } from 'next/navigation' diff --git a/src/app/admin/mail/createMailaddressExternalForm.tsx b/src/app/admin/mail/createMailaddressExternalForm.tsx index 4cfd67d70..d4daebcdb 100644 --- a/src/app/admin/mail/createMailaddressExternalForm.tsx +++ b/src/app/admin/mail/createMailaddressExternalForm.tsx @@ -1,5 +1,5 @@ 'use client' -import { createMailAddressExternalAction } from '@/actions/mail/mailAddressExternal/create' +import { createMailAddressExternalAction } from '@/services/mail/mailAddressExternal/actions' import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' import { useRouter } from 'next/navigation' diff --git a/src/app/admin/mail/createMailingListForm.tsx b/src/app/admin/mail/createMailingListForm.tsx index 9f6ddb543..bce0b7d6f 100644 --- a/src/app/admin/mail/createMailingListForm.tsx +++ b/src/app/admin/mail/createMailingListForm.tsx @@ -1,5 +1,5 @@ 'use client' -import { createMailingListAction } from '@/actions/mail/list/create' +import { createMailingListAction } from '@/services/mail/list/actions' import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' import { useRouter } from 'next/navigation' diff --git a/src/app/admin/mail/mailList.tsx b/src/app/admin/mail/mailList.tsx index 5ab03a7a4..1cfcf9872 100644 --- a/src/app/admin/mail/mailList.tsx +++ b/src/app/admin/mail/mailList.tsx @@ -1,7 +1,7 @@ import Link from 'next/link' import { v4 as uuid } from 'uuid' -import type { MailListTypes } from '@/services/mail/Types' +import type { MailListTypes } from '@/services/mail/types' export default function MailList({ diff --git a/src/app/admin/mail/page.tsx b/src/app/admin/mail/page.tsx index a50c58024..6c273c744 100644 --- a/src/app/admin/mail/page.tsx +++ b/src/app/admin/mail/page.tsx @@ -5,7 +5,7 @@ import CreateMailAlias from './createMailAliasForm' import CreateMailingList from './createMailingListForm' import CreateMailaddressExternal from './createMailaddressExternalForm' import MailListView from './mailListView' -import { getUser } from '@/auth/getUser' +import { getUser } from '@/auth/session/getUser' import PageWrapper from '@/components/PageWrapper/PageWrapper' import { readMailAliases } from '@/services/mail/alias/read' import { readMailingLists } from '@/services/mail/list/read' diff --git a/src/app/admin/notification-channels/[currentId]/channelSettings.module.scss b/src/app/admin/notification-channels/[currentId]/ChannelSettings.module.scss similarity index 100% rename from src/app/admin/notification-channels/[currentId]/channelSettings.module.scss rename to src/app/admin/notification-channels/[currentId]/ChannelSettings.module.scss diff --git a/src/app/admin/notification-channels/[currentId]/channelSettings.tsx b/src/app/admin/notification-channels/[currentId]/ChannelSettings.tsx similarity index 92% rename from src/app/admin/notification-channels/[currentId]/channelSettings.tsx rename to src/app/admin/notification-channels/[currentId]/ChannelSettings.tsx index dad05c7de..d491a849f 100644 --- a/src/app/admin/notification-channels/[currentId]/channelSettings.tsx +++ b/src/app/admin/notification-channels/[currentId]/ChannelSettings.tsx @@ -1,17 +1,17 @@ 'use client' -import styles from './channelSettings.module.scss' +import styles from './ChannelSettings.module.scss' import NotificationMethodSelector from '@/components/NotificaionMethodSelector/NotificaionMethodSelector' import TextInput from '@/components/UI/TextInput' import { SelectNumber } from '@/components/UI/Select' import Form from '@/components/Form/Form' import PageWrapper from '@/components/PageWrapper/PageWrapper' -import { updateNotificationChannelAction } from '@/actions/notifications' -import { NotificationChannelSchemas } from '@/services/notifications/channel/schemas' import { booleanOperationOnMethods } from '@/services/notifications/notificationMethodOperations' -import { configureAction } from '@/actions/configureAction' +import { updateNotificationChannelAction } from '@/services/notifications/actions' +import { findValidParents } from '@/services/notifications/channel/schemas' +import { configureAction } from '@/services/configureAction' import { useState } from 'react' -import type { ExpandedNotificationChannel } from '@/services/notifications/Types' +import type { ExpandedNotificationChannel } from '@/services/notifications/types' import type { MailAlias } from '@prisma/client' export default function ChannelSettings({ @@ -25,7 +25,7 @@ export default function ChannelSettings({ }) { const [currentChannelState, setCurrentChannel] = useState(currentChannel) - const selectOptions = NotificationChannelSchemas.findValidParents(currentChannel.id, channels) + const selectOptions = findValidParents(currentChannel.id, channels) return (NotificationConfig.allMethodsOn) - const [defaultMethods, setDefaultMethods] = useState(NotificationConfig.allMethodsOff) + const [availableMethods, setAvailableMethods] = useState(allNotificationMethodsOn) + const [defaultMethods, setDefaultMethods] = useState(allNotificationMethodsOn) const [selectedParentId, setSelectedParentId] = useState(channels.find(channel => channel.special === 'ROOT')?.id) const [editableMethods, setEditableMethods] = useState( - channels.find(channel => channel.id === selectedParentId)?.availableMethods ?? NotificationConfig.allMethodsOn + channels.find(channel => channel.id === selectedParentId)?.availableMethods ?? allNotificationMethodsOn ) function handleNewParent(id: number) { setSelectedParentId(id) - setEditableMethods(channels.find(channel => channel.id === id)?.availableMethods ?? NotificationConfig.allMethodsOn) + setEditableMethods(channels.find(channel => channel.id === id)?.availableMethods ?? allNotificationMethodsOff) } return = { - serviceMethod: ServiceMethodType, + serviceOperation: ServiceOperation, } & (ParamsSchema extends undefined ? { params?: undefined, } : { - params: (rawparams: RawParams) => z.input>, + params: (rawParams: RawParams) => z.input>, }) async function apiHandlerGeneric(req: Request, handle: (session: SessionNoUser) => Promise) { @@ -41,19 +41,15 @@ export function apiHandler< Return, ParamsSchema extends z.ZodTypeAny | undefined = undefined, DataSchema extends z.ZodTypeAny | undefined = undefined, ->({ serviceMethod, params }: APIHandler) { +>({ serviceOperation, params }: APIHandler) { // TODO: I think I will rewrite this to be easier to read return async (req: Request, { params: rawParams }: { params: Promise }) => await apiHandlerGeneric(req, async session => { - if (serviceMethod.dataSchema) { - try { - const rawdata = await req.json() + let data - return serviceMethod.newClient().executeUnsafe({ - params: params ? params(await rawParams) : undefined, - data: rawdata, - session, - }) + if (serviceOperation.dataSchema) { + try { + data = await req.json() } catch (error) { if (error instanceof SyntaxError) { throw new ServerError('BAD DATA', 'The API only accepts valid json data.') @@ -62,9 +58,9 @@ export function apiHandler< } } - return serviceMethod.newClient().executeUnsafe({ + return serviceOperation<'UNSAFE'>({ params: params ? params(await rawParams) : undefined, - data: undefined, + data, session, }) }) diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 34db9c6c1..34a9f8546 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,4 +1,4 @@ -import { authOptions } from '@/auth/authoptions' +import { authOptions } from '@/auth/nextAuth/authOptions' import NextAuth from 'next-auth' const handler = NextAuth(authOptions) diff --git a/src/app/api/shop/getAll/route.ts b/src/app/api/shop/getAll/route.ts index b1dc4837f..a525df2b4 100644 --- a/src/app/api/shop/getAll/route.ts +++ b/src/app/api/shop/getAll/route.ts @@ -1,6 +1,6 @@ import { apiHandler } from '@/app/api/apiHandler' -import { ShopMethods } from '@/services/shop/shop/methods' +import { shopOperations } from '@/services/shop/shop/operations' export const GET = apiHandler({ - serviceMethod: ShopMethods.readMany, + serviceOperation: shopOperations.readMany, }) diff --git a/src/app/api/shop/product/barcode/route.ts b/src/app/api/shop/product/barcode/route.ts index 972883772..468ef56fc 100644 --- a/src/app/api/shop/product/barcode/route.ts +++ b/src/app/api/shop/product/barcode/route.ts @@ -1,6 +1,6 @@ import { apiHandler } from '@/app/api/apiHandler' -import { ProductMethods } from '@/services/shop/product/methods' +import { productOperations } from '@/services/shop/product/operations' export const POST = apiHandler({ - serviceMethod: ProductMethods.readByBarCode, + serviceOperation: productOperations.readByBarCode, }) diff --git a/src/app/api/shop/purchase/createByStudentCard/route.ts b/src/app/api/shop/purchase/createByStudentCard/route.ts index 3039ff8b6..c2dfd7e47 100644 --- a/src/app/api/shop/purchase/createByStudentCard/route.ts +++ b/src/app/api/shop/purchase/createByStudentCard/route.ts @@ -1,6 +1,6 @@ import { apiHandler } from '@/app/api/apiHandler' -import { PurchaseMethods } from '@/services/shop/purchase/methods' +import { purchaseOperations } from '@/services/shop/purchase/operations' export const POST = apiHandler({ - serviceMethod: PurchaseMethods.createByStudentCard, + serviceOperation: purchaseOperations.createByStudentCard, }) diff --git a/src/app/api/users/connectStudentCard/route.ts b/src/app/api/users/connectStudentCard/route.ts index 038b6915c..6ce1703b6 100644 --- a/src/app/api/users/connectStudentCard/route.ts +++ b/src/app/api/users/connectStudentCard/route.ts @@ -1,7 +1,7 @@ import { apiHandler } from '@/api/apiHandler' -import { UserMethods } from '@/services/users/methods' +import { userOperations } from '@/services/users/operations' export const POST = apiHandler({ - serviceMethod: UserMethods.connectStudentCard + serviceOperation: userOperations.connectStudentCard }) diff --git a/src/app/api/users/get/[username]/route.ts b/src/app/api/users/get/[username]/route.ts index e81bc6b34..957fc7773 100644 --- a/src/app/api/users/get/[username]/route.ts +++ b/src/app/api/users/get/[username]/route.ts @@ -1,12 +1,12 @@ import { apiHandler } from '@/api/apiHandler' -import { UserMethods } from '@/services/users/methods' +import { userOperations } from '@/services/users/operations' export const GET = apiHandler({ - serviceMethod: UserMethods.readProfile, + serviceOperation: userOperations.readProfile, params: (rawparams: {username: string}) => ({ username: rawparams.username }) }) export const PATCH = apiHandler({ - serviceMethod: UserMethods.update, + serviceOperation: userOperations.update, params: (rawparams: { username: string }) => ({ username: rawparams.username }) }) diff --git a/src/app/api/users/getWithBalance/[studentCard]/route.ts b/src/app/api/users/getWithBalance/[studentCard]/route.ts index 492ee5eb8..bf389c275 100644 --- a/src/app/api/users/getWithBalance/[studentCard]/route.ts +++ b/src/app/api/users/getWithBalance/[studentCard]/route.ts @@ -1,9 +1,9 @@ import { apiHandler } from '@/api/apiHandler' -import { UserMethods } from '@/services/users/methods' +import { userOperations } from '@/services/users/operations' export const GET = apiHandler({ params: (rawparams: { studentCard: string }) => ({ studentCard: rawparams.studentCard, }), - serviceMethod: UserMethods.readUserWithBalance, + serviceOperation: userOperations.readUserWithBalance, }) diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts index 45e20a6e7..a68ca7400 100644 --- a/src/app/api/users/route.ts +++ b/src/app/api/users/route.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { apiHandler } from '@/api/apiHandler' -import { UserMethods } from '@/services/users/methods' +import { userOperations } from '@/services/users/operations' export const POST = apiHandler({ - serviceMethod: UserMethods.create + serviceOperation: userOperations.create }) diff --git a/src/app/applications/CreateUpdateApplicationPeriodForm.tsx b/src/app/applications/CreateUpdateApplicationPeriodForm.tsx index a69f2e7a2..10d4a6586 100644 --- a/src/app/applications/CreateUpdateApplicationPeriodForm.tsx +++ b/src/app/applications/CreateUpdateApplicationPeriodForm.tsx @@ -1,14 +1,13 @@ 'use client' import styles from './CreateUpdateApplicationPeriodForm.module.scss' -import { createApplicationPeriodAction } from '@/actions/applications/periods/create' -import { updateApplicationPeriodAction } from '@/actions/applications/periods/update' -import { configureAction } from '@/actions/configureAction' +import { createApplicationPeriodAction, updateApplicationPeriodAction } from '@/services/applications/periods/actions' +import { configureAction } from '@/services/configureAction' import Form from '@/components/Form/Form' import Checkbox from '@/components/UI/Checkbox' import DateInput from '@/components/UI/DateInput' import TextInput from '@/components/UI/TextInput' -import type { ExpandedApplicationPeriod } from '@/services/applications/periods/Types' -import type { ExpandedCommittee } from '@/services/groups/committees/Types' +import type { ExpandedApplicationPeriod } from '@/services/applications/periods/types' +import type { ExpandedCommittee } from '@/services/groups/committees/types' type PropTypes = { committees: ExpandedCommittee[] diff --git a/src/app/applications/[periodName]/Reprioritize.tsx b/src/app/applications/[periodName]/Reprioritize.tsx index 41b44bdb4..0d36473e2 100644 --- a/src/app/applications/[periodName]/Reprioritize.tsx +++ b/src/app/applications/[periodName]/Reprioritize.tsx @@ -1,6 +1,6 @@ 'use client' import styles from './Reprioritize.module.scss' -import { updateApplicationAction } from '@/actions/applications/update' +import { updateApplicationAction } from '@/services/applications/actions' import { faArrowDown, faArrowUp } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { useCallback, useState } from 'react' diff --git a/src/app/applications/[periodName]/countdown/CommitteeLogoRoll.tsx b/src/app/applications/[periodName]/countdown/CommitteeLogoRoll.tsx index 13bddbe46..c631ad008 100644 --- a/src/app/applications/[periodName]/countdown/CommitteeLogoRoll.tsx +++ b/src/app/applications/[periodName]/countdown/CommitteeLogoRoll.tsx @@ -2,7 +2,7 @@ import styles from './CommitteeLogoRoll.module.scss' import Image from '@/app/_components/Image/Image' import useInterval from '@/hooks/useInterval' -import { readNumberOfApplicationsAction } from '@/actions/applications/periods/read' +import { readNumberOfApplicationsAction } from '@/services/applications/periods/actions' import { useRef, useState } from 'react' import type { Image as ImageT } from '@prisma/client' diff --git a/src/app/applications/[periodName]/countdown/Countdown.tsx b/src/app/applications/[periodName]/countdown/Countdown.tsx index 20d50d23a..4b991fefc 100644 --- a/src/app/applications/[periodName]/countdown/Countdown.tsx +++ b/src/app/applications/[periodName]/countdown/Countdown.tsx @@ -7,7 +7,7 @@ import FinalCountdown from './FinalCountdown' import useInterval from '@/hooks/useInterval' import { useState } from 'react' import type { Image } from '@prisma/client' -import type { ExpandedApplicationPeriod } from '@/services/applications/periods/Types' +import type { ExpandedApplicationPeriod } from '@/services/applications/periods/types' type PropTypes = { period: ExpandedApplicationPeriod diff --git a/src/app/applications/[periodName]/countdown/FinalCountdown.tsx b/src/app/applications/[periodName]/countdown/FinalCountdown.tsx index 799d542db..62227bad9 100644 --- a/src/app/applications/[periodName]/countdown/FinalCountdown.tsx +++ b/src/app/applications/[periodName]/countdown/FinalCountdown.tsx @@ -1,6 +1,6 @@ import styles from './FinalCountdown.module.scss' -import { readNumberOfApplicationsAction } from '@/actions/applications/periods/read' import useInterval from '@/hooks/useInterval' +import { readNumberOfApplicationsAction } from '@/services/applications/periods/actions' import { useEffect, useRef, useState } from 'react' type PropTypes = { diff --git a/src/app/applications/[periodName]/countdown/page.tsx b/src/app/applications/[periodName]/countdown/page.tsx index a412e2869..2735584ba 100644 --- a/src/app/applications/[periodName]/countdown/page.tsx +++ b/src/app/applications/[periodName]/countdown/page.tsx @@ -1,8 +1,8 @@ import styles from './page.module.scss' import Countdown from './Countdown' -import { readApplicationPeriodAction } from '@/actions/applications/periods/read' +import { readApplicationPeriodAction } from '@/services/applications/periods/actions' import { unwrapActionReturn } from '@/app/redirectToErrorPage' -import { readSpecialImageAction } from '@/images/read' +import { readSpecialImageAction } from '@/services/images/actions' import type { PropTypes } from '@/app/applications/[periodName]/page' export default async function ApplicationPeriodCountdown({ params }: PropTypes) { diff --git a/src/app/applications/[periodName]/page.tsx b/src/app/applications/[periodName]/page.tsx index 05d7a69e8..1e4662621 100644 --- a/src/app/applications/[periodName]/page.tsx +++ b/src/app/applications/[periodName]/page.tsx @@ -1,28 +1,33 @@ import styles from './page.module.scss' import Reprioritize from './Reprioritize' -import { readApplicationPeriodAction } from '@/actions/applications/periods/read' import { unwrapActionReturn } from '@/app/redirectToErrorPage' import { default as DateComponent } from '@/components/Date/Date' import PageWrapper from '@/components/PageWrapper/PageWrapper' import CountDown from '@/components/countDown/CountDown' -import { readSpecialImageAction } from '@/images/read' import BackdropImage from '@/components/BackdropImage/BackdropImage' import CmsParagraph from '@/components/Cms/CmsParagraph/CmsParagraph' import PopUp from '@/components/PopUp/PopUp' -import { readApplicationsForUserAction } from '@/actions/applications/read' -import { Session } from '@/auth/Session' +import { Session } from '@/auth/session/Session' import Textarea from '@/components/UI/Textarea' import Form from '@/components/Form/Form' -import { createApplicationAction } from '@/actions/applications/create' -import { updateApplicationAction } from '@/actions/applications/update' import { SettingsHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' import CreateUpdateApplicationPeriodForm from '@/app/applications/CreateUpdateApplicationPeriodForm' -import { readCommitteesAction } from '@/actions/groups/committees/read' -import { destroyApplicationAction } from '@/actions/applications/destroy' -import { destroyApplicationPeriodAction, removeAllApplicationTextsAction } from '@/actions/applications/periods/destroy' -import Link from 'next/link' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { + createApplicationAction, + updateApplicationAction, + destroyApplicationAction, + readApplicationsForUserAction +} from '@/services/applications/actions' +import { readCommitteesAction } from '@/services/groups/committees/actions' +import { + destroyApplicationPeriodAction, + removeAllApplicationTextsAction, + readApplicationPeriodAction +} from '@/services/applications/periods/actions' +import { readSpecialImageAction } from '@/services/images/actions' import { faVideo } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import Link from 'next/link' export type PropTypes = { params: Promise<{ diff --git a/src/app/applications/page.tsx b/src/app/applications/page.tsx index 277b00c95..03080cc82 100644 --- a/src/app/applications/page.tsx +++ b/src/app/applications/page.tsx @@ -1,11 +1,11 @@ import styles from './page.module.scss' import CreateUpdateApplicationPeriodForm from './CreateUpdateApplicationPeriodForm' import { unwrapActionReturn } from '@/app/redirectToErrorPage' -import { readApplicationPeriodsAction } from '@/actions/applications/periods/read' +import { readApplicationPeriodsAction } from '@/services/applications/periods/actions' import PageWrapper from '@/components/PageWrapper/PageWrapper' import Date from '@/components/Date/Date' import { AddHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' -import { readCommitteesAction } from '@/actions/groups/committees/read' +import { readCommitteesAction } from '@/services/groups/committees/actions' export default async function Apllications() { const periods = unwrapActionReturn(await readApplicationPeriodsAction()) diff --git a/src/app/articles/AddCategory.tsx b/src/app/articles/AddCategory.tsx index 95908c907..0ca7d431f 100644 --- a/src/app/articles/AddCategory.tsx +++ b/src/app/articles/AddCategory.tsx @@ -3,7 +3,7 @@ import styles from './AddCategory.module.scss' import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' import Textarea from '@/components/UI/Textarea' -import { createArticleCategoryAction } from '@/actions/cms/articleCategories/create' +import { createArticleCategoryAction } from '@/services/cms/articleCategories/actions' import { useRouter } from 'next/navigation' export default function AddCategory() { diff --git a/src/app/articles/[category]/EditCategory.tsx b/src/app/articles/[category]/EditCategory.tsx index 683403547..dc4ebdee4 100644 --- a/src/app/articles/[category]/EditCategory.tsx +++ b/src/app/articles/[category]/EditCategory.tsx @@ -3,13 +3,12 @@ import Form from '@/components/Form/Form' import PopUp from '@/components/PopUp/PopUp' import Textarea from '@/components/UI/Textarea' import TextInput from '@/components/UI/TextInput' -import { updateArticleCategoryAction } from '@/actions/cms/articleCategories/update' -import { createArticleAction } from '@/actions/cms/articles/create' -import { destroyArticleCategoryAction } from '@/actions/cms/articleCategories/destroy' +import { updateArticleCategoryAction, destroyArticleCategoryAction } from '@/services/cms/articleCategories/actions' +import { createArticleAction } from '@/services/cms/articles/actions' import { useRouter } from 'next/navigation' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCog } from '@fortawesome/free-solid-svg-icons' -import type { ExpandedArticleCategory } from '@/cms/articleCategories/Types' +import type { ExpandedArticleCategory } from '@/cms/articleCategories/types' type PropTypes = { category: ExpandedArticleCategory diff --git a/src/app/articles/[category]/SideBar.tsx b/src/app/articles/[category]/SideBar.tsx index 65902c1f3..c2a74bf4a 100644 --- a/src/app/articles/[category]/SideBar.tsx +++ b/src/app/articles/[category]/SideBar.tsx @@ -4,13 +4,13 @@ import EditCategory from './EditCategory' import useScroll from '@/hooks/useScroll' import useOnNavigation from '@/hooks/useOnNavigation' import useViewPort from '@/hooks/useViewPort' -import { destroyArticleAction } from '@/actions/cms/articles/destroy' +import { destroyArticleAction } from '@/services/cms/articles/actions' import React, { useRef, useState } from 'react' import Link from 'next/link' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faChevronUp, faX } from '@fortawesome/free-solid-svg-icons' import { useRouter } from 'next/navigation' -import type { ExpandedArticleCategory } from '@/cms/articleCategories/Types' +import type { ExpandedArticleCategory } from '@/cms/articleCategories/types' type PropTypes = { category: ExpandedArticleCategory diff --git a/src/app/articles/[category]/[name]/page.tsx b/src/app/articles/[category]/[name]/page.tsx index b4b8a4e70..a0104c511 100644 --- a/src/app/articles/[category]/[name]/page.tsx +++ b/src/app/articles/[category]/[name]/page.tsx @@ -1,6 +1,6 @@ import styles from './page.module.scss' import Article from '@/cms/Article/Article' -import { readArticleAction } from '@/cms/articles/read' +import { readArticleAction } from '@/cms/articles/actions' import { notFound } from 'next/navigation' type PropTypes = { diff --git a/src/app/articles/[category]/layout.tsx b/src/app/articles/[category]/layout.tsx index 3337753c5..c3539e44c 100644 --- a/src/app/articles/[category]/layout.tsx +++ b/src/app/articles/[category]/layout.tsx @@ -1,6 +1,6 @@ import styles from './layout.module.scss' import SideBar from './SideBar' -import { readArticleCategoryAction } from '@/cms/articleCategories/read' +import { readArticleCategoryAction } from '@/cms/articleCategories/actions' import { notFound } from 'next/navigation' import type { ReactNode } from 'react' diff --git a/src/app/articles/page.tsx b/src/app/articles/page.tsx index 00031dbb9..6e9cc1812 100644 --- a/src/app/articles/page.tsx +++ b/src/app/articles/page.tsx @@ -2,7 +2,7 @@ import styles from './page.module.scss' import AddCategory from './AddCategory' import { AddHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' import ImageCard from '@/components/ImageCard/ImageCard' -import { readArticleCategoriesAction } from '@/cms/articleCategories/read' +import { readArticleCategoriesAction } from '@/cms/articleCategories/actions' import PageWrapper from '@/components/PageWrapper/PageWrapper' export default async function ArticleCategoryList() { diff --git a/src/app/cabin/book/CabinCalendar.tsx b/src/app/cabin/book/CabinCalendar.tsx index 66065ce4c..c193140d3 100644 --- a/src/app/cabin/book/CabinCalendar.tsx +++ b/src/app/cabin/book/CabinCalendar.tsx @@ -3,7 +3,7 @@ import styles from './CabinCalendar.module.scss' import { dateInInterval, dateLessThan, datesEqual, getWeekNumber } from '@/lib/dates/comparison' import React, { useState } from 'react' import { v4 as uuid } from 'uuid' -import type { BookingFiltered } from '@/services/cabin/booking/Types' +import type { BookingFiltered } from '@/services/cabin/booking/types' import type { ReactNode } from 'react' import type { Record } from '@prisma/client/runtime/library' diff --git a/src/app/cabin/book/CabinPriceCalculator.tsx b/src/app/cabin/book/CabinPriceCalculator.tsx index 89c4d0236..1cf32e0b2 100644 --- a/src/app/cabin/book/CabinPriceCalculator.tsx +++ b/src/app/cabin/book/CabinPriceCalculator.tsx @@ -1,9 +1,9 @@ import SimpleTable from '@/app/_components/Table/SimpleTable' import { displayPrice } from '@/lib/money/convert' import { calculateCabinBookingPrice, calculateTotalCabinBookingPrice } from '@/services/cabin/booking/cabinPriceCalculator' +import type { CabinProductExtended } from '@/services/cabin/product/constants' import type { CabinPriceCalculatorReturnType } from '@/services/cabin/booking/cabinPriceCalculator' import type { PricePeriod } from '@prisma/client' -import type { CabinProductConfig } from '@/services/cabin/product/config' export default function CabinPriceCalculator({ @@ -16,7 +16,7 @@ export default function CabinPriceCalculator({ numberOfNonMembers, }: { pricePeriods: PricePeriod[], - products: CabinProductConfig.CabinProductExtended[], + products: CabinProductExtended[], productAmounts: number[], startDate?: Date, endDate?: Date, diff --git a/src/app/cabin/book/SelectBedProduct.tsx b/src/app/cabin/book/SelectBedProduct.tsx index a2aaf5fdf..33409b772 100644 --- a/src/app/cabin/book/SelectBedProduct.tsx +++ b/src/app/cabin/book/SelectBedProduct.tsx @@ -1,5 +1,5 @@ import NumberInput from '@/app/_components/UI/NumberInput' -import type { CabinProductConfig } from '@/services/cabin/product/config' +import type { CabinProductExtended } from '@/services/cabin/product/constants' export default function SelectBedProducts({ @@ -7,7 +7,7 @@ export default function SelectBedProducts({ onChange, amounts, }: { - bedProducts: CabinProductConfig.CabinProductExtended[], + bedProducts: CabinProductExtended[], onChange: (product: number[]) => void amounts: number[], }) { diff --git a/src/app/cabin/book/page.tsx b/src/app/cabin/book/page.tsx index 72c916c81..e9c115055 100644 --- a/src/app/cabin/book/page.tsx +++ b/src/app/cabin/book/page.tsx @@ -7,10 +7,10 @@ import { readCabinProductsActiveAction, readPublicPricePeriodsAction, readReleasePeriodsAction -} from '@/actions/cabin' +} from '@/services/cabin/actions' import { displayDate } from '@/lib/dates/displayDate' -import { Session } from '@/auth/Session' -import { CabinBookingAuthers } from '@/services/cabin/booking/authers' +import { Session } from '@/auth/session/Session' +import { cabinBookingAuth } from '@/services/cabin/booking/auth' import type { ReleasePeriod } from '@prisma/client' function findCurrentReleasePeriod(releasePeriods: ReleasePeriod[]) { @@ -41,8 +41,8 @@ export default async function CabinBooking() { const pricePeriods = unwrapActionReturn(await readPublicPricePeriodsAction()) const cabinProducts = unwrapActionReturn(await readCabinProductsActiveAction()) const session = await Session.fromNextAuth() - const canBookCabin = CabinBookingAuthers.createCabinBookingNoUser.dynamicFields({}).auth(session) - const canBookBed = CabinBookingAuthers.createBedBookingNoUser.dynamicFields({}).auth(session) + const canBookCabin = cabinBookingAuth.createCabinBookingNoUser.dynamicFields({}).auth(session) + const canBookBed = cabinBookingAuth.createBedBookingNoUser.dynamicFields({}).auth(session) return (canBookCabin ? 'CABIN' : 'BED') const [dateRange, setDateRange] = useState({}) - const [selectedProducts, setSelectedProducts] = useState( + const [selectedProducts, setSelectedProducts] = useState( canBookCabin ? [cabinProduct] : bedProducts ) const [bedAmounts, setBedAmounts] = useState(Array(bedProducts.length).fill(0)) @@ -217,29 +217,29 @@ export default function StateWrapper({ name="firstname" label="Fornavn" defaultValue={user.user?.firstname ?? ''} - disabled={!!user.user} - readOnly={!!user.user} + disabled={Boolean(user.user)} + readOnly={Boolean(user.user)} /> diff --git a/src/app/career/companies/page.tsx b/src/app/career/companies/page.tsx index 363336fde..8bbc19ac1 100644 --- a/src/app/career/companies/page.tsx +++ b/src/app/career/companies/page.tsx @@ -1,17 +1,16 @@ -import { createCompanyAction } from '@/actions/career/companies/create' +import { createCompanyAction, readCompanyPageAction } from '@/services/career/companies/actions' import Form from '@/components/Form/Form' import { AddHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' import TextInput from '@/components/UI/TextInput' import PageWrapper from '@/components/PageWrapper/PageWrapper' -import CompanyPagingProvider from '@/contexts/paging/CompanyPaging' -import { readCompanyPageAction } from '@/actions/career/companies/read' +import { CompanyPagingProvider } from '@/contexts/paging/CompanyPaging' import CompanyList from '@/components/Company/CompanyList' import { companyListRenderer } from '@/components/Company/CompanyListRenderer' -import { QueryParams } from '@/lib/query-params/queryParams' +import { QueryParams } from '@/lib/queryParams/queryParams' import CompanyListFilter from '@/app/_components/Company/CompanyListFilter' -import { Session } from '@/auth/Session' -import { configureAction } from '@/actions/configureAction' -import type { SearchParamsServerSide } from '@/lib/query-params/Types' +import { Session } from '@/auth/session/Session' +import { configureAction } from '@/services/configureAction' +import type { SearchParamsServerSide } from '@/lib/queryParams/types' import type { PageSizeCompany } from '@/contexts/paging/CompanyPaging' type PropTypes = SearchParamsServerSide diff --git a/src/app/career/jobads/CreateJobAdForm.tsx b/src/app/career/jobads/CreateJobAdForm.tsx index 15cc5e8ad..0c13102bb 100644 --- a/src/app/career/jobads/CreateJobAdForm.tsx +++ b/src/app/career/jobads/CreateJobAdForm.tsx @@ -2,11 +2,11 @@ import styles from './CreateJobAdForm.module.scss' import CompanyChooser from './CompanyChooser' import SelectedCompany from './SelectedCompany' -import { createJobAdAction } from '@/actions/career/jobAds/create' +import { createJobAdAction } from '@/services/career/jobAds/actions' import TextInput from '@/components/UI/TextInput' import Form from '@/components/Form/Form' import { SelectString } from '@/components/UI/Select' -import { JobAdConfig } from '@/services/career/jobAds/config' +import { jobAdOptions } from '@/services/career/jobAds/constants' import DateInput from '@/components/UI/DateInput' import { v4 as uuid } from 'uuid' @@ -23,7 +23,7 @@ export default function CreateJobAdForm() { - + diff --git a/src/app/career/jobads/CurrentJobAds.tsx b/src/app/career/jobads/CurrentJobAds.tsx index 3df157894..b5efdc009 100644 --- a/src/app/career/jobads/CurrentJobAds.tsx +++ b/src/app/career/jobads/CurrentJobAds.tsx @@ -1,5 +1,5 @@ import JobAd from './JobAd' -import { readActiveJobAdsAction } from '@/actions/career/jobAds/read' +import { readActiveJobAdsAction } from '@/services/career/jobAds/actions' type PropTypes = { not?: number diff --git a/src/app/career/jobads/JobAd.tsx b/src/app/career/jobads/JobAd.tsx index 9705c8cf0..2c0e920ed 100644 --- a/src/app/career/jobads/JobAd.tsx +++ b/src/app/career/jobads/JobAd.tsx @@ -1,7 +1,7 @@ import styles from './JobAd.module.scss' import ImageCard from '@/components/ImageCard/ImageCard' -import { JobAdConfig } from '@/services/career/jobAds/config' -import type { SimpleJobAd } from '@/services/career/jobAds/Types' +import { jobAdType } from '@/services/career/jobAds/constants' +import type { SimpleJobAd } from '@/services/career/jobAds/types' type PropTypes = { jobAd: SimpleJobAd @@ -16,7 +16,7 @@ export default function JobAd({ jobAd }: PropTypes) { key={jobAd.id} > {!jobAd.active ?

Inaktiv

: <>} -

{jobAd.companyName} - {JobAdConfig.type[jobAd.type].label}

+

{jobAd.companyName} - {jobAdType[jobAd.type].label}

{jobAd.description} ) diff --git a/src/app/career/jobads/[...orderAndName]/EditJobAd.tsx b/src/app/career/jobads/[...orderAndName]/EditJobAd.tsx index f7f2bfc6c..af4a5b1b7 100644 --- a/src/app/career/jobads/[...orderAndName]/EditJobAd.tsx +++ b/src/app/career/jobads/[...orderAndName]/EditJobAd.tsx @@ -2,8 +2,6 @@ import styles from './EditJobAd.module.scss' import SelectedCompany from '@/career/jobads/SelectedCompany' import Form from '@/components/Form/Form' -import { updateJobAdAction } from '@/career/jobAds/update' -import { destroyJobAdAction } from '@/career/jobAds/destroy' import TextInput from '@/components/UI/TextInput' import Textarea from '@/components/UI/Textarea' import useEditing from '@/hooks/useEditing' @@ -12,11 +10,12 @@ import DateInput from '@/components/UI/DateInput' import Slider from '@/app/_components/UI/Slider' import { CompanyPagingContext } from '@/contexts/paging/CompanyPaging' import CompanyChooser from '@/app/career/jobads/CompanyChooser' -import { JobAdConfig } from '@/services/career/jobAds/config' -import { configureAction } from '@/actions/configureAction' +import { destroyJobAdAction, updateJobAdAction } from '@/career/jobAds/actions' +import { jobAdOptions } from '@/services/career/jobAds/constants' +import { configureAction } from '@/services/configureAction' import { v4 as uuid } from 'uuid' import { useContext, type ReactNode } from 'react' -import type { ExpandedJobAd } from '@/career/jobAds/Types' +import type { ExpandedJobAd } from '@/services/career/jobAds/types' type PropTypes = { jobAd: ExpandedJobAd @@ -61,7 +60,7 @@ export default function EditJobAd({ jobAd, children }: PropTypes) { />

Stillingstype

-

{JobAdConfig.type[jobAd.type].label}

+

{jobAdType[jobAd.type].label}

  • diff --git a/src/app/career/jobads/archive/page.tsx b/src/app/career/jobads/archive/page.tsx index 1a83f8863..5b4ceb084 100644 --- a/src/app/career/jobads/archive/page.tsx +++ b/src/app/career/jobads/archive/page.tsx @@ -1,7 +1,7 @@ import styles from './page.module.scss' import JobAdInactiveList from './JobAdInactiveList' import PageWrapper from '@/components/PageWrapper/PageWrapper' -import JobAdInactiveProvider from '@/contexts/paging/JobAdInactivePaging' +import { JobAdInactivePagingProvider } from '@/contexts/paging/JobAdInactivePaging' import { faArrowLeft } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import Link from 'next/link' @@ -13,7 +13,7 @@ export default async function JobAdsArchive() { }> - - + ) } diff --git a/src/app/career/jobads/page.tsx b/src/app/career/jobads/page.tsx index 0febd4caa..c5083a09f 100644 --- a/src/app/career/jobads/page.tsx +++ b/src/app/career/jobads/page.tsx @@ -3,7 +3,7 @@ import CreateJobAdForm from './CreateJobAdForm' import CurrentJobAds from './CurrentJobAds' import { AddHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' import PageWrapper from '@/components/PageWrapper/PageWrapper' -import CompanyPagingProvider from '@/contexts/paging/CompanyPaging' +import { CompanyPagingProvider } from '@/contexts/paging/CompanyPaging' import CompanySelectionProvider from '@/contexts/CompanySelection' import Link from 'next/link' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' diff --git a/src/app/career/page.tsx b/src/app/career/page.tsx index 66786bebb..47cd93712 100644 --- a/src/app/career/page.tsx +++ b/src/app/career/page.tsx @@ -1,13 +1,13 @@ import styles from './page.module.scss' import SpecialCmsParagraph from '@/components/Cms/CmsParagraph/SpecialCmsParagraph' import PageWrapper from '@/components/PageWrapper/PageWrapper' -import { Session } from '@/auth/Session' +import { Session } from '@/auth/session/Session' import Image from '@/components/Image/Image' -import { readSpecialImageAction } from '@/actions/images/read' -import { readSpecialCmsLinkAction } from '@/actions/cms/links/read' import CmsLink from '@/components/Cms/CmsLink/CmsLink' -import { readSpecialEventTagAction } from '@/actions/events/tags/read' -import { QueryParams } from '@/lib/query-params/queryParams' +import { QueryParams } from '@/lib/queryParams/queryParams' +import { readSpecialImageAction } from '@/services/images/actions' +import { readSpecialCmsLinkAction } from '@/services/cms/links/actions' +import { readSpecialEventTagAction } from '@/services/events/tags/actions' import Link from 'next/link' export default async function CareerLandingPage() { diff --git a/src/app/committees/[shortName]/about/page.tsx b/src/app/committees/[shortName]/about/page.tsx index 52f87d68c..3ccec592b 100644 --- a/src/app/committees/[shortName]/about/page.tsx +++ b/src/app/committees/[shortName]/about/page.tsx @@ -1,5 +1,5 @@ import styles from './page.module.scss' -import { readCommitteeArticleAction } from '@/actions/groups/committees/read' +import { readCommitteeArticleAction } from '@/services/groups/committees/actions' import Article from '@/components/Cms/Article/Article' export type PropTypes = { diff --git a/src/app/committees/[shortName]/getCommittee.ts b/src/app/committees/[shortName]/getCommittee.ts index b58648b3b..3bf1691bf 100644 --- a/src/app/committees/[shortName]/getCommittee.ts +++ b/src/app/committees/[shortName]/getCommittee.ts @@ -1,4 +1,4 @@ -import { readCommitteeAction } from '@/actions/groups/committees/read' +import { readCommitteeAction } from '@/services/groups/committees/actions' import { notFound } from 'next/navigation' import type { PropTypes } from './page' diff --git a/src/app/committees/[shortName]/layout.tsx b/src/app/committees/[shortName]/layout.tsx index 995fba501..0362fee6d 100644 --- a/src/app/committees/[shortName]/layout.tsx +++ b/src/app/committees/[shortName]/layout.tsx @@ -1,7 +1,7 @@ import getCommitee from './getCommittee' import Nav from './Nav' import styles from './layout.module.scss' -import { readSpecialImageAction } from '@/actions/images/read' +import { readSpecialImageAction } from '@/services/images/actions' import BackdropImage from '@/components/BackdropImage/BackdropImage' import PageWrapper from '@/components/PageWrapper/PageWrapper' import CommitteeImage from '@/components/CommitteeImage/CommitteeImage' diff --git a/src/app/committees/[shortName]/members/page.tsx b/src/app/committees/[shortName]/members/page.tsx index 50e606bd0..8615e21a9 100644 --- a/src/app/committees/[shortName]/members/page.tsx +++ b/src/app/committees/[shortName]/members/page.tsx @@ -1,6 +1,6 @@ import styles from './page.module.scss' import { unwrapActionReturn } from '@/app/redirectToErrorPage' -import { readCommitteeMembersAction } from '@/actions/groups/committees/read' +import { readCommitteeMembersAction } from '@/services/groups/committees/actions' import UserCard from '@/components/User/UserCard' import type { PropTypes } from '@/app/committees/[shortName]/page' diff --git a/src/app/committees/[shortName]/page.tsx b/src/app/committees/[shortName]/page.tsx index 044601693..99bc321ca 100644 --- a/src/app/committees/[shortName]/page.tsx +++ b/src/app/committees/[shortName]/page.tsx @@ -1,5 +1,5 @@ import styles from './page.module.scss' -import { readCommitteeMembersAction, readCommitteeParagraphAction } from '@/actions/groups/committees/read' +import { readCommitteeMembersAction, readCommitteeParagraphAction } from '@/services/groups/committees/actions' import { unwrapActionReturn } from '@/app/redirectToErrorPage' import CmsParagraph from '@/components/Cms/CmsParagraph/CmsParagraph' import UserCard from '@/components/User/UserCard' diff --git a/src/app/committees/page.tsx b/src/app/committees/page.tsx index 0174a133c..1a10c1aaf 100644 --- a/src/app/committees/page.tsx +++ b/src/app/committees/page.tsx @@ -1,7 +1,7 @@ import styles from './page.module.scss' import CommitteeCard from '@/components/CommitteeCard/CommitteeCard' -import { readCommitteesAction } from '@/actions/groups/committees/read' -import { readSpecialImageAction } from '@/actions/images/read' +import { readCommitteesAction } from '@/services/groups/committees/actions' +import { readSpecialImageAction } from '@/services/images/actions' export default async function Committees() { const res = await readCommitteesAction() diff --git a/src/app/education/courses/page.tsx b/src/app/education/courses/page.tsx index dd41c093c..466dbcd90 100644 --- a/src/app/education/courses/page.tsx +++ b/src/app/education/courses/page.tsx @@ -2,7 +2,7 @@ import { AddHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' import PageWrapper from '@/components/PageWrapper/PageWrapper' import TextInput from '@/components/UI/TextInput' import Form from '@/components/Form/Form' -import { createCourseAction } from '@/education/courses/create' +import { createCourseAction } from '@/education/courses/actions' export default function Courses() { return ( diff --git a/src/app/education/page.tsx b/src/app/education/page.tsx index a1659cd69..d9c024d46 100644 --- a/src/app/education/page.tsx +++ b/src/app/education/page.tsx @@ -1,5 +1,5 @@ import styles from './page.module.scss' -import { readSpecialImageAction } from '@/actions/images/read' +import { readSpecialImageAction } from '@/services/images/actions' import PageWrapper from '@/components/PageWrapper/PageWrapper' import ImageCard from '@/components/ImageCard/ImageCard' diff --git a/src/app/education/schools/page.tsx b/src/app/education/schools/page.tsx index dd6ac0d92..fdee5ad2c 100644 --- a/src/app/education/schools/page.tsx +++ b/src/app/education/schools/page.tsx @@ -1,8 +1,8 @@ import styles from './page.module.scss' -import { readSchoolsPageAction } from '@/actions/education/schools/read' -import { getUser } from '@/auth/getUser' +import { readSchoolsPageAction } from '@/services/education/schools/actions' +import { getUser } from '@/auth/session/getUser' import PageWrapper from '@/components/PageWrapper/PageWrapper' -import SchoolPagingProvider from '@/contexts/paging/SchoolPaging' +import { SchoolPagingProvider } from '@/contexts/paging/SchoolPaging' import SchoolList from '@/components/School/SchoolList' import { schoolListRenderer } from '@/components/School/SchoolListRenderer' import Link from 'next/link' diff --git a/src/app/events/CreateOrUpdateEventForm.tsx b/src/app/events/CreateOrUpdateEventForm.tsx index dcd01aae5..658d3144b 100644 --- a/src/app/events/CreateOrUpdateEventForm.tsx +++ b/src/app/events/CreateOrUpdateEventForm.tsx @@ -7,12 +7,11 @@ import Slider from '@/components/UI/Slider' import NumberInput from '@/components/UI/NumberInput' import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' -import { EventConfig } from '@/services/events/config' -import { updateEventAction } from '@/actions/events/update' -import { createEventAction } from '@/actions/events/create' import EventTag from '@/components/Event/EventTag' -import { FIELD_IS_PRESENT_VALUE } from '@/lib/fields/config' -import { configureAction } from '@/actions/configureAction' +import { createEventAction, updateEventAction } from '@/services/events/actions' +import { eventCanBeViewdByOptions } from '@/services/events/constants' +import { FIELD_IS_PRESENT_VALUE } from '@/lib/fields/constants' +import { configureAction } from '@/services/configureAction' import { useState } from 'react' import type { Event, EventTag as EventTagT } from '@prisma/client' import type { ChangeEvent } from 'react' @@ -55,7 +54,7 @@ export default function CreateOrUpdateEventForm({ event, eventTags }: PropTypes) className={styles.canBeViewdBy} label="Hvem kan se" name="canBeViewdBy" - options={EventConfig.canBeViewdByOptions} + options={eventCanBeViewdByOptions} defaultValue={event?.canBeViewdBy} /> diff --git a/src/app/events/EventsLandingLayout.tsx b/src/app/events/EventsLandingLayout.tsx index 3aaaa822d..e562edd04 100644 --- a/src/app/events/EventsLandingLayout.tsx +++ b/src/app/events/EventsLandingLayout.tsx @@ -1,6 +1,6 @@ import styles from './EventsLandingLayout.module.scss' import EventTag from '@/components/Event/EventTag' -import { QueryParams } from '@/lib/query-params/queryParams' +import { QueryParams } from '@/lib/queryParams/queryParams' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import Link from 'next/link' import type { EventTag as EventTagT } from '@prisma/client' diff --git a/src/app/events/[order]/[name]/ManualRegistrationForm.tsx b/src/app/events/[order]/[name]/ManualRegistrationForm.tsx index 25d76288b..18366378c 100644 --- a/src/app/events/[order]/[name]/ManualRegistrationForm.tsx +++ b/src/app/events/[order]/[name]/ManualRegistrationForm.tsx @@ -1,15 +1,15 @@ 'use client' import styles from './ManualRegistrationForm.module.scss' -import { createEventRegistrationAction, createGuestEventRegistrationAction } from '@/actions/events/registration' import Form from '@/components/Form/Form' import UserList from '@/components/User/UserList/UserList' -import UserPagingProvider from '@/contexts/paging/UserPaging' +import { UserPagingProvider } from '@/contexts/paging/UserPaging' import UserSelectionProvider, { UserSelectionContext } from '@/contexts/UserSelection' import TextInput from '@/components/UI/TextInput' -import { configureAction } from '@/actions/configureAction' +import { createEventRegistrationAction, createGuestEventRegistrationAction } from '@/services/events/registration/actions' +import { configureAction } from '@/services/configureAction' import { useContext } from 'react' import type { EventRegistration } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' +import type { ActionReturn } from '@/services/actionTypes' function ManualRegistrationFormInner({ eventId, diff --git a/src/app/events/[order]/[name]/RegistrationUI.tsx b/src/app/events/[order]/[name]/RegistrationUI.tsx index 17dfe3041..b4f13369d 100644 --- a/src/app/events/[order]/[name]/RegistrationUI.tsx +++ b/src/app/events/[order]/[name]/RegistrationUI.tsx @@ -1,18 +1,18 @@ 'use client' import styles from './RegistrationUI.module.scss' -import { - createEventRegistrationAction, - eventRegistrationDestroyAction, - eventRegistrationUpdateNotesAction -} from '@/actions/events/registration' import CountDown from '@/components/countDown/CountDown' import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' import SubmitButton from '@/components/UI/SubmitButton' -import { configureAction } from '@/actions/configureAction' +import { + createEventRegistrationAction, + eventRegistrationDestroyAction, + eventRegistrationUpdateNotesAction +} from '@/services/events/registration/actions' +import { configureAction } from '@/services/configureAction' import { useEffect, useState } from 'react' import { useSession } from 'next-auth/react' -import type { EventExpanded } from '@/services/events/Types' +import type { EventExpanded } from '@/services/events/types' import type { EventRegistration } from '@prisma/client' enum RegistrationButtonState { diff --git a/src/app/events/[order]/[name]/RegistrationsList.tsx b/src/app/events/[order]/[name]/RegistrationsList.tsx index 97e151d25..b6aa16c99 100644 --- a/src/app/events/[order]/[name]/RegistrationsList.tsx +++ b/src/app/events/[order]/[name]/RegistrationsList.tsx @@ -1,28 +1,29 @@ 'use client' import styles from './RegistrationsList.module.scss' import EndlessScroll from '@/components/PagingWrappers/EndlessScroll' -import EventRegistrationPagingProvider, { EventRegistrationPagingContext } from '@/contexts/paging/EventRegistrationPaging' +import { EventRegistrationPagingProvider, EventRegistrationPagingContext } from '@/contexts/paging/EventRegistrationPaging' import UserCard from '@/components/User/UserCard' -import EventRegistrationDetailedPagingProvider, { - EventRegistrationDetailedPagingContext +import { + EventRegistrationDetailedPagingProvider, + EventRegistrationDetailedPagingContext, } from '@/contexts/paging/EventRegistrationDetailedPaging' import UserDisplayName from '@/components/User/UserDisplayName' import Slider from '@/components/UI/Slider' import Form from '@/components/Form/Form' -import { eventRegistrationDestroyAction } from '@/actions/events/registration' -import { EventRegistrationConfig } from '@/services/events/registration/config' import ContactCard from '@/components/User/ContactCard' -import { configureAction } from '@/actions/configureAction' +import { eventRegistrationDestroyAction } from '@/services/events/registration/actions' +import { REGISTRATION_READER_TYPE } from '@/services/events/registration/constants' +import { configureAction } from '@/services/configureAction' import Link from 'next/link' import { useState } from 'react' -import type { EventFiltered } from '@/services/events/Types' +import type { EventFiltered } from '@/services/events/types' function DetailedTable({ event, type, }: { event: EventFiltered, - type: EventRegistrationConfig.REGISTRATION_READER_TYPE + type: REGISTRATION_READER_TYPE, }) { return } {detailedView ? : } {event.waitingList && <>

    Venteliste

    {detailedView ? : } } diff --git a/src/app/events/[order]/[name]/ShowAndEditName.tsx b/src/app/events/[order]/[name]/ShowAndEditName.tsx index b8cd68ac3..ff4b37030 100644 --- a/src/app/events/[order]/[name]/ShowAndEditName.tsx +++ b/src/app/events/[order]/[name]/ShowAndEditName.tsx @@ -1,8 +1,8 @@ 'use client' import styles from './ShowAndEditName.module.scss' import EditableTextField from '@/components/EditableTextField/EditableTextField' -import { updateEventAction } from '@/actions/events/update' -import { configureAction } from '@/actions/configureAction' +import { updateEventAction } from '@/services/events/actions' +import { configureAction } from '@/services/configureAction' import type { Event } from '@prisma/client' type PropTypes = { diff --git a/src/app/events/[order]/[name]/page.tsx b/src/app/events/[order]/[name]/page.tsx index 10e3e1b79..8b87e9118 100644 --- a/src/app/events/[order]/[name]/page.tsx +++ b/src/app/events/[order]/[name]/page.tsx @@ -5,17 +5,16 @@ import RegistrationsList from './RegistrationsList' import ManualRegistrationForm from './ManualRegistrationForm' import Date from '@/components/Date/Date' import CreateOrUpdateEventForm from '@/app/events/CreateOrUpdateEventForm' -import { readEventAction } from '@/actions/events/read' import CmsImage from '@/components/Cms/CmsImage/CmsImage' import CmsParagraph from '@/components/Cms/CmsParagraph/CmsParagraph' import Form from '@/components/Form/Form' import EventTag from '@/components/Event/EventTag' -import { destroyEventAction } from '@/actions/events/destroy' import { SettingsHeaderItemPopUp, UsersHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' -import { readEventTagsAction } from '@/actions/events/tags/read' -import { QueryParams } from '@/lib/query-params/queryParams' +import { QueryParams } from '@/lib/queryParams/queryParams' import { unwrapActionReturn } from '@/app/redirectToErrorPage' -import { configureAction } from '@/actions/configureAction' +import { readEventTagsAction } from '@/services/events/tags/actions' +import { destroyEventAction, readEventAction } from '@/services/events/actions' +import { configureAction } from '@/services/configureAction' import Link from 'next/link' import { faCalendar, faExclamation, faLocationDot, faUsers } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' diff --git a/src/app/events/archive/page.tsx b/src/app/events/archive/page.tsx index f7d7efc61..5e6cc3dd6 100644 --- a/src/app/events/archive/page.tsx +++ b/src/app/events/archive/page.tsx @@ -1,13 +1,13 @@ import EventArchiveList from './EventArchiveList' import TagHeaderItem from '@/app/events/TagHeaderItem' -import { readEventTagsAction } from '@/actions/events/tags/read' +import { readEventTagsAction } from '@/services/events/tags/actions' import EventsLandingLayout from '@/app/events/EventsLandingLayout' -import EventArchivePagingProvider from '@/contexts/paging/EventArchivePaging' -import { QueryParams } from '@/lib/query-params/queryParams' -import { EventTagAuthers } from '@/services/events/tags/authers' -import { Session } from '@/auth/Session' +import { EventArchivePagingProvider } from '@/contexts/paging/EventArchivePaging' +import { QueryParams } from '@/lib/queryParams/queryParams' +import { eventTagAuth } from '@/services/events/tags/auth' +import { Session } from '@/auth/session/Session' import { faArrowLeft } from '@fortawesome/free-solid-svg-icons' -import type { SearchParamsServerSide } from '@/lib/query-params/Types' +import type { SearchParamsServerSide } from '@/lib/queryParams/types' type PropTypes = SearchParamsServerSide @@ -24,9 +24,9 @@ export default async function EventArchive({ const session = await Session.fromNextAuth() - const canUpdate = EventTagAuthers.update.dynamicFields({}).auth(session) - const canCreate = EventTagAuthers.create.dynamicFields({}).auth(session) - const canDestroy = EventTagAuthers.destroy.dynamicFields({}).auth(session) + const canUpdate = eventTagAuth.update.dynamicFields({}).auth(session) + const canCreate = eventTagAuth.create.dynamicFields({}).auth(session) + const canDestroy = eventTagAuth.destroy.dynamicFields({}).auth(session) return ( { // split files into batches of maxNumberOfImagesInOneBatch const batches = files.reduce((acc, file, index) => { - if (index % ImageConfig.maxNumberInOneBatch === 0) { + if (index % maxImageCountInOneBatch === 0) { acc.push([]) } acc[acc.length - 1].push(file) diff --git a/src/app/images/collections/[id]/page.tsx b/src/app/images/collections/[id]/page.tsx index c01ad46c9..c11c4c491 100644 --- a/src/app/images/collections/[id]/page.tsx +++ b/src/app/images/collections/[id]/page.tsx @@ -1,11 +1,11 @@ import styles from './page.module.scss' import CollectionAdmin from './CollectionAdmin' -import { readImagesPageAction } from '@/actions/images/read' -import { readImageCollectionAction } from '@/actions/images/collections/read' import ImageList from '@/components/Image/ImageList/ImageList' -import ImagePagingProvider from '@/contexts/paging/ImagePaging' +import { ImagePagingProvider } from '@/contexts/paging/ImagePaging' import ImageListImage from '@/components/Image/ImageList/ImageListImage' import ImageDisplayProvider from '@/contexts/ImageDisplayProvider' +import { readImageCollectionAction } from '@/services/images/collections/actions' +import { readImagesPageAction } from '@/services/images/actions' import { notFound } from 'next/navigation' import type { PageSizeImage } from '@/contexts/paging/ImagePaging' diff --git a/src/app/images/page.tsx b/src/app/images/page.tsx index fd8507e46..a2c51e127 100644 --- a/src/app/images/page.tsx +++ b/src/app/images/page.tsx @@ -1,10 +1,10 @@ import styles from './page.module.scss' import MakeNewCollection from './MakeNewCollection' import ImageCollectionList from '@/components/Image/Collection/ImageCollectionList' -import { readImageCollectionsPageAction } from '@/actions/images/collections/read' -import ImageCollectionPagingProvider from '@/contexts/paging/ImageCollectionPaging' +import { ImageCollectionPagingProvider } from '@/contexts/paging/ImageCollectionPaging' import CollectionCard from '@/components/Image/Collection/CollectionCard' -import { getUser } from '@/auth/getUser' +import { getUser } from '@/auth/session/getUser' +import { readImageCollectionsPageAction } from '@/services/images/collections/actions' import type { PageSizeImageCollection } from '@/contexts/paging/ImageCollectionPaging' export default async function Images() { diff --git a/src/app/interest-groups/CreateInterestGroupForm.tsx b/src/app/interest-groups/CreateInterestGroupForm.tsx index 356738d80..4b936d2fb 100644 --- a/src/app/interest-groups/CreateInterestGroupForm.tsx +++ b/src/app/interest-groups/CreateInterestGroupForm.tsx @@ -1,6 +1,6 @@ import styles from './CreateInterestGroupForm.module.scss' import Form from '@/components/Form/Form' -import { createInterestGroupAction } from '@/actions/groups/interestGroups/create' +import { createInterestGroupAction } from '@/services/groups/interestGroups/actions' import TextInput from '@/components/UI/TextInput' export default function CreateInterestGroupForm() { diff --git a/src/app/interest-groups/InterestGroup.tsx b/src/app/interest-groups/InterestGroup.tsx index d59b5deff..e58e29e66 100644 --- a/src/app/interest-groups/InterestGroup.tsx +++ b/src/app/interest-groups/InterestGroup.tsx @@ -3,11 +3,10 @@ import Form from '@/components/Form/Form' import TextInput from '@/components/UI/TextInput' import ArticleSection from '@/components/Cms/ArticleSection/ArticleSection' import { SettingsHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' -import { updateInterestGroupAction } from '@/actions/groups/interestGroups/update' -import { destroyInterestGroupAction } from '@/actions/groups/interestGroups/destroy' -import { InterestGroupAuthers } from '@/services/groups/interestGroups/authers' -import type { SessionMaybeUser } from '@/auth/Session' -import type { ExpandedInterestGroup } from '@/services/groups/interestGroups/Types' +import { updateInterestGroupAction, destroyInterestGroupAction } from '@/services/groups/interestGroups/actions' +import { interestGroupAuth } from '@/services/groups/interestGroups/auth' +import type { SessionMaybeUser } from '@/auth/session/Session' +import type { ExpandedInterestGroup } from '@/services/groups/interestGroups/types' type PropTypes = { interestGroup: ExpandedInterestGroup @@ -15,8 +14,8 @@ type PropTypes = { } export default function InterestGroup({ interestGroup, session }: PropTypes) { - const canUpdate = InterestGroupAuthers.update.dynamicFields({ groupId: interestGroup.groupId }).auth(session) - const canDestroy = InterestGroupAuthers.destroy.dynamicFields({}).auth(session) + const canUpdate = interestGroupAuth.update.dynamicFields({ groupId: interestGroup.groupId }).auth(session) + const canDestroy = interestGroupAuth.destroy.dynamicFields({}).auth(session) const PopUpKey = `Update interest group ${interestGroup.name}` diff --git a/src/app/interest-groups/page.tsx b/src/app/interest-groups/page.tsx index fe50ffa3d..76726d238 100644 --- a/src/app/interest-groups/page.tsx +++ b/src/app/interest-groups/page.tsx @@ -1,11 +1,11 @@ import CreateInterestGroupForm from './CreateInterestGroupForm' import InterestGroup from './InterestGroup' -import { readInterestGroupsAction } from '@/actions/groups/interestGroups/read' import SpecialCmsParagraph from '@/cms/CmsParagraph/SpecialCmsParagraph' import PageWrapper from '@/components/PageWrapper/PageWrapper' import { AddHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' -import { Session } from '@/auth/Session' -import { InterestGroupAuthers } from '@/services/groups/interestGroups/authers' +import { Session } from '@/auth/session/Session' +import { interestGroupAuth } from '@/services/groups/interestGroups/auth' +import { readInterestGroupsAction } from '@/services/groups/interestGroups/actions' export default async function InterestGroups() { const session = await Session.fromNextAuth() @@ -13,7 +13,7 @@ export default async function InterestGroups() { if (!interestGroupsRes.success) return
    Failed to load interest groups
    //TODO: Change to unwrap const interestGroups = interestGroupsRes.data - const canCreate = InterestGroupAuthers.create.dynamicFields({}).auth(session) + const canCreate = interestGroupAuth.create.dynamicFields({}).auth(session) return ( (actionReturn: ActionReturn): Data -export function unwrapActionReturn< - Data, ->(actionReturn: ActionReturn): Data | undefined -export function unwrapActionReturn< - Data, - const DataGuarantee extends boolean, ->(actionReturn: ActionReturn): Data | undefined { +>(actionReturn: ActionReturn): Data { if (!actionReturn.success) { redirectToErrorPage(actionReturn.errorCode, actionReturn.error?.length ? actionReturn.error[0].message : undefined) } diff --git a/src/app/users/[username]/(user-admin)/dots/page.tsx b/src/app/users/[username]/(user-admin)/dots/page.tsx index 1f09f5063..3f8b26ff8 100644 --- a/src/app/users/[username]/(user-admin)/dots/page.tsx +++ b/src/app/users/[username]/(user-admin)/dots/page.tsx @@ -1,6 +1,6 @@ import styles from './page.module.scss' -import { readDotWrappersForUserAction } from '@/actions/dots/read' +import { readDotWrappersForUserAction } from '@/services/dots/actions' import { unwrapActionReturn } from '@/app/redirectToErrorPage' import { getProfileForAdmin } from '@/app/users/[username]/(user-admin)/getProfileForAdmin' import Date from '@/components/Date/Date' diff --git a/src/app/users/[username]/(user-admin)/getProfileForAdmin.ts b/src/app/users/[username]/(user-admin)/getProfileForAdmin.ts index b4dffcdc2..a62fd6a13 100644 --- a/src/app/users/[username]/(user-admin)/getProfileForAdmin.ts +++ b/src/app/users/[username]/(user-admin)/getProfileForAdmin.ts @@ -1,6 +1,6 @@ -import { readUserProfileAction } from '@/actions/users/read' -import { Session } from '@/auth/Session' -import { UserAuthers } from '@/services/users/authers' +import { readUserProfileAction } from '@/services/users/actions' +import { Session } from '@/auth/session/Session' +import { userAuth } from '@/services/users/auth' import { notFound, redirect } from 'next/navigation' type Params = { @@ -20,7 +20,7 @@ export async function getProfileForAdmin({ username }: Params, adminPage: string if (!session.user) return notFound() redirect(`/users/${session.user.username}/${adminPage}`) //This throws. } - UserAuthers.updateProfile + userAuth.updateProfile .dynamicFields({ username }) .auth(session) .requireAuthorized({ returnUrlIfFail: `/users/${username}/${adminPage}` }) diff --git a/src/app/users/[username]/(user-admin)/layout.tsx b/src/app/users/[username]/(user-admin)/layout.tsx index a6088a024..0e39e9b07 100644 --- a/src/app/users/[username]/(user-admin)/layout.tsx +++ b/src/app/users/[username]/(user-admin)/layout.tsx @@ -1,7 +1,7 @@ import styles from './layout.module.scss' import Nav from './Nav' -import { Session } from '@/auth/Session' -import { readUserProfileAction } from '@/actions/users/read' +import { Session } from '@/auth/session/Session' +import { readUserProfileAction } from '@/services/users/actions' import { unwrapActionReturn } from '@/app/redirectToErrorPage' import PageWrapper from '@/components/PageWrapper/PageWrapper' import { notFound } from 'next/navigation' diff --git a/src/app/users/[username]/(user-admin)/notifications/notificationSettings.tsx b/src/app/users/[username]/(user-admin)/notifications/notificationSettings.tsx index 536a07bb6..69b929046 100644 --- a/src/app/users/[username]/(user-admin)/notifications/notificationSettings.tsx +++ b/src/app/users/[username]/(user-admin)/notifications/notificationSettings.tsx @@ -4,20 +4,20 @@ import SubscriptionItem from './subscriptionItem' import styles from './notificationSettings.module.scss' import { booleanOperationOnMethods, newAllMethodsOff } from '@/services/notifications/notificationMethodOperations' import SubmitButton from '@/components/UI/SubmitButton' -import { SUCCESS_FEEDBACK_TIME } from '@/components/Form/ConfigVars' -import { updateNotificationSubscriptionsAction } from '@/actions/notifications' -import { NotificationConfig } from '@/services/notifications/config' +import { SUCCESS_FEEDBACK_TIME } from '@/components/Form/constants' +import { updateNotificationSubscriptionsAction } from '@/services/notifications/actions' +import { notificationMethodsArray, notificationMethodsDisplayMap } from '@/services/notifications/constants' import { v4 as uuid } from 'uuid' import { useState } from 'react' -import type { UserFiltered } from '@/services/users/Types' -import type { MinimizedSubscription, Subscription } from '@/services/notifications/subscription/Types' -import type { NotificationBranch } from './Types' +import type { UserFiltered } from '@/services/users/types' +import type { MinimizedSubscription, Subscription } from '@/services/notifications/subscription/types' +import type { NotificationBranch } from './types' import type { ErrorMessage } from '@/services/error' import type { ExpandedNotificationChannel, NotificationMethodGeneral, NotificationMethods -} from '@/services/notifications/Types' +} from '@/services/notifications/types' function generateChannelTree(channels: ExpandedNotificationChannel[], subscriptions: Subscription[]): NotificationBranch { const rootChannel = channels.find(channel => channel.special === 'ROOT') @@ -229,12 +229,12 @@ export default function NotificationSettings({
  • - {NotificationConfig.methods.map(method => + {notificationMethodsArray.map(method => )} diff --git a/src/app/users/[username]/(user-admin)/notifications/page.tsx b/src/app/users/[username]/(user-admin)/notifications/page.tsx index 8d84834de..a23ff4170 100644 --- a/src/app/users/[username]/(user-admin)/notifications/page.tsx +++ b/src/app/users/[username]/(user-admin)/notifications/page.tsx @@ -1,7 +1,7 @@ 'use server' import NotificationSettings from './notificationSettings' -import { readNotificationChannelsAction, readNotificationSubscriptionsAction } from '@/actions/notifications' import { getProfileForAdmin } from '@/app/users/[username]/(user-admin)/getProfileForAdmin' +import { readNotificationChannelsAction, readNotificationSubscriptionsAction } from '@/services/notifications/actions' import type { PropTypes } from '@/app/users/[username]/page' export default async function Notififcations({ params }: PropTypes) { diff --git a/src/app/users/[username]/(user-admin)/notifications/subscriptionItem.tsx b/src/app/users/[username]/(user-admin)/notifications/subscriptionItem.tsx index 68534ae30..1ead0b1d7 100644 --- a/src/app/users/[username]/(user-admin)/notifications/subscriptionItem.tsx +++ b/src/app/users/[username]/(user-admin)/notifications/subscriptionItem.tsx @@ -2,11 +2,11 @@ import styles from './subscriptionItem.module.scss' import NotificationMethodCheckboxes from '@/components/NotificaionMethodSelector/NotificationMethodCheckboxes' -import { NotificationConfig } from '@/services/notifications/config' +import { allNotificationMethodsOn } from '@/services/notifications/constants' import { v4 as uuid } from 'uuid' import React from 'react' -import type { NotificationMethodGeneral } from '@/services/notifications/Types' -import type { NotificationBranch } from './Types' +import type { NotificationMethodGeneral } from '@/services/notifications/types' +import type { NotificationBranch } from './types' export default function SubscriptionItem({ @@ -19,7 +19,7 @@ export default function SubscriptionItem({ onChange?: (branchId: number, method: NotificationMethodGeneral) => void }) { const checkboxes = NotificationMethodCheckboxes({ - methods: branch.subscription?.methods ?? NotificationConfig.allMethodsOff, + methods: branch.subscription?.methods ?? allNotificationMethodsOn, editable: branch.availableMethods, onChange: (method: NotificationMethodGeneral) => { if (!onChange) { diff --git a/src/app/users/[username]/(user-admin)/notifications/Types.ts b/src/app/users/[username]/(user-admin)/notifications/types.ts similarity index 84% rename from src/app/users/[username]/(user-admin)/notifications/Types.ts rename to src/app/users/[username]/(user-admin)/notifications/types.ts index d4e56c9a3..249c3a77c 100644 --- a/src/app/users/[username]/(user-admin)/notifications/Types.ts +++ b/src/app/users/[username]/(user-admin)/notifications/types.ts @@ -1,5 +1,5 @@ -import type { ExpandedNotificationChannel, NotificationMethodGeneral } from '@/services/notifications/Types' -import type { Subscription } from '@/services/notifications/subscription/Types' +import type { ExpandedNotificationChannel, NotificationMethodGeneral } from '@/services/notifications/types' +import type { Subscription } from '@/services/notifications/subscription/types' export type NotificationBranch = ExpandedNotificationChannel & { diff --git a/src/app/users/[username]/(user-admin)/settings/RegisterStudentCardButton.tsx b/src/app/users/[username]/(user-admin)/settings/RegisterStudentCardButton.tsx index f1569a2fa..ca098eb37 100644 --- a/src/app/users/[username]/(user-admin)/settings/RegisterStudentCardButton.tsx +++ b/src/app/users/[username]/(user-admin)/settings/RegisterStudentCardButton.tsx @@ -1,8 +1,8 @@ 'use client' -import { configureAction } from '@/actions/configureAction' -import { registerStudentCardInQueueAction } from '@/actions/users/update' +import { registerStudentCardInQueueAction } from '@/services/users/actions' +import { configureAction } from '@/services/configureAction' import Form from '@/app/_components/Form/Form' -import { UserConfig } from '@/services/users/config' +import { studentCardRegistrationExpiry } from '@/services/users/constants' export default function RegisterStudentCardButton({ @@ -17,7 +17,7 @@ export default function RegisterStudentCardButton({

    For å registrere studentkortet til brukeren din trykk på knappen under. Deretter skan kortet ditt på skanneren til Koigeskabet på Lophtet, uten at du har lagt inn noen varer enda. - Fra du trykker på knappen har du { UserConfig.studentCardRegistrationExpiry } + Fra du trykker på knappen har du { studentCardRegistrationExpiry } minutter på deg til å skanne kortet ditt.

    diff --git a/src/app/users/[username]/page.tsx b/src/app/users/[username]/page.tsx index 132c9df33..d3e000b51 100644 --- a/src/app/users/[username]/page.tsx +++ b/src/app/users/[username]/page.tsx @@ -1,15 +1,15 @@ import styles from './page.module.scss' -import { readSpecialImageAction } from '@/actions/images/read' import BorderButton from '@/components/UI/BorderButton' import { readCommitteesFromGroupIds } from '@/services/groups/committees/read' -import { readUserProfileAction } from '@/actions/users/read' import OmegaId from '@/components/OmegaId/identification/OmegaId' import PopUp from '@/components/PopUp/PopUp' -import { Session } from '@/auth/Session' -import { UserAuthers } from '@/services/users/authers' +import { Session } from '@/auth/session/Session' +import { userAuth } from '@/services/users/auth' import ProfilePicture from '@/components/User/ProfilePicture' -import { UserConfig } from '@/services/users/config' import UserDisplayName from '@/components/User/UserDisplayName' +import { readUserProfileAction } from '@/services/users/actions' +import { readSpecialImageAction } from '@/services/images/actions' +import { sexConfig } from '@/services/users/constants' import Link from 'next/link' import { notFound, redirect } from 'next/navigation' import { v4 as uuid } from 'uuid' @@ -49,7 +49,7 @@ export default async function User({ params }: PropTypes) { return res.data }) - const { authorized: canAdministrate } = UserAuthers.updateProfile.dynamicFields( + const { authorized: canAdministrate } = userAuth.updateProfile.dynamicFields( { username: profile.user.username } ).auth(session) @@ -96,7 +96,7 @@ export default async function User({ params }: PropTypes) {

    - {UserConfig.sexConfig[profile.user.sex ?? 'OTHER'].title} + {sexConfig[profile.user.sex ?? 'OTHER'].title} uudaf {order}´dis orden i Sanctus Omega Broderskab

    diff --git a/src/app/users/page.tsx b/src/app/users/page.tsx index 4e3269fef..7e038eb35 100644 --- a/src/app/users/page.tsx +++ b/src/app/users/page.tsx @@ -3,7 +3,7 @@ import { AddHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' import PageWrapper from '@/components/PageWrapper/PageWrapper' import UserList from '@/components/User/UserList/UserList' import CreateUserForm from '@/components/User/CreateUserForm' -import UserPagingProvider from '@/contexts/paging/UserPaging' +import { UserPagingProvider } from '@/contexts/paging/UserPaging' export default async function Users() { return ( diff --git a/src/auth/auther/AuthResult.ts b/src/auth/auther/AuthResult.ts index a714b3346..4cbab879e 100644 --- a/src/auth/auther/AuthResult.ts +++ b/src/auth/auther/AuthResult.ts @@ -1,6 +1,6 @@ import { redirectToErrorPage } from '@/app/redirectToErrorPage' import { redirect } from 'next/navigation' -import type { SessionType, UserGuaranteeOption } from '@/auth/Session' +import type { SessionType, UserGuaranteeOption } from '@/auth/session/Session' export type AuthStatus = 'AUTHORIZED' | 'UNAUTHORIZED' | 'AUTHORIZED_NO_USER' | 'UNAUTHENTICATED' diff --git a/src/auth/auther/Auther.ts b/src/auth/auther/Auther.ts index 9d6946813..b2e18d47b 100644 --- a/src/auth/auther/Auther.ts +++ b/src/auth/auther/Auther.ts @@ -1,17 +1,21 @@ import { AuthResult } from './AuthResult' -import type { SessionMaybeUser, SessionUser } from '@/auth/Session' +import type { SessionMaybeUser, SessionUser } from '@/auth/session/Session' export type UserRequieredOutOpt = 'USER_NOT_REQUIERED_FOR_AUTHORIZED' | 'USER_REQUIERED_FOR_AUTHORIZED' +export type AutherResult< + UserRequieredOut extends UserRequieredOutOpt = 'USER_NOT_REQUIERED_FOR_AUTHORIZED' | 'USER_REQUIERED_FOR_AUTHORIZED', +> = { + auth: (session: SessionMaybeUser) => UserRequieredOut extends 'USER_REQUIERED_FOR_AUTHORIZED' + ? (AuthResult<'HAS_USER', true> | AuthResult<'HAS_USER' | 'NO_USER', false>) + : (AuthResult<'HAS_USER' | 'NO_USER', true> | AuthResult<'HAS_USER' | 'NO_USER', false>) +} + export type AutherStaticFieldsBound< DynamicFields extends object, UserRequieredOut extends UserRequieredOutOpt = 'USER_NOT_REQUIERED_FOR_AUTHORIZED' | 'USER_REQUIERED_FOR_AUTHORIZED', > = { - dynamicFields: (dynamicFields: DynamicFields) => { - auth: (session: SessionMaybeUser) => UserRequieredOut extends 'USER_REQUIERED_FOR_AUTHORIZED' - ? (AuthResult<'HAS_USER', true> | AuthResult<'HAS_USER' | 'NO_USER', false>) - : (AuthResult<'HAS_USER' | 'NO_USER', true> | AuthResult<'HAS_USER' | 'NO_USER', false>) - } + dynamicFields: (dynamicFields: DynamicFields) => AutherResult, } export type Auther< diff --git a/src/auth/auther/RequireJWT.ts b/src/auth/auther/RequireJWT.ts index c34d15c9d..4c688d00e 100644 --- a/src/auth/auther/RequireJWT.ts +++ b/src/auth/auther/RequireJWT.ts @@ -1,7 +1,7 @@ import { AutherFactory } from './Auther' import { verifyJWT } from '@/lib/jwt/jwt' import { ServerError } from '@/services/error' -import type { OmegaJWTAudience } from '@/jwt/Types' +import type { OmegaJWTAudience } from '@/lib/jwt/types' export const RequireJWT = AutherFactory< { audience: OmegaJWTAudience }, diff --git a/src/auth/auther/RequireVisibility.ts b/src/auth/auther/RequireVisibility.ts index 3b4a558c0..86e778925 100644 --- a/src/auth/auther/RequireVisibility.ts +++ b/src/auth/auther/RequireVisibility.ts @@ -1,6 +1,6 @@ import { AutherFactory } from './Auther' import { checkVisibility } from '@/auth/checkVisibility' -import type { VisibilityCollapsed } from '@/services/visibility/Types' +import type { VisibilityCollapsed } from '@/services/visibility/types' import type { Permission } from '@prisma/client' export const RequireVisibility = AutherFactory< diff --git a/src/auth/checkVisibility.ts b/src/auth/checkVisibility.ts index ed77db37d..f0529543d 100644 --- a/src/auth/checkVisibility.ts +++ b/src/auth/checkVisibility.ts @@ -1,7 +1,7 @@ import { BypassPermissions } from '@/services/visibility/ConfigVars' -import type { MembershipFiltered } from '@/services/groups/memberships/Types' +import type { MembershipFiltered } from '@/services/groups/memberships/types' import type { Permission } from '@prisma/client' -import type { GroupMatrix, VisibilityCollapsed, VisibilityLevelType } from '@/services/visibility/Types' +import type { GroupMatrix, VisibilityCollapsed, VisibilityLevelType } from '@/services/visibility/types' type MembershipAndPermission = { memberships: MembershipFiltered[], diff --git a/src/auth/getMembershipFilter.ts b/src/auth/getMembershipFilter.ts index 796f5ce2e..73276e13c 100644 --- a/src/auth/getMembershipFilter.ts +++ b/src/auth/getMembershipFilter.ts @@ -1,4 +1,4 @@ -import type { MembershipSelectorType } from '@/services/groups/memberships/Types' +import type { MembershipSelectorType } from '@/services/groups/memberships/types' import type { Prisma } from '@prisma/client' /** diff --git a/src/auth/getVisibilityFilter.ts b/src/auth/getVisibilityFilter.ts index 07ccef69b..8eec8d04c 100644 --- a/src/auth/getVisibilityFilter.ts +++ b/src/auth/getVisibilityFilter.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { BypassPermissions } from '@/services/visibility/ConfigVars' -import type { MembershipFiltered } from '@/services/groups/memberships/Types' +import type { MembershipFiltered } from '@/services/groups/memberships/types' import type { Permission, Prisma, VisibilityPurpose } from '@prisma/client' function userMayBypassVisibilityBasedOnPermission( diff --git a/src/auth/VevenAdapter.ts b/src/auth/nextAuth/VevenAdapter.ts similarity index 92% rename from src/auth/VevenAdapter.ts rename to src/auth/nextAuth/VevenAdapter.ts index 1a1730b5e..0a4c3cf29 100644 --- a/src/auth/VevenAdapter.ts +++ b/src/auth/nextAuth/VevenAdapter.ts @@ -2,9 +2,9 @@ import '@pn-server-only' import { readJWTPayload } from '@/jwt/jwtReadUnsecure' import { createFeideAccount } from '@/services/auth/feideAccounts/create' import { readUserOrNullOfFeideAccount } from '@/services/auth/feideAccounts/read' -import { UserMethods } from '@/services/users/methods' -import { UserConfig } from '@/services/users/config' -import type { UserFiltered } from '@/services/users/Types' +import { userOperations } from '@/services/users/operations' +import { userFilterSelection } from '@/services/users/constants' +import type { UserFiltered } from '@/services/users/types' import type { PrismaClient } from '@prisma/client' import type { Adapter, AdapterUser, AdapterAccount } from 'next-auth/adapters' @@ -96,7 +96,7 @@ export default function VevenAdapter(prisma: PrismaClient): Adapter { username, emailVerified: null, }, - select: UserConfig.filterSelection, + select: userFilterSelection, }) return convertToAdapterUser(createdUser) @@ -105,9 +105,8 @@ export default function VevenAdapter(prisma: PrismaClient): Adapter { async getUser(id) { console.log('get id') - const user = await UserMethods.readOrNull.newClient().execute({ + const user = await userOperations.readOrNull({ params: { id: Number(id) }, - session: null, bypassAuth: true, }) @@ -117,9 +116,8 @@ export default function VevenAdapter(prisma: PrismaClient): Adapter { async getUserByEmail(email) { console.log('get email') console.log(email) - const user = await UserMethods.readOrNull.newClient().execute({ + const user = await userOperations.readOrNull({ params: { email }, - session: null, bypassAuth: true, }) @@ -133,7 +131,7 @@ export default function VevenAdapter(prisma: PrismaClient): Adapter { }, include: { user: { - select: UserConfig.filterSelection, + select: userFilterSelection, }, }, }) @@ -165,7 +163,7 @@ export default function VevenAdapter(prisma: PrismaClient): Adapter { firstname: user.firstname, lastname: user.lastname, }, - select: UserConfig.filterSelection, + select: userFilterSelection, }) return convertToAdapterUser(updatedUser) diff --git a/src/auth/authoptions.ts b/src/auth/nextAuth/authOptions.ts similarity index 92% rename from src/auth/authoptions.ts rename to src/auth/nextAuth/authOptions.ts index f1f97ecf1..8ac32c787 100644 --- a/src/auth/authoptions.ts +++ b/src/auth/nextAuth/authOptions.ts @@ -1,13 +1,13 @@ import '@pn-server-only' import VevenAdapter from './VevenAdapter' -import { decryptAndComparePassword } from './password' +import { decryptAndComparePassword } from '@/auth/passwordHash' import FeideProvider from '@/lib/feide/FeideProvider' import { updateUserStudyProgrammes } from '@/lib/feide/userRoutines' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { readMembershipsOfUser } from '@/services/groups/memberships/read' import { updateEmailForFeideAccount } from '@/services/auth/feideAccounts/update' -import { UserMethods } from '@/services/users/methods' -import { PermissionMethods } from '@/services/permissions/methods' +import { userOperations } from '@/services/users/operations' +import { permissionOperations } from '@/services/permissions/operations' import CredentialsProvider from 'next-auth/providers/credentials' import { decode } from 'next-auth/jwt' import type { AuthOptions } from 'next-auth' @@ -127,9 +127,8 @@ export const authOptions: AuthOptions = { } // Trigger is undefined for subsequent calls case undefined: { - const dbUser = await UserMethods.read.newClient().execute({ + const dbUser = await userOperations.read({ params: { id: token.user.id }, - session: null, bypassAuth: true, }) @@ -165,14 +164,12 @@ export const authOptions: AuthOptions = { return { provider, - user: await UserMethods.read.newClient().execute({ + user: await userOperations.read({ params: { id: userId }, - session: null, bypassAuth: true, }), - permissions: await PermissionMethods.readPermissionsOfUser.newClient().execute({ + permissions: await permissionOperations.readPermissionsOfUser({ bypassAuth: true, - session: null, params: { userId, } diff --git a/src/auth/password.ts b/src/auth/passwordHash.ts similarity index 100% rename from src/auth/password.ts rename to src/auth/passwordHash.ts diff --git a/src/auth/Session.ts b/src/auth/session/Session.ts similarity index 83% rename from src/auth/Session.ts rename to src/auth/session/Session.ts index 6f5919bf0..ad85ab956 100644 --- a/src/auth/Session.ts +++ b/src/auth/session/Session.ts @@ -1,13 +1,13 @@ -import { authOptions } from './authoptions' -import { ApiKeyMethods } from '@/services/api-keys/methods' -import { apiKeyDecryptAndCompare } from '@/services/api-keys/hashEncryptKey' -import { decodeApiKey } from '@/services/api-keys/apiKeyEncoder' +import { authOptions } from '@/auth/nextAuth/authOptions' +import { apiKeyOperations } from '@/services/apiKeys/operations' +import { apiKeyDecryptAndCompare } from '@/services/apiKeys/hashEncryptKey' +import { decodeApiKey } from '@/services/apiKeys/apiKeyEncoder' import { ServerError } from '@/services/error' -import { PermissionMethods } from '@/services/permissions/methods' +import { permissionOperations } from '@/services/permissions/operations' import { getServerSession as getSessionNextAuth } from 'next-auth' import type { Permission } from '@prisma/client' -import type { UserFiltered } from '@/services/users/Types' -import type { MembershipFiltered } from '@/services/groups/memberships/Types' +import type { UserFiltered } from '@/services/users/types' +import type { MembershipFiltered } from '@/services/groups/memberships/types' export type UserGuaranteeOption = 'HAS_USER' | 'NO_USER' @@ -71,8 +71,7 @@ export class Session { public static async fromNextAuth(): Promise | Session<'HAS_USER'>> { const { user = null, - permissions = await PermissionMethods.readDefaultPermissions.newClient().execute({ - session: null, + permissions = await permissionOperations.readDefaultPermissions({ bypassAuth: true, }), memberships = [], @@ -87,8 +86,7 @@ export class Session { * If the key is null, the session will be cratedwith only default permissios */ public static async fromApiKey(keyAndIdEncoded: string | null): Promise> { - const defaultPermissions = await PermissionMethods.readDefaultPermissions.newClient().execute({ - session: null, + const defaultPermissions = await permissionOperations.readDefaultPermissions({ bypassAuth: true, }) if (!keyAndIdEncoded) return new Session<'NO_USER'>({ user: null, permissions: defaultPermissions, memberships: [] }) @@ -99,8 +97,7 @@ export class Session { let apiKeyFetch try { - apiKeyFetch = await ApiKeyMethods.readWithHash.newClient().execute({ - session: null, + apiKeyFetch = await apiKeyOperations.readWithHash({ bypassAuth: true, params: { id } }) diff --git a/src/auth/getUser.ts b/src/auth/session/getUser.ts similarity index 91% rename from src/auth/getUser.ts rename to src/auth/session/getUser.ts index 8929606c1..aea1d1955 100644 --- a/src/auth/getUser.ts +++ b/src/auth/session/getUser.ts @@ -1,13 +1,13 @@ import '@pn-server-only' -import { authOptions } from './authoptions' -import checkMatrix from '@/utils/checkMatrix' -import { PermissionMethods } from '@/services/permissions/methods' +import { authOptions } from '@/auth/nextAuth/authOptions' +import checkMatrix from '@/lib/checkMatrix' +import { permissionOperations } from '@/services/permissions/operations' import { getServerSession } from 'next-auth' import { notFound, redirect } from 'next/navigation' -import type { Matrix } from '@/utils/checkMatrix' +import type { Matrix } from '@/lib/checkMatrix' import type { Permission } from '@prisma/client' -import type { MembershipFiltered } from '@/services/groups/memberships/Types' -import type { UserFiltered } from '@/services/users/Types' +import type { MembershipFiltered } from '@/services/groups/memberships/types' +import type { UserFiltered } from '@/services/users/types' type GetUserArgsType = { requiredPermissions?: Matrix, @@ -92,8 +92,7 @@ export async function getUser({ }: GetUserArgsType = {}): Promise> { const { user = null, - permissions = await PermissionMethods.readDefaultPermissions.newClient().execute({ - session: null, + permissions = await permissionOperations.readDefaultPermissions({ bypassAuth: true, }), memberships = [], diff --git a/src/auth/useUser.ts b/src/auth/session/useUser.ts similarity index 97% rename from src/auth/useUser.ts rename to src/auth/session/useUser.ts index 130c2254b..a09bc86a0 100644 --- a/src/auth/useUser.ts +++ b/src/auth/session/useUser.ts @@ -1,14 +1,14 @@ 'use client' import { DefaultPermissionsContext } from '@/contexts/DefaultPermissions' -import checkMatrix from '@/utils/checkMatrix' +import checkMatrix from '@/lib/checkMatrix' import { useSession } from 'next-auth/react' import { usePathname, useRouter } from 'next/navigation' import { useContext, useEffect, useState } from 'react' import type { Permission } from '@prisma/client' -import type { UserFiltered } from '@/services/users/Types' -import type { Matrix } from '@/utils/checkMatrix' -import type { MembershipFiltered } from '@/services/groups/memberships/Types' +import type { UserFiltered } from '@/services/users/types' +import type { Matrix } from '@/lib/checkMatrix' +import type { MembershipFiltered } from '@/services/groups/memberships/types' // SessionProvider needs to be exported from a 'use client' file so that it can // be used in a server side file. diff --git a/src/contexts/UserSelection.tsx b/src/contexts/UserSelection.tsx index 340224d7f..6477ac75d 100644 --- a/src/contexts/UserSelection.tsx +++ b/src/contexts/UserSelection.tsx @@ -1,7 +1,7 @@ 'use client' import { createContext, useEffect, useRef, useState } from 'react' import type { ReactNode } from 'react' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' type PropTypes = { children: ReactNode diff --git a/src/contexts/UsersSelection.tsx b/src/contexts/UsersSelection.tsx index e033d5ca1..c84d26d34 100644 --- a/src/contexts/UsersSelection.tsx +++ b/src/contexts/UsersSelection.tsx @@ -2,7 +2,7 @@ import { createContext, useState } from 'react' import type { ReactNode } from 'react' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' type PropTypes = { children: ReactNode diff --git a/src/contexts/groupSelection.tsx b/src/contexts/groupSelection.tsx index 86cae41c6..6e7548cac 100644 --- a/src/contexts/groupSelection.tsx +++ b/src/contexts/groupSelection.tsx @@ -1,7 +1,7 @@ 'use client' import { createContext, useState } from 'react' -import type { ExpandedGroup } from '@/services/groups/Types' +import type { ExpandedGroup } from '@/services/groups/types' import type { ReactNode } from 'react' export const GroupSelectionContext = createContext<{ diff --git a/src/contexts/paging/CompanyPaging.tsx b/src/contexts/paging/CompanyPaging.tsx index 77ee416d8..9f9ad01d4 100644 --- a/src/contexts/paging/CompanyPaging.tsx +++ b/src/contexts/paging/CompanyPaging.tsx @@ -1,24 +1,16 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { readCompanyPageAction } from '@/actions/career/companies/read' -import type { CompanyCursor, CompanyDetails, CompanyExpanded } from '@/services/career/companies/Types' -import type { ReadPageInput } from '@/lib/paging/Types' +import { generatePaging } from './PagingGenerator' +import { readCompanyPageAction } from '@/services/career/companies/actions' +import type { CompanyCursor, CompanyDetails, CompanyExpanded } from '@/services/career/companies/types' export type PageSizeCompany = 10 -const fetcher = async (x: ReadPageInput) => { - const ret = await readCompanyPageAction({ params: { paging: x } }) - return ret -} -export const CompanyPagingContext = generatePagingContext< +export const [CompanyPagingContext, CompanyPagingProvider] = generatePaging< CompanyExpanded, CompanyCursor, PageSizeCompany, CompanyDetails ->() -const CompanyPagingProvider = generatePagingProvider({ - Context: CompanyPagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +>({ + fetcher: async ({ paging }) => await readCompanyPageAction({ params: { paging } }), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default CompanyPagingProvider diff --git a/src/contexts/paging/DotPaging.tsx b/src/contexts/paging/DotPaging.tsx index 8fbf486c2..d2949ac3f 100644 --- a/src/contexts/paging/DotPaging.tsx +++ b/src/contexts/paging/DotPaging.tsx @@ -1,25 +1,17 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' +import { generatePaging } from './PagingGenerator' -import { readDotPageAction } from '@/actions/dots/read' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { DotDetails, DotCursor, DotWrapperWithDots } from '@/services/dots/Types' +import { readDotPageAction } from '@/services/dots/actions' +import type { DotDetails, DotCursor, DotWrapperWithDots } from '@/services/dots/types' export type PageSizeDots = 30 -const fetcher = async (x: ReadPageInput) => { - const ret = await readDotPageAction({ params: { paging: x } }) - return ret -} -export const DotPagingContext = generatePagingContext< +export const [DotPagingContext, DotPagingProvider] = generatePaging< DotWrapperWithDots, DotCursor, PageSizeDots, DotDetails ->() -const DotPagingProvider = generatePagingProvider({ - Context: DotPagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +>({ + fetcher: async ({ paging }) => await readDotPageAction({ params: { paging } }), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default DotPagingProvider diff --git a/src/contexts/paging/EventArchivePaging.tsx b/src/contexts/paging/EventArchivePaging.tsx index fcc64a13e..33e22261d 100644 --- a/src/contexts/paging/EventArchivePaging.tsx +++ b/src/contexts/paging/EventArchivePaging.tsx @@ -1,24 +1,16 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { readArchivedEventsPageAction } from '@/actions/events/read' -import type { EventArchiveCursor, EventArchiveDetails, EventExpanded } from '@/services/events/Types' -import type { ReadPageInput } from '@/lib/paging/Types' +import { generatePaging } from './PagingGenerator' +import { readArchivedEventsPageAction } from '@/services/events/actions' +import type { EventArchiveCursor, EventArchiveDetails, EventExpanded } from '@/services/events/types' export type PageSizeEventArchive = 12 -const fetcher = async (x: ReadPageInput) => { - const ret = await readArchivedEventsPageAction({ params: { paging: x } }) - return ret -} -export const EventArchivePagingContext = generatePagingContext< +export const [EventArchivePagingContext, EventArchivePagingProvider] = generatePaging< EventExpanded, EventArchiveCursor, PageSizeEventArchive, EventArchiveDetails ->() -const EventArchivePagingProvider = generatePagingProvider({ - Context: EventArchivePagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +>({ + fetcher: async ({ paging }) => await readArchivedEventsPageAction({ params: { paging } }), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default EventArchivePagingProvider diff --git a/src/contexts/paging/EventRegistrationDetailedPaging.tsx b/src/contexts/paging/EventRegistrationDetailedPaging.tsx index 87b90e1b2..f36c8fcbf 100644 --- a/src/contexts/paging/EventRegistrationDetailedPaging.tsx +++ b/src/contexts/paging/EventRegistrationDetailedPaging.tsx @@ -1,30 +1,27 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { eventRegistrationReadManyDetailedAction } from '@/actions/events/registration' +import { generatePaging } from './PagingGenerator' +import { eventRegistrationReadManyDetailedAction } from '@/services/events/registration/actions' import type { EventRegistrationDetailedExpanded, EventRegistrationFetcherDetails -} from '@/services/events/registration/Types' +} from '@/services/events/registration/types' import type { PageSizeUsers } from './UserPaging' -import type { ReadPageInput } from '@/lib/paging/Types' -const fetcher = async (x: ReadPageInput) => { - const registrations = await eventRegistrationReadManyDetailedAction({ - params: { - eventId: x.details.eventId, - take: x.page.pageSize, - skip: (x.page.page * x.page.pageSize) || undefined, - type: x.details.type, - } - }) - return registrations -} - -export const EventRegistrationDetailedPagingContext = - generatePagingContext() -const EventRegistrationDetailedPagingProvider = generatePagingProvider({ - Context: EventRegistrationDetailedPagingContext, - fetcher, - getCursorAfterFetch: data => (data.length), +export const [EventRegistrationDetailedPagingContext, EventRegistrationDetailedPagingProvider] = generatePaging< + EventRegistrationDetailedExpanded, + number, + PageSizeUsers, + EventRegistrationFetcherDetails +>({ + fetcher: async ({ paging }) => + // TODO: These calculations should be done inside the function. + await eventRegistrationReadManyDetailedAction({ + params: { + eventId: paging.details.eventId, + take: paging.page.pageSize, + skip: (paging.page.page * paging.page.pageSize) || undefined, + type: paging.details.type, + } + }), + getCursor: ({ fetchedCount }) => fetchedCount, }) -export default EventRegistrationDetailedPagingProvider diff --git a/src/contexts/paging/EventRegistrationPaging.tsx b/src/contexts/paging/EventRegistrationPaging.tsx index 663403ee6..662e3689b 100644 --- a/src/contexts/paging/EventRegistrationPaging.tsx +++ b/src/contexts/paging/EventRegistrationPaging.tsx @@ -1,27 +1,22 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { readManyEventRegistrationAction } from '@/actions/events/registration' -import type { EventRegistrationExpanded, EventRegistrationFetcherDetails } from '@/services/events/registration/Types' +import { generatePaging } from './PagingGenerator' +import { readManyEventRegistrationAction } from '@/services/events/registration/actions' +import type { EventRegistrationExpanded, EventRegistrationFetcherDetails } from '@/services/events/registration/types' import type { PageSizeUsers } from './UserPaging' -import type { ReadPageInput } from '@/lib/paging/Types' -const fetcher = async (x: ReadPageInput) => { - const registrations = await readManyEventRegistrationAction({ +export const [EventRegistrationPagingContext, EventRegistrationPagingProvider] = generatePaging< + EventRegistrationExpanded, + number, + PageSizeUsers, + EventRegistrationFetcherDetails +>({ + fetcher: async ({ paging }) => await readManyEventRegistrationAction({ params: { - eventId: x.details.eventId, - take: x.page.pageSize, - skip: (x.page.page * x.page.pageSize) || undefined, - type: x.details.type, + eventId: paging.details.eventId, + take: paging.page.pageSize, + skip: (paging.page.page * paging.page.pageSize) || undefined, + type: paging.details.type, } - }) - return registrations -} - -export const EventRegistrationPagingContext = - generatePagingContext() -const EventRegistrationPagingProvider = generatePagingProvider({ - Context: EventRegistrationPagingContext, - fetcher, - getCursorAfterFetch: data => (data.length), + }), + getCursor: ({ fetchedCount }) => (fetchedCount), }) -export default EventRegistrationPagingProvider diff --git a/src/contexts/paging/ImageCollectionPaging.tsx b/src/contexts/paging/ImageCollectionPaging.tsx index 10fbd2432..6550e01c2 100644 --- a/src/contexts/paging/ImageCollectionPaging.tsx +++ b/src/contexts/paging/ImageCollectionPaging.tsx @@ -1,23 +1,16 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { readImageCollectionsPageAction } from '@/actions/images/collections/read' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { ImageCollectionCursor, ImageCollectionPageReturn } from '@/services/images/collections/Types' +import { generatePaging } from './PagingGenerator' +import { readImageCollectionsPageAction } from '@/services/images/collections/actions' +import type { ImageCollectionCursor, ImageCollectionPageReturn } from '@/services/images/collections/types' export type PageSizeImageCollection = 12 -const fetcher = async (x: ReadPageInput) => { - const ret = await readImageCollectionsPageAction(x) - return ret -} -export const ImageCollectionPagingContext = generatePagingContext< +export const [ImageCollectionPagingContext, ImageCollectionPagingProvider] = generatePaging< ImageCollectionPageReturn, ImageCollectionCursor, PageSizeImageCollection ->() -const ImageCollectionPagingProvider = generatePagingProvider({ - Context: ImageCollectionPagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +>({ + fetcher: async ({ paging }) => await readImageCollectionsPageAction(paging), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default ImageCollectionPagingProvider + diff --git a/src/contexts/paging/ImagePaging.tsx b/src/contexts/paging/ImagePaging.tsx index b0b3a3fc5..a83a660ed 100644 --- a/src/contexts/paging/ImagePaging.tsx +++ b/src/contexts/paging/ImagePaging.tsx @@ -1,20 +1,17 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { readImagesPageAction } from '@/actions/images/read' -import type { ReadPageInput } from '@/lib/paging/Types' +import { generatePaging } from './PagingGenerator' +import { readImagesPageAction } from '@/services/images/actions' import type { Image } from '@prisma/client' -import type { ImageCursor, ImageDetails } from '@/services/images/Types' +import type { ImageCursor, ImageDetails } from '@/services/images/types' export type PageSizeImage = 30 -const fetcher = async (x: ReadPageInput) => { - const ret = await readImagesPageAction({ params: { paging: x } }) - return ret -} -export const ImagePagingContext = generatePagingContext() -const ImagePagingProvider = generatePagingProvider({ - Context: ImagePagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +export const [ImagePagingContext, ImagePagingProvider] = generatePaging< + Image, + ImageCursor, + PageSizeImage, + ImageDetails +>({ + fetcher: async ({ paging }) => await readImagesPageAction({ params: { paging } }), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default ImagePagingProvider diff --git a/src/contexts/paging/JobAdInactivePaging.tsx b/src/contexts/paging/JobAdInactivePaging.tsx index b600963f0..a412c0ce5 100644 --- a/src/contexts/paging/JobAdInactivePaging.tsx +++ b/src/contexts/paging/JobAdInactivePaging.tsx @@ -1,22 +1,16 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { readInactiveJobAdsPageAction } from '@/actions/career/jobAds/read' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { JobAdInactiveCursor, JobAdInactiveDetails, SimpleJobAd } from '@/services/career/jobAds/Types' +import { generatePaging } from './PagingGenerator' +import { readInactiveJobAdsPageAction } from '@/services/career/jobAds/actions' +import type { JobAdInactiveCursor, JobAdInactiveDetails, SimpleJobAd } from '@/services/career/jobAds/types' export type PageSizeJobAdInactive = 12 -const fetcher = async (x: ReadPageInput) => - await readInactiveJobAdsPageAction({ params: { paging: x } }) -export const JobAdInactivePagingContext = generatePagingContext< +export const [JobAdInactivePagingContext, JobAdInactivePagingProvider] = generatePaging< SimpleJobAd, JobAdInactiveCursor, PageSizeJobAdInactive, JobAdInactiveDetails ->() -const JobAdInactiveProvider = generatePagingProvider({ - Context: JobAdInactivePagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +>({ + fetcher: async ({ paging }) => await readInactiveJobAdsPageAction({ params: { paging } }), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default JobAdInactiveProvider diff --git a/src/contexts/paging/LockerPaging.tsx b/src/contexts/paging/LockerPaging.tsx index 524025347..bcb75064e 100644 --- a/src/contexts/paging/LockerPaging.tsx +++ b/src/contexts/paging/LockerPaging.tsx @@ -1,17 +1,15 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from '@/contexts/paging/PagingGenerator' -import { readLockerPageAction } from '@/actions/lockers/lockers' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { LockerCursor, LockerWithReservation } from '@/services/lockers/Types' +import { generatePaging } from '@/contexts/paging/PagingGenerator' +import { readLockerPageAction } from '@/services/lockers/actions' +import type { LockerCursor, LockerWithReservation } from '@/services/lockers/types' export type PageSizeLocker = 20 -const fetcher = async (paging: ReadPageInput) => - await readLockerPageAction({ params: { paging } }) -export const LockerPagingContext = generatePagingContext() -const LockerPagingProvider = generatePagingProvider({ - Context: LockerPagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +export const [LockerPagingContext, LockerPagingProvider] = generatePaging< + LockerWithReservation, + LockerCursor, + PageSizeLocker +>({ + fetcher: async ({ paging }) => await readLockerPageAction({ params: { paging } }), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default LockerPagingProvider diff --git a/src/contexts/paging/OldNewsPaging.tsx b/src/contexts/paging/OldNewsPaging.tsx index 668cf3fa6..07d04fa8f 100644 --- a/src/contexts/paging/OldNewsPaging.tsx +++ b/src/contexts/paging/OldNewsPaging.tsx @@ -1,16 +1,15 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { readOldNewsPageAction } from '@/actions/news/read' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { NewsCursor, SimpleNewsArticle } from '@/services/news/Types' +import { generatePaging } from './PagingGenerator' +import { readOldNewsPageAction } from '@/services/news/actions' +import type { NewsCursor, SimpleNewsArticle } from '@/services/news/types' export type PageSizeOldNews = 20 -const fetcher = async (x: ReadPageInput) => await readOldNewsPageAction(x) -export const OldNewsPagingContext = generatePagingContext() -const OldNewsPagingProvider = generatePagingProvider({ - Context: OldNewsPagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +export const [OldNewsPagingContext, OldNewsPagingProvider] = generatePaging< + SimpleNewsArticle, + NewsCursor, + PageSizeOldNews +>({ + fetcher: async ({ paging }) => await readOldNewsPageAction(paging), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default OldNewsPagingProvider diff --git a/src/contexts/paging/OmegaquotesPaging.tsx b/src/contexts/paging/OmegaquotesPaging.tsx index 78b665618..3330e0f48 100644 --- a/src/contexts/paging/OmegaquotesPaging.tsx +++ b/src/contexts/paging/OmegaquotesPaging.tsx @@ -1,17 +1,15 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from '@/contexts/paging/PagingGenerator' -import { readQuotesPageAction } from '@/actions/omegaquotes/read' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { OmegaquoteCursor, OmegaquoteFiltered } from '@/services/omegaquotes/Types' +import { generatePaging } from '@/contexts/paging/PagingGenerator' +import { readQuotesPageAction } from '@/services/omegaquotes/actions' +import type { OmegaquoteCursor, OmegaquoteFiltered } from '@/services/omegaquotes/types' export type PageSizeOmegaquote = 20; -const fetcher = async (x: ReadPageInput) => await readQuotesPageAction(x) - -export const OmegaquotePagingContext = generatePagingContext() -const OmegaquotePagingProvider = generatePagingProvider({ - Context: OmegaquotePagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +export const [OmegaquotePagingContext, OmegaquotePagingProvider] = generatePaging< + OmegaquoteFiltered, + OmegaquoteCursor, + PageSizeOmegaquote +>({ + fetcher: async ({ paging }) => await readQuotesPageAction(paging), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default OmegaquotePagingProvider diff --git a/src/contexts/paging/PagingGenerator.tsx b/src/contexts/paging/PagingGenerator.tsx index e356910c4..2f7329e03 100644 --- a/src/contexts/paging/PagingGenerator.tsx +++ b/src/contexts/paging/PagingGenerator.tsx @@ -1,18 +1,18 @@ 'use client' import React, { createContext, useState, useRef, useEffect } from 'react' -import type { ActionReturn } from '@/actions/Types' -import type { ReadPageInput, Page } from '@/lib/paging/Types' +import type { ActionReturn } from '@/services/actionTypes' +import type { ReadPageInput, Page } from '@/lib/paging/types' import type { Context as ReactContextType } from 'react' -export type StateTypes = { +export type PagingState = { page: Page, data: Data[], allLoaded: boolean, } -export type PagingContextType = ReactContextType<{ - state: StateTypes, +export type PagingData = { + state: PagingState, loadMore: () => Promise, refetch: () => Promise, setDetails: (details: FetcherDetails, withFetch?: boolean) => void, @@ -20,39 +20,69 @@ export type PagingContextType, 'cursor'>, loading: boolean, deatils: FetcherDetails, -} | null> +} + +export type PagingContext = + ReactContextType | null> -export type PropTypes = { +export type PagingProviderProps = { startPage: Omit, 'cursor'>, children: React.ReactNode, details: FetcherDetails, serverRenderedData: Data[], } -export type GeneratorPropTypes = { - fetcher: (x: ReadPageInput) => Promise>, - Context: PagingContextType, - getCursorAfterFetch: (data: Data[]) => Cursor | null, +export type GeneratePagingProviderProps = { + fetcher: ({ paging }: { paging: ReadPageInput }) => Promise>, + getCursor: ({ lastElement, fetchedCount }: { lastElement: Data, fetchedCount: number }) => Cursor, } /** * Generates a paging provider. Should be used in conjunction with generatePagingContext. - * @param fetcher The fetcher function that fetches the data. - * @param Context The context to use. - * @param getCursorAfterFetch A function that returns the cursor after fetching the data. You need - * to provide the way to set the next cursor after fetching the data. A return of null is interpreted as - * no data returned at all. In this case, the cursor will be unchanged. - * @returns A react component that provides the paging context. + * + * Example usage: + * ``` + * import { generatePaging } from './PagingGenerator' + * import { readItemsPageAction } from '@/services/items/actions' + * import type { Item, ItemCursor } from '@/services/items/types' + * + * export type PageSizeItems = 20 + * + * export const [ItemPagingContext, ItemPagingProvider] = generatePaging< + * Item, + * ItemCursor, + * PageSizeItems + * >({ + * fetcher: async ({ paging }) => await readItemsPageAction(paging), + * getCursor: ({ lastElement }) => ({ id: lastElement.id }), + * }) + * ``` + * + * @param fetcher The function that fetches the data. + * @param getCursor A function that returns the cursor after fetching data. + * It is provided the last element fetched and the number of elements fetched. + * In the case no data is fetched the function will not be called and the cursor will be unchanged. + * @returns A tuple containing the paging context and a react component that provides the paging context. */ -function generatePagingProvider({ +export function generatePaging({ fetcher, - Context, - getCursorAfterFetch, -}: GeneratorPropTypes + getCursor, +}: GeneratePagingProviderProps ) { - return function PagingProvider( - { serverRenderedData, startPage, children, details: givenDetails }: PropTypes - ) { + // Wrap `getCursor` to return null if no data is fetched. + const getCursorAfterFetch = (data: Data[]) => { + if (!data.length) return null + return getCursor({ lastElement: data[data.length - 1], fetchedCount: data.length }) + } + + const Context = createContext | null>(null) + + function Provider({ + serverRenderedData, + startPage, + children, + details: givenDetails + }: PagingProviderProps) { const generateDefaultState = () => { const cursor = getCursorAfterFetch(serverRenderedData) const page: Page = cursor ? { @@ -69,7 +99,7 @@ function generatePagingProvider>( + const [state, setState_] = useState>( generateDefaultState() ) const [loading, setLoading_] = useState(false) @@ -82,7 +112,7 @@ function generatePagingProvider) => { + const setState = (newState: PagingState) => { stateRef.current = newState setState_(newState) } @@ -98,8 +128,10 @@ function generatePagingProvider {children} ) } -} -function generatePagingContext< - Data, - Cursor, - const PageSize extends number, - FetcherDetails = undefined ->(): PagingContextType< - Data, - Cursor, - PageSize, - FetcherDetails - > { - const context = createContext<{ - state: StateTypes, - loadMore: () => Promise, - refetch: () => Promise, - setDetails: (details: FetcherDetails, withFetch?: boolean) => void, - serverRenderedData: Data[], - startPage: Omit, 'cursor'>, - loading: boolean, - deatils: FetcherDetails, - } | null>(null) - return context -} -export default generatePagingProvider -export { generatePagingContext } + return [Context, Provider] as const +} diff --git a/src/contexts/paging/SchoolPaging.tsx b/src/contexts/paging/SchoolPaging.tsx index 083e62ce4..ec0ebe7c3 100644 --- a/src/contexts/paging/SchoolPaging.tsx +++ b/src/contexts/paging/SchoolPaging.tsx @@ -1,23 +1,15 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { readSchoolsPageAction } from '@/education/schools/read' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { ExpandedSchool, SchoolCursor } from '@/education/schools/Types' +import { generatePaging } from './PagingGenerator' +import { readSchoolsPageAction } from '@/education/schools/actions' +import type { ExpandedSchool, SchoolCursor } from '@/services/education/schools/types' export type PageSizeSchool = 8 -const fetcher = async (x: ReadPageInput) => { - const res = await readSchoolsPageAction(x) - return res -} -export const SchoolPagingContext = generatePagingContext< +export const [SchoolPagingContext, SchoolPagingProvider] = generatePaging< ExpandedSchool, SchoolCursor, PageSizeSchool ->() -const SchoolPagingProvider = generatePagingProvider({ - Context: SchoolPagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +>({ + fetcher: async ({ paging }) => await readSchoolsPageAction(paging), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default SchoolPagingProvider diff --git a/src/contexts/paging/UserPaging.tsx b/src/contexts/paging/UserPaging.tsx index 73345d02c..1ef789789 100644 --- a/src/contexts/paging/UserPaging.tsx +++ b/src/contexts/paging/UserPaging.tsx @@ -1,19 +1,16 @@ 'use client' -import generatePagingProvider, { generatePagingContext } from './PagingGenerator' -import { readUserPageAction } from '@/actions/users/read' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { UserDetails, UserPagingReturn, UserCursor } from '@/services/users/Types' +import { generatePaging } from './PagingGenerator' +import { readUserPageAction } from '@/services/users/actions' +import type { UserDetails, UserPagingReturn, UserCursor } from '@/services/users/types' export type PageSizeUsers = 50; -const fetcher = async (x: ReadPageInput) => { - const users = await readUserPageAction({ params: { paging: x } }) - return users -} -export const UserPagingContext = generatePagingContext() -const UserPagingProvider = generatePagingProvider({ - Context: UserPagingContext, - fetcher, - getCursorAfterFetch: data => (data.length ? { id: data[data.length - 1].id } : null), +export const [UserPagingContext, UserPagingProvider] = generatePaging< + UserPagingReturn, + UserCursor, + PageSizeUsers, + UserDetails +>({ + fetcher: async ({ paging }) => await readUserPageAction({ params: { paging } }), + getCursor: ({ lastElement }) => ({ id: lastElement.id }), }) -export default UserPagingProvider diff --git a/src/hooks/useActionCall.ts b/src/hooks/useActionCall.ts index ddc724aaf..63122f29c 100644 --- a/src/hooks/useActionCall.ts +++ b/src/hooks/useActionCall.ts @@ -1,7 +1,7 @@ 'use client' -import { createActionError } from '@/actions/error' +import { createActionError } from '@/services/actionError' import { useState, useEffect } from 'react' -import type { ActionReturn, ActionReturnError } from '@/actions/Types' +import type { ActionReturn, ActionError } from '@/services/actionTypes' /** * You sometimes want to call a server action that reads from the client. This hook helps with that. @@ -10,16 +10,15 @@ import type { ActionReturn, ActionReturnError } from '@/actions/Types' */ export default function useActionCall< Data, - DataGuarantee extends true >( - action: () => Promise> + action: () => Promise> ) { const [res, setRes] = useState<{ data: Data | null, error: null } | { data: null, - error: ActionReturnError + error: ActionError }>({ data: null, error: null diff --git a/src/hooks/useEditing.ts b/src/hooks/useEditing.ts index ce6e33c37..b18e0478c 100644 --- a/src/hooks/useEditing.ts +++ b/src/hooks/useEditing.ts @@ -1,12 +1,12 @@ 'use client' -import { useUser } from '@/auth/useUser' +import { useUser } from '@/auth/session/useUser' import { EditModeContext } from '@/contexts/EditMode' import { checkVisibility } from '@/auth/checkVisibility' import { useContext, useEffect, useRef, useState } from 'react' import { v4 as uuid } from 'uuid' import type { Permission } from '@prisma/client' -import type { Matrix } from '@/utils/checkMatrix' -import type { VisibilityCollapsed, VisibilityLevelType } from '@/services/visibility/Types' +import type { Matrix } from '@/lib/checkMatrix' +import type { VisibilityCollapsed, VisibilityLevelType } from '@/services/visibility/types' /** * A hook that uses useUser to determine if the user is allowed to edit the content. diff --git a/src/utils/checkMatrix.ts b/src/lib/checkMatrix.ts similarity index 100% rename from src/utils/checkMatrix.ts rename to src/lib/checkMatrix.ts diff --git a/src/lib/feide/userRoutines.ts b/src/lib/feide/userRoutines.ts index ce76fa1dc..a60ffecfe 100644 --- a/src/lib/feide/userRoutines.ts +++ b/src/lib/feide/userRoutines.ts @@ -2,7 +2,7 @@ import '@pn-server-only' import { fetchStudyProgrammesFromFeide } from './api' import { upsertStudyProgrammes } from '@/services/groups/studyProgrammes/create' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' export async function updateUserStudyProgrammes(userId: number, accessToken: string) { const feideStudyProgrammes = await fetchStudyProgrammesFromFeide(accessToken) diff --git a/src/lib/fields/config.ts b/src/lib/fields/constants.ts similarity index 100% rename from src/lib/fields/config.ts rename to src/lib/fields/constants.ts diff --git a/src/lib/fields/zpn.ts b/src/lib/fields/zpn.ts index 6dc0d023c..c9096923c 100644 --- a/src/lib/fields/zpn.ts +++ b/src/lib/fields/zpn.ts @@ -1,10 +1,10 @@ -import { FIELD_IS_PRESENT_VALUE } from './config' +import { FIELD_IS_PRESENT_VALUE } from './constants' import { dateMatchCron } from '@/lib/dates/cron' import { z } from 'zod' import { zfd } from 'zod-form-data' import type { EnumLike } from 'zod' -export namespace zpn { +export namespace Zpn { /** * This field is used to represent a boolean that could be a checkbox in frontend with no specified value * That is: the value is 'on' or not present at all (default behavior of checkboxes) diff --git a/src/lib/isBuildPhase.ts b/src/lib/isBuildPhase.ts new file mode 100644 index 000000000..a8336e6f0 --- /dev/null +++ b/src/lib/isBuildPhase.ts @@ -0,0 +1,3 @@ +export function isBuildPhase(): boolean { + return process.env.NEXT_PHASE === 'phase-production-build' +} diff --git a/src/lib/jwt/ConfigVars.ts b/src/lib/jwt/constants.ts similarity index 100% rename from src/lib/jwt/ConfigVars.ts rename to src/lib/jwt/constants.ts diff --git a/src/lib/jwt/jwt.ts b/src/lib/jwt/jwt.ts index cbcf3187d..c9f4b3b72 100644 --- a/src/lib/jwt/jwt.ts +++ b/src/lib/jwt/jwt.ts @@ -1,11 +1,11 @@ import '@pn-server-only' import { readJWTPart } from './jwtReadUnsecure' -import { JWT_ISSUER } from '@/jwt/ConfigVars' +import { JWT_ISSUER } from '@/lib/jwt/constants' import { ServerError } from '@/services/error' import { JsonWebTokenError, TokenExpiredError, sign, verify } from 'jsonwebtoken' import type jwt from 'jsonwebtoken' import type { JwtPayloadType } from './validation' -import type { OmegaJWTAudience } from '@/jwt/Types' +import type { OmegaJWTAudience } from '@/lib/jwt/types' // See https://www.rfc-editor.org/rfc/rfc7519#section-4.1 for the diff --git a/src/lib/jwt/parseJWTClient.ts b/src/lib/jwt/parseJWTClient.ts index 9a4405354..574534300 100644 --- a/src/lib/jwt/parseJWTClient.ts +++ b/src/lib/jwt/parseJWTClient.ts @@ -1,10 +1,10 @@ 'use client' import { readJWTPayload } from './jwtReadUnsecure' -import { createActionError } from '@/actions/error' -import { JWT_ISSUER } from '@/jwt/ConfigVars' -import type { OmegaJWTAudience } from '@/jwt/Types' -import type { ActionReturn } from '@/actions/Types' +import { createActionError } from '@/services/actionError' +import { JWT_ISSUER } from '@/lib/jwt/constants' +import type { OmegaJWTAudience } from '@/lib/jwt/types' +import type { ActionReturn } from '@/services/actionTypes' /** * Parses a JSON Web Token (JWT) and verifies its signature using the provided public key. diff --git a/src/lib/jwt/Types.ts b/src/lib/jwt/types.ts similarity index 54% rename from src/lib/jwt/Types.ts rename to src/lib/jwt/types.ts index d049aa259..aa7c26174 100644 --- a/src/lib/jwt/Types.ts +++ b/src/lib/jwt/types.ts @@ -1,5 +1,5 @@ -import type { OmegaJWTAudienceFields } from './ConfigVars' +import type { OmegaJWTAudienceFields } from './constants' export type OmegaJWTAudience = typeof OmegaJWTAudienceFields[number] diff --git a/src/lib/paging/cursorPageingSelection.ts b/src/lib/paging/cursorPageingSelection.ts index 7f0c39352..e255d183c 100644 --- a/src/lib/paging/cursorPageingSelection.ts +++ b/src/lib/paging/cursorPageingSelection.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import type { Page } from '@/lib/paging/Types' +import type { Page } from '@/lib/paging/types' /** * A function to generate the cursor-paging selection for a given page diff --git a/src/lib/paging/Types.ts b/src/lib/paging/types.ts similarity index 100% rename from src/lib/paging/Types.ts rename to src/lib/paging/types.ts diff --git a/src/lib/query-params/QueryParam.ts b/src/lib/queryParams/QueryParam.ts similarity index 97% rename from src/lib/query-params/QueryParam.ts rename to src/lib/queryParams/QueryParam.ts index 20e30a3a0..1f80d0db3 100644 --- a/src/lib/query-params/QueryParam.ts +++ b/src/lib/queryParams/QueryParam.ts @@ -1,4 +1,4 @@ -import type { SearchParamsServerSide } from './Types' +import type { SearchParamsServerSide } from './types' export abstract class QueryParam { public name: string diff --git a/src/lib/query-params/decoders.ts b/src/lib/queryParams/decoders.ts similarity index 85% rename from src/lib/query-params/decoders.ts rename to src/lib/queryParams/decoders.ts index 2a3898870..92ec10aa1 100644 --- a/src/lib/query-params/decoders.ts +++ b/src/lib/queryParams/decoders.ts @@ -1,4 +1,4 @@ -import type { SearchParamsServerSide } from './Types' +import type { SearchParamsServerSide } from './types' export const stringDecoder = (raw: SearchParamsServerSide['searchParams']) => { if (typeof raw !== 'string') { diff --git a/src/lib/query-params/queryParams.ts b/src/lib/queryParams/queryParams.ts similarity index 100% rename from src/lib/query-params/queryParams.ts rename to src/lib/queryParams/queryParams.ts diff --git a/src/lib/query-params/Types.ts b/src/lib/queryParams/types.ts similarity index 100% rename from src/lib/query-params/Types.ts rename to src/lib/queryParams/types.ts diff --git a/src/prisma/client.ts b/src/prisma/client.ts new file mode 100644 index 000000000..3bc814850 --- /dev/null +++ b/src/prisma/client.ts @@ -0,0 +1,12 @@ +import { PrismaClient } from '@prisma/client' + +// To prevent hot reloading from creating new instances of PrismaClient it is stored in the global object. +// Read more about it in the section "Prevent hot reloading from creating new instances of PrismaClient" here: +// https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/databases-connections + +// This is how the Prisma docs recommend doing it +const globalForPrisma = global as unknown as { prisma: PrismaClient } + +export const prisma = globalForPrisma.prisma || new PrismaClient() + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma diff --git a/src/prisma/index.ts b/src/prisma/index.ts deleted file mode 100644 index dda84f8cb..000000000 --- a/src/prisma/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { PrismaClient } from '@prisma/client' - -const prisma = global.prisma || new PrismaClient() - -if (process.env.NODE_ENV !== 'production') global.prisma = prisma - -export default prisma diff --git a/src/prisma/seeder/seed.ts b/src/prisma/seeder/seed.ts index a18e6b89a..e755cbd1a 100644 --- a/src/prisma/seeder/seed.ts +++ b/src/prisma/seeder/seed.ts @@ -1,5 +1,5 @@ import seed from './src/seeder' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { exit } from 'process' seed( diff --git a/src/prisma/seeder/src/development/seedDevEvents.ts b/src/prisma/seeder/src/development/seedDevEvents.ts index c06dc7192..17b1114bd 100644 --- a/src/prisma/seeder/src/development/seedDevEvents.ts +++ b/src/prisma/seeder/src/development/seedDevEvents.ts @@ -1,4 +1,4 @@ -import { EventMethods } from '@/services/events/methods' +import { eventOperations } from '@/services/events/operations' import type { PrismaClient } from '@prisma/client' export default async function seedDevEvents(prisma: PrismaClient) { @@ -17,8 +17,8 @@ export default async function seedDevEvents(prisma: PrismaClient) { } }) - const bedpres = await EventMethods.create.client(prisma).execute({ - session: null, + const bedpres = await eventOperations.create({ + prisma, bypassAuth: true, data: { name: 'Bedpres med Kongsberg', @@ -37,8 +37,8 @@ export default async function seedDevEvents(prisma: PrismaClient) { } }) - await EventMethods.create.client(prisma).execute({ - session: null, + await eventOperations.create({ + prisma, bypassAuth: true, data: { name: 'Stresset eksamenslesing', diff --git a/src/prisma/seeder/src/development/seedDevJobAds.ts b/src/prisma/seeder/src/development/seedDevJobAds.ts index 2a894857b..50378ef0c 100644 --- a/src/prisma/seeder/src/development/seedDevJobAds.ts +++ b/src/prisma/seeder/src/development/seedDevJobAds.ts @@ -1,4 +1,4 @@ -import { JobadMethods } from '@/services/career/jobAds/methods' +import { jobAdOperations } from '@/services/career/jobAds/operations' import type { JobType, PrismaClient } from '@prisma/client' @@ -51,13 +51,13 @@ export default async function seedDevJobAds(prisma: PrismaClient) { ] for (const jobAd of jobAdData) { - const restult = await JobadMethods.create.client(prisma).execute({ + const restult = await jobAdOperations.create({ + prisma, data: { ...jobAd, companyId: 1, }, bypassAuth: true, - session: null, }) await prisma.article.update({ diff --git a/src/prisma/seeder/src/development/seedDevUsers.ts b/src/prisma/seeder/src/development/seedDevUsers.ts index 1772dcd60..f992f175a 100644 --- a/src/prisma/seeder/src/development/seedDevUsers.ts +++ b/src/prisma/seeder/src/development/seedDevUsers.ts @@ -1,4 +1,4 @@ -import { hashAndEncryptPassword } from '@/auth/password' +import { hashAndEncryptPassword } from '@/auth/passwordHash' import { v4 as uuid } from 'uuid' import type { PrismaClient } from '@prisma/client' diff --git a/src/prisma/seeder/src/seedNotificationsChannels.ts b/src/prisma/seeder/src/seedNotificationsChannels.ts index a6255e2f1..190d33de0 100644 --- a/src/prisma/seeder/src/seedNotificationsChannels.ts +++ b/src/prisma/seeder/src/seedNotificationsChannels.ts @@ -1,4 +1,4 @@ -import { NotificationConfig } from '@/services/notifications/config' +import { allNotificationMethodsOn } from '@/services/notifications/constants' import { SpecialNotificationChannel } from '@prisma/client' import type { NotificationMethod, PrismaClient } from '@prisma/client' @@ -19,8 +19,8 @@ export default async function seedNotificationChannels(prisma: PrismaClient) { special: 'ROOT', name: 'Alle varslinger', description: 'Denne kanalen styrer alle varslinger', - defaultMethods: NotificationConfig.allMethodsOn, - availableMethods: NotificationConfig.allMethodsOn, + defaultMethods: allNotificationMethodsOn, + availableMethods: allNotificationMethodsOn, }, { special: 'NEW_EVENT', @@ -30,35 +30,35 @@ export default async function seedNotificationChannels(prisma: PrismaClient) { email: false, emailWeekly: true, }, - availableMethods: NotificationConfig.allMethodsOn, + availableMethods: allNotificationMethodsOn, }, { special: 'NEW_OMBUL', name: 'Ny ombul', description: 'Varsling når det kommer ny ombul', - defaultMethods: NotificationConfig.allMethodsOff, - availableMethods: NotificationConfig.allMethodsOn, + defaultMethods: allNotificationMethodsOn, + availableMethods: allNotificationMethodsOn, }, { special: 'NEW_NEWS_ARTICLE', name: 'Ny nyhetsartikkel', description: 'Varslinger om nye artikler', - defaultMethods: NotificationConfig.allMethodsOff, - availableMethods: NotificationConfig.allMethodsOn, + defaultMethods: allNotificationMethodsOn, + availableMethods: allNotificationMethodsOn, }, { special: 'NEW_JOBAD', name: 'Ny jobbannonse', description: 'Varslinger at en ny jobbanonse er ute', - defaultMethods: NotificationConfig.allMethodsOff, - availableMethods: NotificationConfig.allMethodsOn, + defaultMethods: allNotificationMethodsOn, + availableMethods: allNotificationMethodsOn, }, { special: 'NEW_OMEGAQUOTE', name: 'Ny omegaquote', description: 'Varslinger om en ny omega quote', - defaultMethods: NotificationConfig.allMethodsOff, - availableMethods: NotificationConfig.allMethodsOn, + defaultMethods: allNotificationMethodsOn, + availableMethods: allNotificationMethodsOn, }, { special: 'EVENT_WAITINGLIST_PROMOTION', @@ -94,7 +94,7 @@ export default async function seedNotificationChannels(prisma: PrismaClient) { email: true, emailWeekly: false, }, - availableMethods: NotificationConfig.allMethodsOn, + availableMethods: allNotificationMethodsOn, alias: 'hs', }, { @@ -104,7 +104,7 @@ export default async function seedNotificationChannels(prisma: PrismaClient) { email: true, emailWeekly: false, }, - availableMethods: NotificationConfig.allMethodsOn, + availableMethods: allNotificationMethodsOn, alias: 'bleast', }, { @@ -114,7 +114,7 @@ export default async function seedNotificationChannels(prisma: PrismaClient) { email: true, emailWeekly: false, }, - availableMethods: NotificationConfig.allMethodsOn, + availableMethods: allNotificationMethodsOn, alias: 'vevcom', }, { @@ -124,7 +124,7 @@ export default async function seedNotificationChannels(prisma: PrismaClient) { email: true, emailWeekly: false, }, - availableMethods: NotificationConfig.allMethodsOn, + availableMethods: allNotificationMethodsOn, alias: 'contactor', }, { @@ -147,7 +147,7 @@ export default async function seedNotificationChannels(prisma: PrismaClient) { email: true, emailWeekly: false, }, - availableMethods: NotificationConfig.allMethodsOn, + availableMethods: allNotificationMethodsOn, }, ] diff --git a/src/serverSidePage.tsx b/src/serverSidePage.tsx index 87f616041..b1bddd6a4 100644 --- a/src/serverSidePage.tsx +++ b/src/serverSidePage.tsx @@ -1,6 +1,8 @@ import { Smorekopp } from './services/error' import type { ReactNode } from 'react' +// WIP + export function serverSidePage( serviceMethod: () => Promise, render: (data: Data) => ReactNode diff --git a/src/services/ServiceMethod.ts b/src/services/ServiceMethod.ts deleted file mode 100644 index da2a82a6b..000000000 --- a/src/services/ServiceMethod.ts +++ /dev/null @@ -1,360 +0,0 @@ -import '@pn-server-only' -import { ParseError, Smorekopp } from './error' -import { prismaErrorWrapper } from './prismaCall' -import { default as globalPrisma } from '@/prisma' -import { Session } from '@/auth/Session' -import { zfd } from 'zod-form-data' -import type { z } from 'zod' -import type { SessionMaybeUser } from '@/auth/Session' -import type { Prisma, PrismaClient } from '@prisma/client' -import type { AutherStaticFieldsBound } from '@/auth/auther/Auther' - -export type InferedOrInput = - Schema extends undefined - ? object - : InferedOfInput extends 'INFERED' ? z.infer> : z.input> - -export type ParamsObject = - ParamsSchema extends undefined - ? object - : { params: InferedOrInput } -export type ImplementationParamsObject< - ImplementationParamsSchema extends z.ZodTypeAny | undefined, - InferedOfInput extends 'INFERED' | 'INPUT' -> = - ImplementationParamsSchema extends undefined - ? object - : { implementationParams: InferedOrInput } -export type DataObject = - DataSchema extends undefined - ? object - : { data: InferedOrInput } - -/** - * This is the type for the argument that are passed to the execute method of a service method. - */ -export type ServiceMethodExecuteArgs< - Unsafe extends 'UNSAFE' | 'SAFE', - ParamsSchema extends z.ZodTypeAny | undefined, - DataSchema extends z.ZodTypeAny | undefined, - ImplementationParamsSchema extends z.ZodTypeAny | undefined -> = { - session: SessionMaybeUser | null, - bypassAuth?: boolean, -} & ( - Unsafe extends 'UNSAFE' - ? { - params?: unknown, - implementationParams?: unknown, - data?: unknown, - } - : - & ParamsObject - & ImplementationParamsObject - & DataObject -) - -export type ServiceMethodMethod< - OpensTransaction extends boolean, - ParamsSchema extends z.ZodTypeAny | undefined, - DataSchema extends z.ZodTypeAny | undefined, - Return, -> = ( - args: { - prisma: PrismaPossibleTransaction, - session: SessionMaybeUser, - } & ParamsObject & DataObject -) => Promise | Return - -export type SubServiceMethodConfig< - ParamsSchema extends z.ZodTypeAny | undefined, - DataSchema extends z.ZodTypeAny | undefined, - ParamsSchemaImplementationFields extends object | undefined, - DataSchemaImplementationFields extends object | undefined, - MethodImplementationParams extends object | undefined, - Return, - OpensTransaction extends boolean = false, -> = { - paramsSchema?: ((implementationFields: ParamsSchemaImplementationFields) => ParamsSchema) | undefined, - dataSchema?: (implementationFields: DataSchemaImplementationFields) => DataSchema | undefined, - opensTransaction?: OpensTransaction, - method: (implementationParams: MethodImplementationParams) => - ServiceMethodMethod -} - -export type AutherGetter< - AutherDynamicFields extends object, - ParamsSchema extends z.ZodTypeAny | undefined, - DataSchema extends z.ZodTypeAny | undefined, - ImplementationParamsSchema extends z.ZodTypeAny | undefined -> = ( - paramsData: ParamsObject & - ImplementationParamsObject & - DataObject -) => // Todo: Make prettier type for returntype of dynamic fields - | ReturnType['dynamicFields']> - | Promise['dynamicFields']>> - -export type ServiceMethodImplementationConfig< - ImplementationParamsSchema extends z.ZodTypeAny | undefined, - ParamsSchema extends z.ZodTypeAny | undefined, - DataSchema extends z.ZodTypeAny | undefined, - ParamsSchemaImplementationFields extends object | undefined, - DataSchemaImplementationFields extends object | undefined, - MethodImplementationFields extends object | undefined, - AutherDynamicFields extends object -> = { - implementationParamsSchema: ImplementationParamsSchema, - auther: AutherGetter, - paramsSchemaImplementationFields: ParamsSchemaImplementationFields - dataSchemaImplementationFields: DataSchemaImplementationFields - methodImplementationFields: MethodImplementationFields -} - - -/** - * This is the type for the prisma client that is passed to the service method. - * It can't simply be PrismaClient because it can be usefull to use a service method - * inside a transaction. In that case, the prisma client is a Prisma.TransactionClient. - * The caveat is that a Prisma.TransactionClient can't be used to open a new transaction - * so if the service method opens a transaction, the prisma client can only be a PrismaClient. - */ -export type PrismaPossibleTransaction< - OpensTransaction extends boolean -> = OpensTransaction extends true ? PrismaClient : Prisma.TransactionClient - -/** - * This is the return type of the ServiceMethod function. It contains a client function that can be used - * to pass a specific prisma client to the service method, and a newClient function that can be used to - * pass the global prisma client to the service method. - * - * TypeScript is smart enough to infer the behaviour of the return functons without the need to excplitly - * type the return type of the ServiceMethod function, but it is done so for the sake of clarity. - */ -export type ServiceMethodType< - OpensTransaction extends boolean, - Return, - ParamsSchema extends z.ZodTypeAny | undefined = undefined, - DataSchema extends z.ZodTypeAny | undefined = undefined, - ImplementationParamsSchema extends z.ZodTypeAny | undefined = undefined -> = { - /** - * Pass a specific prisma client to the service method. Usefull when using the service method inside a transaction. - * @note - * @param client - */ - client: (client: PrismaPossibleTransaction) => { - execute: ( - args: ServiceMethodExecuteArgs<'SAFE', ParamsSchema, DataSchema, ImplementationParamsSchema> - ) => Promise, - executeUnsafe: ( - args: ServiceMethodExecuteArgs<'UNSAFE', ParamsSchema, DataSchema, ImplementationParamsSchema> - ) => Promise, - }, - /** - * Use the global prisma client for the service method. - */ - newClient: () => ( - ReturnType< - ServiceMethodType['client'] - > - ), - paramsSchema?: ParamsSchema, - dataSchema?: DataSchema, - implementationParamsSchema?: ImplementationParamsSchema, -} - -export function SubServiceMethod< - Return, - OpensTransaction extends boolean = false, - ParamsSchema extends z.ZodTypeAny | undefined = undefined, - DataSchema extends z.ZodTypeAny | undefined = undefined, - ParamsSchemaImplementationFields extends object | undefined = undefined, - DataSchemaImplementationFields extends object | undefined = undefined, - MethodImplementationParams extends object | undefined = undefined, ->( - serviceMethodConfig: SubServiceMethodConfig< - ParamsSchema, - DataSchema, - ParamsSchemaImplementationFields, - DataSchemaImplementationFields, - MethodImplementationParams, - Return, - OpensTransaction - > -) { - return { - implement: < - ImplementationParamsSchema extends z.ZodTypeAny | undefined, - AutherDynamicFields extends object - >( - implementationArgs: ServiceMethodImplementationConfig< - ImplementationParamsSchema, - ParamsSchema, - DataSchema, - ParamsSchemaImplementationFields, - DataSchemaImplementationFields, - MethodImplementationParams, - AutherDynamicFields - > - ) : ServiceMethodType => { - const expectedArgsArePresent = ( - args: ServiceMethodExecuteArgs<'UNSAFE', ParamsSchema, DataSchema, ImplementationParamsSchema> - ): args is ServiceMethodExecuteArgs<'SAFE', ParamsSchema, DataSchema, ImplementationParamsSchema> => { - const paramsMatch = - Boolean(args.params) === Boolean(serviceMethodConfig.paramsSchema) - const dataMatches = - Boolean(args.data) === Boolean(serviceMethodConfig.dataSchema) - const implementationParamsMatch = - Boolean(args.implementationParams) === Boolean(implementationArgs.methodImplementationFields) - return paramsMatch && dataMatches && implementationParamsMatch - } - - const client = (prisma: PrismaPossibleTransaction) => { - const executeUnsafe = async ( - args: ServiceMethodExecuteArgs<'UNSAFE', ParamsSchema, DataSchema, ImplementationParamsSchema> - ) => { - if (args.params) { - if (!serviceMethodConfig.paramsSchema) { - throw new Smorekopp( - 'BAD PARAMETERS', 'Service method recieved params, but has no params schema.' - ) - } - const paramsSchema = serviceMethodConfig.paramsSchema( - implementationArgs.paramsSchemaImplementationFields! - ) - if (!paramsSchema) { - throw new Smorekopp( - 'BAD PARAMETERS', 'Service method recieved params, but has no params schema.' - ) - } - const paramsParse = paramsSchema.safeParse(args.params) - - if (!paramsParse.success) { - console.log(paramsParse) // TODO: This needs to be returned to give good error message. - throw new Smorekopp('BAD PARAMETERS', 'Invalid params passed to service method.') - } - - args.params = paramsParse.data - } - if (args.data) { - if (!serviceMethodConfig.dataSchema) { - throw new Smorekopp( - 'BAD PARAMETERS', 'Service method recieved data, but has no data schema. 1' - ) - } - const dataSchema = serviceMethodConfig.dataSchema( - implementationArgs.dataSchemaImplementationFields - ) - if (!dataSchema) { - throw new Smorekopp( - 'BAD PARAMETERS', 'Service method recieved data, but has no data schema. 2' - ) - } - const dataParse = zfd.formData(dataSchema).safeParse(args.data) - if (!dataParse.success) { - console.log(dataParse) - throw new ParseError(dataParse) - } - args.data = dataParse.data - } - - if (args.implementationParams) { - if (!implementationArgs.implementationParamsSchema) { - throw new Smorekopp( - 'BAD PARAMETERS', - 'Service method recieved implementation params, but has no implementation params schema.' - ) - } - const implementationParamsSchema = implementationArgs.implementationParamsSchema - const implementationParamsParse = implementationParamsSchema.safeParse( - args.implementationParams - ) - if (!implementationParamsParse.success) { - // TODO: This needs to be returned to give good error message. - console.log(implementationParamsParse) - throw new Smorekopp('BAD PARAMETERS', 'Invalid implementation params passed to service method.') - } - args.implementationParams = implementationParamsParse.data - } - - if (!expectedArgsArePresent(args)) { - throw new Smorekopp( - 'SERVER ERROR', - 'Service method recieved invalid arguments.' - ) - } - - if (!args.bypassAuth) { - const authRes = (await implementationArgs.auther(args)).auth(args.session ?? Session.empty()) - if (!authRes.authorized) { - throw new Smorekopp(authRes.status, authRes.getErrorMessage) - } - } - - return prismaErrorWrapper(() => serviceMethodConfig.method( - implementationArgs.methodImplementationFields - )({ - ...args, - prisma, - session: args.session ?? Session.empty() - })) - } - - return { - executeUnsafe, - execute: ( - args: ServiceMethodExecuteArgs<'SAFE', ParamsSchema, DataSchema, ImplementationParamsSchema> - ) => executeUnsafe(args) - } - } - - return { - client, - newClient: () => client(globalPrisma), - paramsSchema: serviceMethodConfig.paramsSchema ? - serviceMethodConfig.paramsSchema(implementationArgs.paramsSchemaImplementationFields) : - undefined, - dataSchema: serviceMethodConfig.dataSchema ? - serviceMethodConfig.dataSchema(implementationArgs.dataSchemaImplementationFields) : - undefined, - implementationParamsSchema: implementationArgs.implementationParamsSchema - } - } - } -} - -export function ServiceMethod< - AutherDynamicFields extends object, - OpensTransaction extends boolean, - Return, - ParamsSchema extends z.ZodTypeAny | undefined = undefined, - DataSchema extends z.ZodTypeAny | undefined = undefined, ->({ paramsSchema, dataSchema, opensTransaction, auther, method }: { - paramsSchema?: ParamsSchema, - dataSchema?: DataSchema, - opensTransaction?: OpensTransaction, - auther: AutherGetter, - method: ServiceMethodMethod -}): ServiceMethodType { - return SubServiceMethod< - Return, - OpensTransaction, - ParamsSchema, - DataSchema, - undefined, - undefined, - undefined - >({ - opensTransaction, - method: () => method, - paramsSchema: paramsSchema !== undefined ? () => paramsSchema : undefined, - dataSchema: dataSchema !== undefined ? () => dataSchema : undefined, - }).implement({ - auther, - implementationParamsSchema: undefined, - dataSchemaImplementationFields: undefined, - paramsSchemaImplementationFields: undefined, - methodImplementationFields: undefined, - }) -} diff --git a/src/services/actionError.ts b/src/services/actionError.ts new file mode 100644 index 000000000..d8bc22767 --- /dev/null +++ b/src/services/actionError.ts @@ -0,0 +1,64 @@ +import { errorCodes, type ErrorCode, type ErrorMessage } from '@/services/error' +import { ParseError, Smorekopp } from '@/services/error' +import type { AuthStatus } from '@/auth/session/getUser' +import type { SafeParseError } from 'zod' +import type { ActionError, ActionReturn } from './actionTypes' + +/** + * @deprecated With the "new" service operation system this should not be called directly. + * The action creation utility should handle this internally. + */ +export function createActionError(errorCode: ErrorCode | AuthStatus, error?: string | ErrorMessage[]): ActionError { + if (errorCode === 'AUTHORIZED' || errorCode === 'AUTHORIZED_NO_USER') { + return { + success: false, + errorCode: 'UNKNOWN ERROR', + httpCode: 500, + error: typeof error === 'string' ? [{ message: error }] : error, + } + } + return { + success: false, + errorCode, + httpCode: errorCodes.find(e => e.name === errorCode)?.httpCode ?? 500, + error: typeof error === 'string' ? [{ message: error }] : error, + } +} + +/** + * @deprecated With the "new" service operation system this should not be called directly. + * The action creation utility should handle this internally. + */ +export function createZodActionError(parse: SafeParseError): ActionError { + return { + success: false, + httpCode: 400, + errorCode: 'BAD PARAMETERS', + error: parse.error.issues, + } +} + +/** + * A function that calls a server function. If all goes well, it returns a ActionReturn with the data. + * If an error is thrown it returns ActionReturn of success false and the error. + * The function handles ServerErrors class, and treats all other errors as unknown. + * @param call - A async server function to call. + * @returns - A promise that resolves to an ActionReturn. + */ +export async function safeServerCall(call: () => Promise): Promise> { + try { + const data = await call() + return { + success: true, + data, + } + } catch (error) { + if (error instanceof ParseError) { + return createZodActionError(error.parseError) + } + if (error instanceof Smorekopp) { + return createActionError(error.errorCode, error.errors) + } + return createActionError('UNKNOWN ERROR', 'unknown error') + } +} diff --git a/src/services/actionTypes.ts b/src/services/actionTypes.ts new file mode 100644 index 000000000..9c93900a7 --- /dev/null +++ b/src/services/actionTypes.ts @@ -0,0 +1,28 @@ +import type { ErrorMessage, ErrorCode } from '@/services/error' + +/** + * The return type of an action on error. + */ +export type ActionError = { + success: false, + errorCode: ErrorCode, + httpCode: number, + error?: ErrorMessage[], +} + +/** + * The return type of an action on success. + */ +export type ActionData = { + success: true, + data: T, +} + +/** + * The return type of an action. Either success with data or error with error info. + */ +export type ActionReturn = ActionData | ActionError + +export type Action = (formData: FormData) => ( + Promise> +) diff --git a/src/services/admission/actions.ts b/src/services/admission/actions.ts new file mode 100644 index 000000000..0e7f18af7 --- /dev/null +++ b/src/services/admission/actions.ts @@ -0,0 +1,6 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { admissionOperations } from '@/services/admission/operations' + +export const createAdmissionTrialAction = makeAction(admissionOperations.createTrial) diff --git a/src/services/admission/authers.ts b/src/services/admission/auth.ts similarity index 52% rename from src/services/admission/authers.ts rename to src/services/admission/auth.ts index 9bcf6e00d..f0a81a861 100644 --- a/src/services/admission/authers.ts +++ b/src/services/admission/auth.ts @@ -1,9 +1,9 @@ import { RequireNothing } from '@/auth/auther/RequireNothing' import { RequirePermissionAndUser } from '@/auth/auther/RequirePermissionAndUser' -export namespace AdmissionAuthers { - export const createTrial = RequirePermissionAndUser.staticFields({ +export const admissionAuth = { + createTrial: RequirePermissionAndUser.staticFields({ permission: 'ADMISSION_TRIAL_CREATE', - }) - export const readTrial = RequireNothing.staticFields({}) + }), + readTrial: RequireNothing.staticFields({}), } diff --git a/src/services/admission/config.ts b/src/services/admission/config.ts deleted file mode 100644 index 364ca97e8..000000000 --- a/src/services/admission/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Admission } from '@prisma/client' - -export namespace AdmissionConfig { - export const displayNames = { - PLIKTTIAENESTE: 'Plikttiaeneste', - PROEVELSEN: 'Proevelsen', - } as const satisfies Record - export const array = Object.values(Admission) -} diff --git a/src/services/admission/constants.ts b/src/services/admission/constants.ts new file mode 100644 index 000000000..b91f12313 --- /dev/null +++ b/src/services/admission/constants.ts @@ -0,0 +1,8 @@ +import { Admission } from '@prisma/client' + +export const admissionDisplayNames = { + PLIKTTIAENESTE: 'Plikttiaeneste', + PROEVELSEN: 'Proevelsen', +} as const satisfies Record + +export const allAdmissions = Object.values(Admission) diff --git a/src/services/admission/methods.ts b/src/services/admission/operations.ts similarity index 59% rename from src/services/admission/methods.ts rename to src/services/admission/operations.ts index e654b4f69..873080ece 100644 --- a/src/services/admission/methods.ts +++ b/src/services/admission/operations.ts @@ -1,32 +1,32 @@ import '@pn-server-only' -import { AdmissionSchemas } from './schemas' -import { AdmissionAuthers } from './authers' -import { ServiceMethod } from '@/services/ServiceMethod' +import { admissionSchemas } from './schemas' +import { admissionAuth } from './auth' +import { userFilterSelection } from '@/services/users/constants' +import { defineOperation } from '@/services/serviceOperation' import { updateUserOmegaMembershipGroup } from '@/services/groups/omegaMembershipGroups/update' -import { UserConfig } from '@/services/users/config' import { Admission } from '@prisma/client' import { z } from 'zod' -import type { ExpandedAdmissionTrail } from './Types' +import type { ExpandedAdmissionTrail } from './types' -export namespace AdmissionMethods { - export const readTrial = ServiceMethod({ - auther: () => AdmissionAuthers.readTrial.dynamicFields({}), +export const admissionOperations = { + readTrial: defineOperation({ + authorizer: () => admissionAuth.readTrial.dynamicFields({}), paramsSchema: z.object({ userId: z.number(), }), - method: async ({ prisma, params: { userId } }) => await prisma.admissionTrial.findMany({ + operation: async ({ prisma, params: { userId } }) => await prisma.admissionTrial.findMany({ where: { userId, } }) - }) - export const createTrial = ServiceMethod({ - auther: () => AdmissionAuthers.createTrial.dynamicFields({}), + }), + createTrial: defineOperation({ + authorizer: () => admissionAuth.createTrial.dynamicFields({}), paramsSchema: z.object({ admission: z.nativeEnum(Admission), }), - dataSchema: AdmissionSchemas.createTrial, - method: async ({ prisma, session, params, data }): Promise => { + dataSchema: admissionSchemas.createTrial, + operation: async ({ prisma, session, params, data }): Promise => { const results = await prisma.admissionTrial.create({ data: { user: { @@ -43,14 +43,13 @@ export namespace AdmissionMethods { }, include: { user: { - select: UserConfig.filterSelection + select: userFilterSelection, } } }) // check if user has taken all admissions - const userTrials = await readTrial.newClient().execute({ - session, + const userTrials = await admissionOperations.readTrial({ params: { userId: data.userId }, @@ -62,5 +61,5 @@ export namespace AdmissionMethods { return results } - }) + }), } diff --git a/src/services/admission/schemas.ts b/src/services/admission/schemas.ts index 3ace62c7b..630b7b210 100644 --- a/src/services/admission/schemas.ts +++ b/src/services/admission/schemas.ts @@ -1,10 +1,11 @@ import { z } from 'zod' -export namespace AdmissionSchemas { - const fields = z.object({ - userId: z.coerce.number(), - }) - export const createTrial = fields.pick({ +const baseSchema = z.object({ + userId: z.coerce.number(), +}) + +export const admissionSchemas = { + createTrial: baseSchema.pick({ userId: true, }) } diff --git a/src/services/admission/Types.ts b/src/services/admission/types.ts similarity index 69% rename from src/services/admission/Types.ts rename to src/services/admission/types.ts index c245088ce..0c3b005d4 100644 --- a/src/services/admission/Types.ts +++ b/src/services/admission/types.ts @@ -1,5 +1,5 @@ import type { AdmissionTrial } from '@prisma/client' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' export type ExpandedAdmissionTrail = AdmissionTrial & { user: UserFiltered diff --git a/src/services/api-keys/Types.ts b/src/services/api-keys/Types.ts deleted file mode 100644 index 3bd68ad70..000000000 --- a/src/services/api-keys/Types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ApiKeyConfig } from './config' -import type { ApiKey } from '@prisma/client' - -export type ApiKeyFiltered = Pick - -export type ApiKeyFilteredWithKey = ApiKeyFiltered & { - key: string -} diff --git a/src/services/api-keys/authers.ts b/src/services/api-keys/authers.ts deleted file mode 100644 index 7c92c6325..000000000 --- a/src/services/api-keys/authers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace ApiKeyAuthers { - export const create = RequirePermission.staticFields({ permission: 'APIKEY_ADMIN' }) - export const read = RequirePermission.staticFields({ permission: 'APIKEY_ADMIN' }) - export const readMany = RequirePermission.staticFields({ permission: 'APIKEY_ADMIN' }) - export const readWithHash = RequirePermission.staticFields({ permission: 'APIKEY_ADMIN' }) - export const update = RequirePermission.staticFields({ permission: 'APIKEY_ADMIN' }) - export const updateIfExpired = RequirePermission.staticFields({ permission: 'APIKEY_ADMIN' }) - export const destroy = RequirePermission.staticFields({ permission: 'APIKEY_ADMIN' }) -} diff --git a/src/services/api-keys/config.ts b/src/services/api-keys/config.ts deleted file mode 100644 index 7376d7402..000000000 --- a/src/services/api-keys/config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createSelection } from '@/services/createSelection' -import type { ApiKey } from '@prisma/client' - - -export namespace ApiKeyConfig { - export const fieldsToExpose = [ - 'id', - 'active', - 'createdAt', - 'updatedAt', - 'name', - 'permissions', - 'expiresAt' - ] as const satisfies (keyof ApiKey)[] - - export const filterSelection = createSelection([...fieldsToExpose]) - export const keyLength = 32 as const -} diff --git a/src/services/api-keys/schemas.ts b/src/services/api-keys/schemas.ts deleted file mode 100644 index 4cc875129..000000000 --- a/src/services/api-keys/schemas.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { zpn } from '@/lib/fields/zpn' -import { z } from 'zod' -import { Permission } from '@prisma/client' - -export namespace ApiKeySchemas { - const fields = z.object({ - name: z.string().min(10, 'minimum lengde 10').max(100, 'maksimum lengde 100'), - expiresAt: z.coerce.date().optional(), - active: zpn.checkboxOrBoolean({ label: 'Aktiv' }), - permissions: zpn.enumListCheckboxFriendly({ label: 'Tillatelser', enum: Permission }) - }) - export const create = fields.pick({ - name: true, - }) - export const update = fields.partial().pick({ - name: true, - expiresAt: true, - active: true, - permissions: true - }) -} diff --git a/src/services/apiKeys/actions.ts b/src/services/apiKeys/actions.ts new file mode 100644 index 000000000..d3834fbc0 --- /dev/null +++ b/src/services/apiKeys/actions.ts @@ -0,0 +1,13 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { apiKeyOperations } from '@/services/apiKeys/operations' + +export const createApiKeyAction = makeAction(apiKeyOperations.create) + +export const destroyApiKeyAction = makeAction(apiKeyOperations.destroy) + +export const readApiKeysAction = makeAction(apiKeyOperations.readMany) +export const readApiKeyAction = makeAction(apiKeyOperations.read) + +export const updateApiKeyAction = makeAction(apiKeyOperations.update) diff --git a/src/services/api-keys/apiKeyEncoder.ts b/src/services/apiKeys/apiKeyEncoder.ts similarity index 100% rename from src/services/api-keys/apiKeyEncoder.ts rename to src/services/apiKeys/apiKeyEncoder.ts diff --git a/src/services/apiKeys/auth.ts b/src/services/apiKeys/auth.ts new file mode 100644 index 000000000..ccf82ffb7 --- /dev/null +++ b/src/services/apiKeys/auth.ts @@ -0,0 +1,13 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +const baseAuther = RequirePermission.staticFields({ permission: 'APIKEY_ADMIN' }) + +export const apiKeyAuth = { + create: baseAuther, + read: baseAuther, + readMany: baseAuther, + readWithHash: baseAuther, + update: baseAuther, + updateIfExpired: baseAuther, + destroy: baseAuther, +} diff --git a/src/services/apiKeys/constants.ts b/src/services/apiKeys/constants.ts new file mode 100644 index 000000000..5c08a5cf2 --- /dev/null +++ b/src/services/apiKeys/constants.ts @@ -0,0 +1,15 @@ +import { createSelection } from '@/services/createSelection' +import type { ApiKey } from '@prisma/client' + +export const apiKeyFieldsToExpose = [ + 'id', + 'active', + 'createdAt', + 'updatedAt', + 'name', + 'permissions', + 'expiresAt' +] as const satisfies (keyof ApiKey)[] + +export const apiFilterSelection = createSelection([...apiKeyFieldsToExpose]) +export const apiKeyLength = 32 as const diff --git a/src/services/api-keys/hashEncryptKey.ts b/src/services/apiKeys/hashEncryptKey.ts similarity index 100% rename from src/services/api-keys/hashEncryptKey.ts rename to src/services/apiKeys/hashEncryptKey.ts diff --git a/src/services/api-keys/methods.ts b/src/services/apiKeys/operations.ts similarity index 52% rename from src/services/api-keys/methods.ts rename to src/services/apiKeys/operations.ts index 751f60514..a6999da9e 100644 --- a/src/services/api-keys/methods.ts +++ b/src/services/apiKeys/operations.ts @@ -1,54 +1,57 @@ import '@pn-server-only' -import { ApiKeyAuthers } from './authers' -import { ApiKeyConfig } from './config' -import { ApiKeySchemas } from './schemas' +import { apiKeyAuth } from './auth' +import { apiKeySchemas } from './schemas' import { apiKeyHashAndEncrypt } from './hashEncryptKey' import { encodeApiKey } from './apiKeyEncoder' +import { apiFilterSelection, apiKeyLength } from './constants' import { ServerError } from '@/services/error' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import logger from '@/lib/logger' import { z } from 'zod' import crypto from 'crypto' -import type { ApiKeyFiltered, ApiKeyFilteredWithKey } from './Types' +import type { ApiKeyFiltered, ApiKeyFilteredWithKey } from './types' -export namespace ApiKeyMethods { - /** - * Updates the active status of an api key if it has expired, i.e. if the expiresAt date is in the past. - * This method is used when reading api keys to ensure that the active status is correct. - */ - const updateIfExpired = ServiceMethod({ - auther: () => ApiKeyAuthers.updateIfExpired.dynamicFields({}), - paramsSchema: z.object({ - id: z.number(), - expiresAt: z.date().nullable(), - active: z.boolean(), - }), - method: async ({ prisma, params: apiKey }) => { - if (!apiKey) { - throw new ServerError('NOT FOUND', 'Nøkkelen finnes ikke') - } +/** + * Updates the active status of an api key if it has expired, i.e. if the expiresAt date is in the past. + * This method is used when reading api keys to ensure that the active status is correct. + * + * Note: This operaiton is only used internally. + */ +const updateIfExpired = defineOperation({ + authorizer: () => apiKeyAuth.updateIfExpired.dynamicFields({}), + paramsSchema: z.object({ + id: z.number(), + expiresAt: z.date().nullable(), + active: z.boolean(), + }), + operation: async ({ prisma, params: apiKey }) => { + if (!apiKey) { + throw new ServerError('NOT FOUND', 'Nøkkelen finnes ikke') + } - if (!apiKey.expiresAt || apiKey.expiresAt > new Date()) return { active: apiKey.active } - logger.info('Deactivating expired api key', { id: apiKey.id }) + if (!apiKey.expiresAt || apiKey.expiresAt > new Date()) return { active: apiKey.active } + logger.info('Deactivating expired api key', { id: apiKey.id }) - const updated = await prisma.apiKey.update({ - where: { id: apiKey.id }, - data: { active: false }, - select: { active: true }, - }) - return { - active: updated.active, - } + const updated = await prisma.apiKey.update({ + where: { id: apiKey.id }, + data: { active: false }, + select: { active: true }, + }) + return { + active: updated.active, } - }) - export const create = ServiceMethod({ - auther: () => ApiKeyAuthers.create.dynamicFields({}), - dataSchema: ApiKeySchemas.create, - method: async ({ prisma, data }): Promise => { + } +}) + +export const apiKeyOperations = { + create: defineOperation({ + authorizer: () => apiKeyAuth.create.dynamicFields({}), + dataSchema: apiKeySchemas.create, + operation: async ({ prisma, data }): Promise => { const NODE_ENV = process.env.NODE_ENV const prepend = NODE_ENV === 'production' ? 'prod' : 'dev' - const key = prepend + crypto.randomBytes(ApiKeyConfig.keyLength - prepend.length).toString('hex') + const key = prepend + crypto.randomBytes(apiKeyLength - prepend.length).toString('hex') const keyHashEncrypted = await apiKeyHashAndEncrypt(key) const apiKey = await prisma.apiKey.create({ @@ -57,39 +60,38 @@ export namespace ApiKeyMethods { name: data.name, active: true, }, - select: ApiKeyConfig.filterSelection + select: apiFilterSelection, }) return { ...apiKey, key: encodeApiKey({ key, id: apiKey.id }) } } - }) - export const read = ServiceMethod({ - auther: () => ApiKeyAuthers.read.dynamicFields({}), + }), + read: defineOperation({ + authorizer: () => apiKeyAuth.read.dynamicFields({}), paramsSchema: z.union([z.object({ id: z.number() }), z.object({ name: z.string() })]), - method: async ({ prisma, params }): Promise => { + operation: async ({ prisma, params }): Promise => { const apiKey = await prisma.apiKey.findUnique({ where: { id: 'id' in params ? params.id : undefined, name: 'name' in params ? params.name : undefined, }, - select: ApiKeyConfig.filterSelection + select: apiFilterSelection, }) if (!apiKey) throw new ServerError('BAD PARAMETERS', 'Api key does not exist') return { ...apiKey, - ...await updateIfExpired.client(prisma).execute({ + ...await updateIfExpired({ params: apiKey, - session: null, bypassAuth: true, }) } } - }) - export const readMany = ServiceMethod({ - auther: () => ApiKeyAuthers.readMany.dynamicFields({}), - method: async ({ prisma }): Promise => { + }), + readMany: defineOperation({ + authorizer: () => apiKeyAuth.readMany.dynamicFields({}), + operation: async ({ prisma }): Promise => { const apiKeys = await prisma.apiKey.findMany({ - select: ApiKeyConfig.filterSelection, + select: apiFilterSelection, orderBy: [ { active: 'desc' }, { name: 'asc' } @@ -98,20 +100,19 @@ export namespace ApiKeyMethods { return await Promise.all(apiKeys.map(async apiKey => ({ ...apiKey, - ...await updateIfExpired.client(prisma).execute({ + ...await updateIfExpired({ params: apiKey, - session: null, bypassAuth: true, }) }))) } - }) - export const readWithHash = ServiceMethod({ - auther: () => ApiKeyAuthers.readWithHash.dynamicFields({}), + }), + readWithHash: defineOperation({ + authorizer: () => apiKeyAuth.readWithHash.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { const apiKey = await prisma.apiKey.findUniqueOrThrow({ where: { id: params.id }, select: { @@ -125,21 +126,20 @@ export namespace ApiKeyMethods { return { ...apiKey, - ...await updateIfExpired.client(prisma).execute({ + ...await updateIfExpired({ params: apiKey, - session: null, bypassAuth: true, }), } } - }) - export const update = ServiceMethod({ - auther: () => ApiKeyAuthers.update.dynamicFields({}), + }), + update: defineOperation({ + authorizer: () => apiKeyAuth.update.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - dataSchema: ApiKeySchemas.update, - method: async ({ prisma, params, data }) => { + dataSchema: apiKeySchemas.update, + operation: async ({ prisma, params, data }) => { if (data.active && data.expiresAt && data.expiresAt < new Date()) { throw new ServerError('BAD PARAMETERS', 'Hvis du vil aktivere en nøkkel, kan den ikke ha utløpt') } @@ -150,14 +150,14 @@ export namespace ApiKeyMethods { }) return { name } }, - }) - export const destroy = ServiceMethod({ - auther: () => ApiKeyAuthers.destroy.dynamicFields({}), + }), + destroy: defineOperation({ + authorizer: () => apiKeyAuth.destroy.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), opensTransaction: true, - method: async ({ prisma, params }): Promise => { + operation: async ({ prisma, params }): Promise => { await prisma.$transaction(async (tx) => { const apiKey = await tx.apiKey.findUniqueOrThrow({ where: { id: params.id }, @@ -173,5 +173,5 @@ export namespace ApiKeyMethods { }) }) } - }) + }), } diff --git a/src/services/apiKeys/schemas.ts b/src/services/apiKeys/schemas.ts new file mode 100644 index 000000000..54ba6ea63 --- /dev/null +++ b/src/services/apiKeys/schemas.ts @@ -0,0 +1,22 @@ +import { Zpn } from '@/lib/fields/zpn' +import { z } from 'zod' +import { Permission } from '@prisma/client' + +const baseSchema = z.object({ + name: z.string().min(10, 'minimum lengde 10').max(100, 'maksimum lengde 100'), + expiresAt: z.coerce.date().optional(), + active: Zpn.checkboxOrBoolean({ label: 'Aktiv' }), + permissions: Zpn.enumListCheckboxFriendly({ label: 'Tillatelser', enum: Permission }) +}) + +export const apiKeySchemas = { + create: baseSchema.pick({ + name: true, + }), + update: baseSchema.partial().pick({ + name: true, + expiresAt: true, + active: true, + permissions: true + }), +} diff --git a/src/services/apiKeys/types.ts b/src/services/apiKeys/types.ts new file mode 100644 index 000000000..32d878a71 --- /dev/null +++ b/src/services/apiKeys/types.ts @@ -0,0 +1,8 @@ +import type { apiKeyFieldsToExpose } from './constants' +import type { ApiKey } from '@prisma/client' + +export type ApiKeyFiltered = Pick + +export type ApiKeyFilteredWithKey = ApiKeyFiltered & { + key: string +} diff --git a/src/services/applications/actions.ts b/src/services/applications/actions.ts new file mode 100644 index 000000000..0c9cd3932 --- /dev/null +++ b/src/services/applications/actions.ts @@ -0,0 +1,12 @@ +'use server' + +import { applicationOperations } from './operations' +import { makeAction } from '@/services/serverAction' + +export const createApplicationAction = makeAction(applicationOperations.create) + +export const destroyApplicationAction = makeAction(applicationOperations.destroy) + +export const readApplicationsForUserAction = makeAction(applicationOperations.readForUser) + +export const updateApplicationAction = makeAction(applicationOperations.update) diff --git a/src/services/applications/auth.ts b/src/services/applications/auth.ts new file mode 100644 index 000000000..94d1c947b --- /dev/null +++ b/src/services/applications/auth.ts @@ -0,0 +1,8 @@ +import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' + +export const applicationAuth = { + readForUser: RequireUserIdOrPermission.staticFields({ permission: 'APPLICATION_ADMIN' }), + create: RequireUserIdOrPermission.staticFields({ permission: 'APPLICATION_ADMIN' }), + update: RequireUserIdOrPermission.staticFields({ permission: 'APPLICATION_ADMIN' }), + destroy: RequireUserIdOrPermission.staticFields({ permission: 'APPLICATION_ADMIN' }), +} diff --git a/src/services/applications/authers.ts b/src/services/applications/authers.ts deleted file mode 100644 index e545e43e9..000000000 --- a/src/services/applications/authers.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' - -export namespace ApplicationAuthers { - export const readForUser = RequireUserIdOrPermission.staticFields({ permission: 'APPLICATION_ADMIN' }) - export const create = RequireUserIdOrPermission.staticFields({ permission: 'APPLICATION_ADMIN' }) - export const update = RequireUserIdOrPermission.staticFields({ permission: 'APPLICATION_ADMIN' }) - export const destroy = RequireUserIdOrPermission.staticFields({ permission: 'APPLICATION_ADMIN' }) -} diff --git a/src/services/applications/methods.ts b/src/services/applications/operations.ts similarity index 88% rename from src/services/applications/methods.ts rename to src/services/applications/operations.ts index 32cb9f594..3213fd9b0 100644 --- a/src/services/applications/methods.ts +++ b/src/services/applications/operations.ts @@ -1,33 +1,33 @@ import '@pn-server-only' -import { ApplicationAuthers } from './authers' -import { ApplicationSchemas } from './schemas' +import { applicationAuth } from './auth' +import { applicationSchemas } from './schemas' import { ServerError } from '@/services/error' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import { z } from 'zod' -export namespace ApplicationMethods { - export const readForUser = ServiceMethod({ +export const applicationOperations = { + readForUser: defineOperation({ paramsSchema: z.object({ userId: z.number(), periodId: z.number() }), - auther: ({ params }) => ApplicationAuthers.readForUser.dynamicFields({ userId: params.userId }), - method: async ({ prisma, params }) => prisma.application.findMany({ + authorizer: ({ params }) => applicationAuth.readForUser.dynamicFields({ userId: params.userId }), + operation: async ({ prisma, params }) => prisma.application.findMany({ where: { userId: params.userId, applicationPeriodId: params.periodId } }) - }) + }), - export const create = ServiceMethod({ - dataSchema: ApplicationSchemas.create, + create: defineOperation({ + dataSchema: applicationSchemas.create, paramsSchema: z.object({ userId: z.number(), commiteeParticipationId: z.number() }), - auther: ({ params }) => ApplicationAuthers.create.dynamicFields({ userId: params.userId }), - method: async ({ prisma, data, params }) => { + authorizer: ({ params }) => applicationAuth.create.dynamicFields({ userId: params.userId }), + operation: async ({ prisma, data, params }) => { const commiteeParticipation = await prisma.committeeParticipationInApplicationPeriod.findUniqueOrThrow({ where: { id: params.commiteeParticipationId @@ -76,17 +76,17 @@ export namespace ApplicationMethods { } }) }, - }) + }), - export const update = ServiceMethod({ - dataSchema: ApplicationSchemas.update, + update: defineOperation({ + dataSchema: applicationSchemas.update, paramsSchema: z.object({ userId: z.number(), commiteeParticipationId: z.number() }), opensTransaction: true, - auther: ({ params }) => ApplicationAuthers.update.dynamicFields({ userId: params.userId }), - method: async ({ prisma, data, params }) => { + authorizer: ({ params }) => applicationAuth.update.dynamicFields({ userId: params.userId }), + operation: async ({ prisma, data, params }) => { const application = await prisma.application.findUniqueOrThrow({ where: { userId_applicationPeriodCommiteeId: { @@ -192,16 +192,16 @@ export namespace ApplicationMethods { }) }) }, - }) + }), - export const destroy = ServiceMethod({ + destroy: defineOperation({ paramsSchema: z.object({ userId: z.number(), commiteeParticipationId: z.number() }), - auther: ({ params }) => ApplicationAuthers.destroy.dynamicFields({ userId: params.userId }), + authorizer: ({ params }) => applicationAuth.destroy.dynamicFields({ userId: params.userId }), opensTransaction: true, - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { prisma.$transaction(async (tx) => { const application = await tx.application.delete({ where: { @@ -245,5 +245,5 @@ export namespace ApplicationMethods { }) }) } - }) + }), } diff --git a/src/services/applications/periods/actions.ts b/src/services/applications/periods/actions.ts new file mode 100644 index 000000000..e9003ad4b --- /dev/null +++ b/src/services/applications/periods/actions.ts @@ -0,0 +1,15 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { applicationPeriodOperations } from '@/services/applications/periods/operations' + +export const createApplicationPeriodAction = makeAction(applicationPeriodOperations.create) + +export const destroyApplicationPeriodAction = makeAction(applicationPeriodOperations.destroy) +export const removeAllApplicationTextsAction = makeAction(applicationPeriodOperations.removeAllApplicationTexts) + +export const readApplicationPeriodsAction = makeAction(applicationPeriodOperations.readAll) +export const readApplicationPeriodAction = makeAction(applicationPeriodOperations.read) +export const readNumberOfApplicationsAction = makeAction(applicationPeriodOperations.readNumberOfApplications) + +export const updateApplicationPeriodAction = makeAction(applicationPeriodOperations.update) diff --git a/src/services/applications/periods/auth.ts b/src/services/applications/periods/auth.ts new file mode 100644 index 000000000..dabdf974d --- /dev/null +++ b/src/services/applications/periods/auth.ts @@ -0,0 +1,11 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const applicationPeriodAuth = { + readAll: RequirePermission.staticFields({ permission: 'APPLICATION_WRITE' }), + read: RequirePermission.staticFields({ permission: 'APPLICATION_WRITE' }), + readNumberOfApplications: RequirePermission.staticFields({ permission: 'APPLICATION_WRITE' }), + create: RequirePermission.staticFields({ permission: 'APPLICATION_ADMIN' }), + update: RequirePermission.staticFields({ permission: 'APPLICATION_ADMIN' }), + removeAllApplicationTexts: RequirePermission.staticFields({ permission: 'APPLICATION_ADMIN' }), + destroy: RequirePermission.staticFields({ permission: 'APPLICATION_ADMIN' }), +} diff --git a/src/services/applications/periods/authers.ts b/src/services/applications/periods/authers.ts deleted file mode 100644 index da2046b1f..000000000 --- a/src/services/applications/periods/authers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace ApplicationPeriodAuthers { - export const readAll = RequirePermission.staticFields({ permission: 'APPLICATION_WRITE' }) - export const read = RequirePermission.staticFields({ permission: 'APPLICATION_WRITE' }) - export const readNumberOfApplications = RequirePermission.staticFields({ permission: 'APPLICATION_WRITE' }) - export const create = RequirePermission.staticFields({ permission: 'APPLICATION_ADMIN' }) - export const update = RequirePermission.staticFields({ permission: 'APPLICATION_ADMIN' }) - export const removeAllApplicationTexts = RequirePermission.staticFields({ permission: 'APPLICATION_ADMIN' }) - export const destroy = RequirePermission.staticFields({ permission: 'APPLICATION_ADMIN' }) -} diff --git a/src/services/applications/periods/config.ts b/src/services/applications/periods/config.ts deleted file mode 100644 index 44249731a..000000000 --- a/src/services/applications/periods/config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Prisma } from '@prisma/client' - -export namespace ApplicationPeriodConfig { - export const includer = { - committeesParticipating: { - include: { - committee: { - include: { - logoImage: { - include: { - image: true - } - }, - paragraph: true - } - } - } - } - } as const satisfies Prisma.ApplicationPeriodInclude -} diff --git a/src/services/applications/periods/constants.ts b/src/services/applications/periods/constants.ts new file mode 100644 index 000000000..f213b5360 --- /dev/null +++ b/src/services/applications/periods/constants.ts @@ -0,0 +1,18 @@ +import type { Prisma } from '@prisma/client' + +export const committeesParticipatingincluder = { + committeesParticipating: { + include: { + committee: { + include: { + logoImage: { + include: { + image: true + } + }, + paragraph: true + } + } + } + } +} as const satisfies Prisma.ApplicationPeriodInclude diff --git a/src/services/applications/periods/methods.ts b/src/services/applications/periods/operations.ts similarity index 70% rename from src/services/applications/periods/methods.ts rename to src/services/applications/periods/operations.ts index 5a4ce75ff..c0c6a1b99 100644 --- a/src/services/applications/periods/methods.ts +++ b/src/services/applications/periods/operations.ts @@ -1,33 +1,33 @@ import '@pn-server-only' -import { ApplicationPeriodAuthers } from './authers' -import { ApplicationPeriodSchemas } from './schemas' -import { ApplicationPeriodConfig } from './config' -import { ServiceMethod } from '@/services/ServiceMethod' -import { ApplicationMethods } from '@/services/applications/methods' +import { applicationPeriodAuth } from './auth' +import { applicationPeriodSchemas } from './schemas' +import { committeesParticipatingincluder } from './constants' +import { applicationOperations } from '@/services/applications/operations' import { ServerError } from '@/services/error' +import { defineOperation } from '@/services/serviceOperation' import { z } from 'zod' -export namespace ApplicationPeriodMethods { - export const readAll = ServiceMethod({ - auther: () => ApplicationPeriodAuthers.readAll.dynamicFields({}), - method: async ({ prisma }) => prisma.applicationPeriod.findMany() - }) +export const applicationPeriodOperations = { + readAll: defineOperation({ + authorizer: () => applicationPeriodAuth.readAll.dynamicFields({}), + operation: async ({ prisma }) => prisma.applicationPeriod.findMany() + }), - export const read = ServiceMethod({ - auther: () => ApplicationPeriodAuthers.read.dynamicFields({}), + read: defineOperation({ + authorizer: () => applicationPeriodAuth.read.dynamicFields({}), paramsSchema: z.object({ name: z.string() }), - method: async ({ prisma, params }) => prisma.applicationPeriod.findUniqueOrThrow({ + operation: async ({ prisma, params }) => prisma.applicationPeriod.findUniqueOrThrow({ where: { name: params.name }, - include: ApplicationPeriodConfig.includer + include: committeesParticipatingincluder, }) - }) + }), - export const create = ServiceMethod({ - auther: () => ApplicationPeriodAuthers.create.dynamicFields({}), - dataSchema: ApplicationPeriodSchemas.create, - method: async ({ prisma, data }) => { + create: defineOperation({ + authorizer: () => applicationPeriodAuth.create.dynamicFields({}), + dataSchema: applicationPeriodSchemas.create, + operation: async ({ prisma, data }) => { await prisma.applicationPeriod.create({ data: { name: data.name, @@ -41,15 +41,15 @@ export namespace ApplicationPeriodMethods { }) return { name: data.name } } - }) + }), - export const update = ServiceMethod({ - auther: () => ApplicationPeriodAuthers.update.dynamicFields({}), - dataSchema: ApplicationPeriodSchemas.update, + update: defineOperation({ + authorizer: () => applicationPeriodAuth.update.dynamicFields({}), + dataSchema: applicationPeriodSchemas.update, paramsSchema: z.object({ name: z.string() }), - method: async ({ prisma, data, params, session }) => { + operation: async ({ prisma, data, params }) => { const period = await prisma.applicationPeriod.update({ where: { name: params.name }, data: { @@ -69,7 +69,7 @@ export namespace ApplicationPeriodMethods { if (data.participatingCommitteeIds) { // Remove applications to committees that are no longer participating - // This must be done through the ApplicationMethods.destroy method to ensure + // This must be done through the applicationOperations.destroy method to ensure // that reordering priorities is handled correctly. await Promise.all( period.committeesParticipating @@ -90,12 +90,11 @@ export namespace ApplicationPeriodMethods { }) await Promise.all( removeApplications.map(async application => - await ApplicationMethods.destroy.newClient().execute({ + await applicationOperations.destroy({ params: { userId: application.userId, commiteeParticipationId: application.applicationPeriodCommiteeId }, - session, }) ) ) @@ -121,15 +120,15 @@ export namespace ApplicationPeriodMethods { return { name: period.name } } - }) + }), - export const removeAllApplicationTexts = ServiceMethod({ + removeAllApplicationTexts: defineOperation({ paramsSchema: z.object({ name: z.string() }), - auther: () => ApplicationPeriodAuthers.removeAllApplicationTexts.dynamicFields({}), - method: async ({ prisma, params, session }) => { - const period = await read.client(prisma).execute({ + authorizer: () => applicationPeriodAuth.removeAllApplicationTexts.dynamicFields({}), + operation: async ({ prisma, params, session }) => { + const period = await applicationPeriodOperations.read({ params: { name: params.name }, session }) @@ -147,26 +146,26 @@ export namespace ApplicationPeriodMethods { } }) } - }) + }), - export const destroy = ServiceMethod({ - auther: () => ApplicationPeriodAuthers.destroy.dynamicFields({}), + destroy: defineOperation({ + authorizer: () => applicationPeriodAuth.destroy.dynamicFields({}), paramsSchema: z.object({ name: z.string() }), - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { await prisma.applicationPeriod.delete({ where: { name: params.name } }) } - }) + }), - export const readNumberOfApplications = ServiceMethod({ - auther: () => ApplicationPeriodAuthers.readNumberOfApplications.dynamicFields({}), + readNumberOfApplications: defineOperation({ + authorizer: () => applicationPeriodAuth.readNumberOfApplications.dynamicFields({}), paramsSchema: z.object({ name: z.string() }), - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { const period = await prisma.applicationPeriod.findUniqueOrThrow({ where: { name: params.name }, select: { @@ -179,5 +178,5 @@ export namespace ApplicationPeriodMethods { }, }) } - }) + }), } diff --git a/src/services/applications/periods/schemas.ts b/src/services/applications/periods/schemas.ts index 2c645b70d..9468860fd 100644 --- a/src/services/applications/periods/schemas.ts +++ b/src/services/applications/periods/schemas.ts @@ -1,37 +1,37 @@ -import { zpn } from '@/lib/fields/zpn' +import { Zpn } from '@/lib/fields/zpn' import { z } from 'zod' -export namespace ApplicationPeriodSchemas { - const fields = z.object({ - name: z.string().trim().min(2, { message: 'Navnet må være minst 2 tegn.' }), - startDate: zpn.date({ label: 'Start' }), - endDate: zpn.date({ label: 'Siste frist for søknader' }), - endPriorityDate: zpn.date({ label: 'Siste frist for prioritering' }), - participatingCommitteeIds: zpn.numberListCheckboxFriendly({ label: 'Deltakende komiteer' }) - }) +const fields = z.object({ + name: z.string().trim().min(2, { message: 'Navnet må være minst 2 tegn.' }), + startDate: Zpn.date({ label: 'Start' }), + endDate: Zpn.date({ label: 'Siste frist for søknader' }), + endPriorityDate: Zpn.date({ label: 'Siste frist for prioritering' }), + participatingCommitteeIds: Zpn.numberListCheckboxFriendly({ label: 'Deltakende komiteer' }) +}) - const refineDates = { - fcn: (data: { startDate?: Date, endDate?: Date, endPriorityDate?: Date }) => { - if (!data.startDate && !data.endDate && !data.endPriorityDate) return true - if (!data.startDate || !data.endDate || !data.endPriorityDate) return false - return data.startDate < data.endDate && data.endDate <= data.endPriorityDate - }, - message: 'Starttidspunktet må være før sluttidspunktet.' - } +const refineDates = { + fcn: (data: { startDate?: Date, endDate?: Date, endPriorityDate?: Date }) => { + if (!data.startDate && !data.endDate && !data.endPriorityDate) return true + if (!data.startDate || !data.endDate || !data.endPriorityDate) return false + return data.startDate < data.endDate && data.endDate <= data.endPriorityDate + }, + message: 'Starttidspunktet må være før sluttidspunktet.' +} - export const create = fields.pick({ +export const applicationPeriodSchemas = { + create: fields.pick({ name: true, startDate: true, endDate: true, endPriorityDate: true, participatingCommitteeIds: true - }).refine(refineDates.fcn, refineDates.message) + }).refine(refineDates.fcn, refineDates.message), - export const update = fields.pick({ + update: fields.pick({ name: true, startDate: true, endDate: true, endPriorityDate: true, participatingCommitteeIds: true - }).partial().refine(refineDates.fcn, refineDates.message) + }).partial().refine(refineDates.fcn, refineDates.message), } diff --git a/src/services/applications/periods/Types.ts b/src/services/applications/periods/types.ts similarity index 69% rename from src/services/applications/periods/Types.ts rename to src/services/applications/periods/types.ts index 737af9dbf..36ba710da 100644 --- a/src/services/applications/periods/Types.ts +++ b/src/services/applications/periods/types.ts @@ -1,4 +1,4 @@ -import type { ApplicationPeriodConfig } from './config' +import type { committeesParticipatingincluder } from './constants' import type { Image, Prisma } from '@prisma/client' export type CountdownInfo = { @@ -10,5 +10,5 @@ export type CountdownInfo = { } export type ExpandedApplicationPeriod = Prisma.ApplicationPeriodGetPayload<{ - include: typeof ApplicationPeriodConfig.includer + include: typeof committeesParticipatingincluder }> diff --git a/src/services/applications/schemas.ts b/src/services/applications/schemas.ts index 01d4ca622..3ee37772e 100644 --- a/src/services/applications/schemas.ts +++ b/src/services/applications/schemas.ts @@ -1,21 +1,21 @@ import { z } from 'zod' -export namespace ApplicationSchemas { - const fields = z.object({ - text: z.string().trim().min( - 1, { message: 'Søknadsteksten kan ikke være tom.' } - ).max( - 1000, { message: 'Søknadsteksten kan ikke være lengre enn 1000 tegn.' } - ), - priority: z.literal('UP').or(z.literal('DOWN')) - }) +const baseSchema = z.object({ + text: z.string().trim().min( + 1, { message: 'Søknadsteksten kan ikke være tom.' } + ).max( + 1000, { message: 'Søknadsteksten kan ikke være lengre enn 1000 tegn.' } + ), + priority: z.literal('UP').or(z.literal('DOWN')) +}) - export const create = fields.pick({ +export const applicationSchemas = { + create: baseSchema.pick({ text: true, - }) + }), - export const update = fields.pick({ + update: baseSchema.pick({ text: true, priority: true - }).partial() + }).partial(), } diff --git a/src/services/auth/actions.ts b/src/services/auth/actions.ts new file mode 100644 index 000000000..a2944f8d1 --- /dev/null +++ b/src/services/auth/actions.ts @@ -0,0 +1,9 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { authOperations } from '@/services/auth/operations' + +export const verifyResetPasswordTokenAction = makeAction(authOperations.verifyResetPasswordToken) +export const resetPasswordAction = makeAction(authOperations.resetPassword) +export const sendResetPasswordEmailAction = makeAction(authOperations.sendResetPasswordEmail) +export const verifyEmailAction = makeAction(authOperations.verifyEmail) diff --git a/src/services/auth/auth.ts b/src/services/auth/auth.ts new file mode 100644 index 000000000..aeea9db52 --- /dev/null +++ b/src/services/auth/auth.ts @@ -0,0 +1,9 @@ +import { RequireJWT } from '@/auth/auther/RequireJWT' +import { RequireNothing } from '@/auth/auther/RequireNothing' + +// TODO: A better name lol +export const authAuth = { + verifyEmail: RequireJWT.staticFields({ audience: 'verifyemail' }), + resetPassword: RequireJWT.staticFields({ audience: 'resetpassword' }), + sendResetPasswordEmail: RequireNothing.staticFields({}), +} diff --git a/src/services/auth/authers.ts b/src/services/auth/authers.ts deleted file mode 100644 index 3b9e0a99b..000000000 --- a/src/services/auth/authers.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { RequireJWT } from '@/auth/auther/RequireJWT' -import { RequireNothing } from '@/auth/auther/RequireNothing' - -export namespace AuthAuthers { - export const verifyEmail = RequireJWT.staticFields({ audience: 'verifyemail' }) - export const resetPassword = RequireJWT.staticFields({ audience: 'resetpassword' }) - export const sendResetPasswordEmail = RequireNothing.staticFields({}) -} diff --git a/src/services/auth/feideAccounts/create.ts b/src/services/auth/feideAccounts/create.ts index a3fdf6f6c..97aabd6ae 100644 --- a/src/services/auth/feideAccounts/create.ts +++ b/src/services/auth/feideAccounts/create.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { FeideAccount } from '@prisma/client' export async function createFeideAccount({ diff --git a/src/services/auth/feideAccounts/read.ts b/src/services/auth/feideAccounts/read.ts index c250609c1..cd05cdc12 100644 --- a/src/services/auth/feideAccounts/read.ts +++ b/src/services/auth/feideAccounts/read.ts @@ -1,5 +1,5 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { User } from '@prisma/client' /** diff --git a/src/services/auth/feideAccounts/update.ts b/src/services/auth/feideAccounts/update.ts index 963b2a0b9..1877de04e 100644 --- a/src/services/auth/feideAccounts/update.ts +++ b/src/services/auth/feideAccounts/update.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import { readJWTPayload } from '@/jwt/jwtReadUnsecure' import { ServerError } from '@/services/error' diff --git a/src/services/auth/invalidateSession.ts b/src/services/auth/invalidateSession.ts index f37e8da52..3afbf26be 100644 --- a/src/services/auth/invalidateSession.ts +++ b/src/services/auth/invalidateSession.ts @@ -1,5 +1,5 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' /** * Invalidate a given user. diff --git a/src/services/auth/methods.ts b/src/services/auth/operations.ts similarity index 61% rename from src/services/auth/methods.ts rename to src/services/auth/operations.ts index 85459bb12..3b274bdea 100644 --- a/src/services/auth/methods.ts +++ b/src/services/auth/operations.ts @@ -1,22 +1,22 @@ -import { AuthAuthers } from './authers' -import { AuthSchemas } from './schemas' +import { authAuth } from './auth' +import { authSchemas } from './schemas' +import { userFilterSelection } from '@/services/users/constants' +import { userSchemas } from '@/services/users/schemas' import { sendResetPasswordMail } from '@/services/notifications/email/systemMail/resetPassword' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import { ServerError } from '@/services/error' -import { UserMethods } from '@/services/users/methods' -import { UserConfig } from '@/services/users/config' +import { userOperations } from '@/services/users/operations' import { readJWTPayload } from '@/lib/jwt/jwtReadUnsecure' -import { UserSchemas } from '@/services/users/schemas' import { z } from 'zod' -export namespace AuthMethods { +export const authOperations = { - export const verifyEmail = ServiceMethod({ + verifyEmail: defineOperation({ paramsSchema: z.object({ token: z.string(), }), - auther: ({ params }) => AuthAuthers.verifyEmail.dynamicFields(params), - method: async ({ prisma, params }) => { + authorizer: ({ params }) => authAuth.verifyEmail.dynamicFields(params), + operation: async ({ prisma, params }) => { // INFO: Safe to parse unsafe since the auther has verified the token. const payload = readJWTPayload(params.token) @@ -29,11 +29,10 @@ export namespace AuthMethods { const iat = new Date(payload.iat * 1000) - const user = await UserMethods.read.client(prisma).execute({ + const user = await userOperations.read({ params: { id: userId, }, - session: null, bypassAuth: true, }) @@ -49,17 +48,17 @@ export namespace AuthMethods { emailVerified: new Date(), email, }, - select: UserConfig.filterSelection + select: userFilterSelection, }) } - }) + }), - export const verifyResetPasswordToken = ServiceMethod({ + verifyResetPasswordToken: defineOperation({ paramsSchema: z.object({ token: z.string() }), - auther: ({ params }) => AuthAuthers.resetPassword.dynamicFields(params), - method: async ({ prisma, params }) => { + authorizer: ({ params }) => authAuth.resetPassword.dynamicFields(params), + operation: async ({ prisma, params }) => { // INFO: Safe to parse unsafe since the auther has verified the token. const payload = readJWTPayload(params.token) @@ -84,42 +83,37 @@ export namespace AuthMethods { return userId } - }) + }), - export const resetPassword = ServiceMethod({ + resetPassword: defineOperation({ paramsSchema: z.object({ token: z.string() }), - dataSchema: UserSchemas.updatePassword, - auther: ({ params }) => AuthAuthers.resetPassword.dynamicFields(params), - method: async ({ prisma, params, data, session }) => { - const userId = await verifyResetPasswordToken.client(prisma).executeUnsafe({ - params, - session, - }) + dataSchema: userSchemas.updatePassword, + authorizer: ({ params }) => authAuth.resetPassword.dynamicFields(params), + operation: async ({ params, data }) => { + const userId = await authOperations.verifyResetPasswordToken({ params }) - UserMethods.updatePassword.client(prisma).execute({ + userOperations.updatePassword({ params: { id: userId, }, data, - session, bypassAuth: true, }) } - }) + }), - export const sendResetPasswordEmail = ServiceMethod({ - dataSchema: AuthSchemas.sendResetPasswordEmail, - auther: () => AuthAuthers.sendResetPasswordEmail.dynamicFields({}), - method: async ({ prisma, data, session }) => { + sendResetPasswordEmail: defineOperation({ + dataSchema: authSchemas.sendResetPasswordEmail, + authorizer: () => authAuth.sendResetPasswordEmail.dynamicFields({}), + operation: async ({ data }) => { console.log(data) try { - const user = await UserMethods.read.client(prisma).executeUnsafe({ + const user = await userOperations.read({ params: { - email: data.email + email: data.email, }, - session, bypassAuth: true, }) @@ -131,5 +125,5 @@ export namespace AuthMethods { return data.email } - }) + }), } diff --git a/src/services/auth/schemas.ts b/src/services/auth/schemas.ts index 8e6117e90..d08a9449b 100644 --- a/src/services/auth/schemas.ts +++ b/src/services/auth/schemas.ts @@ -1,8 +1,7 @@ -import { UserSchemas } from '@/services/users/schemas' +import { userSchema } from '@/services/users/schemas' -export namespace AuthSchemas { - - export const sendResetPasswordEmail = UserSchemas.fields.pick({ +export const authSchemas = { + sendResetPasswordEmail: userSchema.pick({ email: true, }) } diff --git a/src/services/cabin/actions.ts b/src/services/cabin/actions.ts new file mode 100644 index 000000000..cbff4f407 --- /dev/null +++ b/src/services/cabin/actions.ts @@ -0,0 +1,32 @@ +'use server' + +import { cabinReleasePeriodOperations } from './releasePeriod/operations' +import { cabinPricePeriodOperations } from './pricePeriod/operations' +import { cabinBookingOperations } from './booking/operations' +import { cabinProductOperations } from './product/operations' +import { makeAction } from '@/services/serverAction' + +export const createReleasePeriodAction = makeAction(cabinReleasePeriodOperations.create) +export const readReleasePeriodsAction = makeAction(cabinReleasePeriodOperations.readMany) +export const updateReleasePeriodAction = makeAction(cabinReleasePeriodOperations.update) +export const destroyReleasePeriodAction = makeAction(cabinReleasePeriodOperations.destroy) + +export const createPricePeriodAction = makeAction(cabinPricePeriodOperations.create) +export const destoryPricePeriodAction = makeAction(cabinPricePeriodOperations.destroy) +export const readPricePeriodsAction = makeAction(cabinPricePeriodOperations.readMany) +export const readPublicPricePeriodsAction = makeAction(cabinPricePeriodOperations.readPublicPeriods) +export const readUnreleasedPricePeriodsAction = makeAction(cabinPricePeriodOperations.readUnreleasedPeriods) + +export const createCabinBookingUserAttachedAction = makeAction(cabinBookingOperations.createCabinBookingUserAttached) +export const createBedBookingUserAttachedAction = makeAction(cabinBookingOperations.createBedBookingUserAttached) +export const createCabinBookingNoUserAction = makeAction(cabinBookingOperations.createCabinBookingNoUser) +export const createBedBookingNoUserAction = makeAction(cabinBookingOperations.createBedBookingNoUser) +export const readCabinAvailabilityAction = makeAction(cabinBookingOperations.readAvailability) +export const readCabinBookingsAction = makeAction(cabinBookingOperations.readMany) +export const readCabinBookingAction = makeAction(cabinBookingOperations.read) + +export const readCabinProductsAction = makeAction(cabinProductOperations.readMany) +export const readCabinProductsActiveAction = makeAction(cabinProductOperations.readActive) +export const readCabinProductAction = makeAction(cabinProductOperations.read) +export const createCabinProductAction = makeAction(cabinProductOperations.create) +export const createCabinProductPriceAction = makeAction(cabinProductOperations.createPrice) diff --git a/src/services/cabin/booking/Types.ts b/src/services/cabin/booking/Types.ts deleted file mode 100644 index e7233fd0f..000000000 --- a/src/services/cabin/booking/Types.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { CabinBookingConfig } from './config' -import type { Booking } from '@prisma/client' - -export type BookingFiltered = Pick diff --git a/src/services/cabin/booking/auth.ts b/src/services/cabin/booking/auth.ts new file mode 100644 index 000000000..1b89907c0 --- /dev/null +++ b/src/services/cabin/booking/auth.ts @@ -0,0 +1,33 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' +import { RequirePermissionAndUserId } from '@/auth/auther/RequirePermissionAndUserId' + +export const cabinBookingAuth = { + createCabinBookingUserAttached: RequirePermissionAndUserId.staticFields({ + permission: 'CABIN_BOOKING_CABIN_CREATE' + }), + + createCabinBookingNoUser: RequirePermission.staticFields({ + permission: 'CABIN_BOOKING_CABIN_CREATE' + }), + + createBedBookingUserAttached: RequirePermissionAndUserId.staticFields({ + permission: 'CABIN_BOOKING_BED_CREATE' + }), + + createBedBookingNoUser: RequirePermission.staticFields({ + permission: 'CABIN_BOOKING_BED_CREATE' + }), + + readAvailability: RequirePermission.staticFields({ + permission: 'CABIN_CALENDAR_READ' + }), + + readMany: RequirePermission.staticFields({ + permission: 'CABIN_BOOKING_ADMIN' + }), + + read: RequirePermission.staticFields({ + permission: 'CABIN_BOOKING_ADMIN' + }), +} + diff --git a/src/services/cabin/booking/authers.ts b/src/services/cabin/booking/authers.ts deleted file mode 100644 index f1ee0a218..000000000 --- a/src/services/cabin/booking/authers.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' -import { RequirePermissionAndUserId } from '@/auth/auther/RequirePermissionAndUserId' - -export namespace CabinBookingAuthers { - export const createCabinBookingUserAttached = RequirePermissionAndUserId.staticFields({ - permission: 'CABIN_BOOKING_CABIN_CREATE' - }) - - export const createCabinBookingNoUser = RequirePermission.staticFields({ - permission: 'CABIN_BOOKING_CABIN_CREATE' - }) - - export const createBedBookingUserAttached = RequirePermissionAndUserId.staticFields({ - permission: 'CABIN_BOOKING_BED_CREATE' - }) - - export const createBedBookingNoUser = RequirePermission.staticFields({ - permission: 'CABIN_BOOKING_BED_CREATE' - }) - - export const readAvailability = RequirePermission.staticFields({ - permission: 'CABIN_CALENDAR_READ' - }) - - export const readMany = RequirePermission.staticFields({ - permission: 'CABIN_BOOKING_ADMIN' - }) - - export const read = RequirePermission.staticFields({ - permission: 'CABIN_BOOKING_ADMIN' - }) -} - diff --git a/src/services/cabin/booking/cabinPriceCalculator.ts b/src/services/cabin/booking/cabinPriceCalculator.ts index 02abda2f2..e38d0fc15 100644 --- a/src/services/cabin/booking/cabinPriceCalculator.ts +++ b/src/services/cabin/booking/cabinPriceCalculator.ts @@ -1,5 +1,5 @@ import { dateMatchCron } from '@/lib/dates/cron' -import type { CabinProductConfig } from '@/services/cabin/product/config' +import type { CabinProductExtended, CabinProductPriceExtended } from '@/services/cabin/product/constants' import type { CabinProduct, CabinProductPrice, PricePeriod } from '@prisma/client' function getDateArray(start: Date, end: Date) { @@ -25,7 +25,7 @@ function matchPriceForDay({ day: Date, memberShare: number, pricePeriods: PricePeriod[], - prices: CabinProductConfig.CabinProductPriceExtended[] + prices: CabinProductPriceExtended[] }) { const currentPricePeriod = pricePeriods.findLast(period => period.validFrom <= day) if (!currentPricePeriod) { @@ -76,7 +76,7 @@ function findPricesForProduct({ numberOfMembers: number, numberOfNonMembers: number, pricePeriods: PricePeriod[], - product: CabinProductConfig.CabinProductExtended + product: CabinProductExtended }): CabinPriceCalculatorReturnType[] { const dateArray = getDateArray(startDate, endDate) let memberShare = Math.ceil(numberOfMembers / (numberOfMembers + numberOfNonMembers) * 100) @@ -117,7 +117,7 @@ export function calculateCabinBookingPrice({ numberOfNonMembers, }: { pricePeriods: PricePeriod[], - products: CabinProductConfig.CabinProductExtended[], + products: CabinProductExtended[], productAmounts: number[], startDate: Date, endDate: Date, diff --git a/src/services/cabin/booking/config.ts b/src/services/cabin/booking/config.ts deleted file mode 100644 index 61c255f5a..000000000 --- a/src/services/cabin/booking/config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createSelection } from '@/services/createSelection' -import { UserConfig } from '@/services/users/config' -import type { Booking } from '@prisma/client' - -export namespace CabinBookingConfig { - - export const bookingFieldsToExpose = ['start', 'end', 'type'] as const satisfies (keyof Booking)[] - - export const bookingFilerSelection = createSelection(bookingFieldsToExpose) - - export const bookingIncluder = { - user: { - select: UserConfig.filterSelection, - }, - BookingProduct: { - include: { - product: true, - } - }, - event: true, - guestUser: true, - } - -} - diff --git a/src/services/cabin/booking/constants.ts b/src/services/cabin/booking/constants.ts new file mode 100644 index 000000000..c805ca080 --- /dev/null +++ b/src/services/cabin/booking/constants.ts @@ -0,0 +1,21 @@ +import { createSelection } from '@/services/createSelection' +import { userFilterSelection } from '@/services/users/constants' +import type { Booking } from '@prisma/client' + +export const cabinBookingFieldsToExpose = ['start', 'end', 'type'] as const satisfies (keyof Booking)[] + +export const cabinBookingFilerSelection = createSelection(cabinBookingFieldsToExpose) + +export const cabinBookingIncluder = { + user: { + select: userFilterSelection, + }, + BookingProduct: { + include: { + product: true, + } + }, + event: true, + guestUser: true, +} + diff --git a/src/services/cabin/booking/methods.ts b/src/services/cabin/booking/methods.ts deleted file mode 100644 index dfb230bfd..000000000 --- a/src/services/cabin/booking/methods.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { CabinBookingAuthers } from './authers' -import { CabinBookingConfig } from './config' -import { CabinBookingSchemas } from './schemas' -import { calculateCabinBookingPrice, calculateTotalCabinBookingPrice } from './cabinPriceCalculator' -import { CabinPricePeriodMethods } from '@/services/cabin/pricePeriod/methods' -import { CabinProductConfig } from '@/services/cabin/product/config' -import { ServiceMethod } from '@/services/ServiceMethod' -import 'server-only' -import { ServerOnlyAuther } from '@/auth/auther/RequireServer' -import { ServerError } from '@/services/error' -import { CabinReleasePeriodMethods } from '@/services/cabin/releasePeriod/methods' -import { sendSystemMail } from '@/services/notifications/email/send' -import { NotificationMethods } from '@/services/notifications/methods' -import { z } from 'zod' -import { BookingType } from '@prisma/client' - -const mailData = { - title: 'Bekreftelse på hyttebooking', - message: `Takk for din hyttebooking. -Dette skal være en bookingbekreftelse, så det bør nok komme noe nyttig info her snart.`, -} - -export namespace CabinBookingMethods { - - const cabinAvailable = ServiceMethod({ - paramsSchema: z.object({ - start: z.date(), - end: z.date() - }), - auther: ServerOnlyAuther, - method: async ({ prisma, params }) => { - const results = await prisma.booking.findMany({ - where: { - start: { - lt: params.end, - }, - end: { - gt: params.start, - }, - } - }) - return results.length === 0 - } - }) - - const bookingProductParams = z.array(z.object({ - cabinProductId: z.number(), - quantity: z.number().int().min(1), - })) - - - const create = ServiceMethod({ - paramsSchema: z.object({ - bookingType: z.nativeEnum(BookingType), - bookingProducts: bookingProductParams, - }), - auther: ServerOnlyAuther, - dataSchema: CabinBookingSchemas.createBookingUserAttached, - method: async ({ prisma, params, data, session }) => { - // TODO: Prevent Race conditions - - const latestReleaseDate = await CabinReleasePeriodMethods.getCurrentReleasePeriod.client(prisma).execute({ - session, - bypassAuth: true, - }) - - if (latestReleaseDate === null) { - throw new ServerError('SERVER ERROR', 'Hyttebooking siden er ikke tilgjengelig.') - } - - if (data.end > latestReleaseDate.releaseUntil) { - throw new ServerError('BAD PARAMETERS', 'Hytta kan ikke bookes etter siste slippdato.') - } - - if (!await cabinAvailable.client(prisma).execute({ - params: data, - session, - bypassAuth: true, - })) { - throw new ServerError('BAD PARAMETERS', 'Hytta er ikke tilgjengelig i den perioden.') - } - - const products = await prisma.cabinProduct.findMany({ - where: { - id: { - in: params.bookingProducts.map(product => product.cabinProductId), - } - }, - include: CabinProductConfig.includer - }) - if (products.length !== params.bookingProducts.length) { - throw new ServerError('BAD PARAMETERS', 'Kunne ikke finne alle hytta produktene. Duplikater er ikke tillat.') - } - - const productsInOrder: CabinProductConfig.CabinProductExtended[] = [] - - for (const paramProduct of params.bookingProducts) { - const product = products.find(prodItem => prodItem.id === paramProduct.cabinProductId) - if (!product) { - throw new ServerError('UNKNOWN ERROR', 'Kunne ikke finne mengden av produktet.') - } - productsInOrder.push(product) - - if (product.type !== params.bookingType) { - throw new ServerError('BAD PARAMETERS', 'Alle produktene må ha samme type som bookingen.') - } - - if (product.amount < paramProduct.quantity) { - throw new ServerError('BAD PARAMETERS', 'Det er ikke nok av produktet til å oppfylle bookingen.') - } - } - - if (params.bookingType === 'EVENT' && params.bookingProducts.length !== 0) { - throw new ServerError('BAD PARAMETERS', 'Hvad der hender bookinger kan ikke inneholde produkter.') - } - - if (params.bookingType === 'CABIN' && - params.bookingProducts.length !== 1 && - params.bookingProducts[0].quantity !== 1 - ) { - throw new ServerError('BAD PARAMETERS', 'Hyttebookinger kan bare inneholde ett produkt med mengde 1.') - } - - if (params.bookingType === 'BED' && params.bookingProducts.length === 0) { - throw new ServerError('BAD PARAMETERS', 'Sengebookinger må inneholde minst ett produkt.') - } - - const pricePeriods = await CabinPricePeriodMethods.readMany.client(prisma).execute({ - bypassAuth: true, - session, - }) - - const priceObjects = calculateCabinBookingPrice({ - pricePeriods, - products: productsInOrder, - productAmounts: params.bookingProducts.map(prod => prod.quantity), - startDate: data.start, - endDate: data.end, - numberOfMembers: data.numberOfMembers, - numberOfNonMembers: data.numberOfNonMembers - }) - - const totalPrice = calculateTotalCabinBookingPrice(priceObjects) - console.log('TOTAL PRICE FOR THE BOOKING:', totalPrice) - - return await prisma.booking.create({ - data: { - type: params.bookingType, - start: data.start, - end: data.end, - tenantNotes: data.tenantNotes, - numberOfMembers: data.numberOfMembers, - numberOfNonMembers: data.numberOfNonMembers, - BookingProduct: { - create: params.bookingProducts.map(product => ({ - cabinProductId: product.cabinProductId, - quantity: product.quantity, - })) - } - } - }) - } - }) - - const createBookingWithUser = ServiceMethod({ - paramsSchema: z.object({ - userId: z.number(), - bookingType: z.nativeEnum(BookingType), - bookingProducts: bookingProductParams, - }), - auther: ServerOnlyAuther, - dataSchema: CabinBookingSchemas.createBookingUserAttached, - method: async ({ prisma, params, data, session }) => { - const result = await create.client(prisma).execute({ - params, - data, - session, - bypassAuth: true, - }) - - await prisma.booking.update({ - where: { - id: result.id, - }, - data: { - user: { - connect: { - id: params.userId, - } - } - } - }) - - await NotificationMethods.createSpecial.client(prisma).execute({ - params: { - special: 'CABIN_BOOKING_CONFIRMATION', - }, - data: { - ...mailData, - userIdList: [params.userId], - }, - session, - bypassAuth: true, - }) - } - }) - - export const createCabinBookingUserAttached = ServiceMethod({ - paramsSchema: z.object({ - userId: z.number(), - bookingProducts: bookingProductParams, - }), - auther: ({ params }) => CabinBookingAuthers.createCabinBookingUserAttached.dynamicFields({ - userId: params.userId, - }), - dataSchema: CabinBookingSchemas.createBookingUserAttached, - method: async ({ prisma, params, data, session }) => - createBookingWithUser.client(prisma).execute({ - params: { - userId: params.userId, - bookingType: BookingType.CABIN, - bookingProducts: params.bookingProducts, - }, - data, - session, - bypassAuth: true, - }) - }) - - export const createBedBookingUserAttached = ServiceMethod({ - paramsSchema: z.object({ - userId: z.number(), - bookingProducts: bookingProductParams, - }), - auther: ({ params }) => CabinBookingAuthers.createBedBookingUserAttached.dynamicFields({ - userId: params.userId, - }), - dataSchema: CabinBookingSchemas.createBookingUserAttached, - method: async ({ prisma, params, data, session }) => - createBookingWithUser.client(prisma).execute({ - params: { - userId: params.userId, - bookingType: BookingType.BED, - bookingProducts: params.bookingProducts, - }, - data, - session, - bypassAuth: true, - }) - }) - - const createBookingNoUser = ServiceMethod({ - paramsSchema: z.object({ - bookingType: z.nativeEnum(BookingType), - bookingProducts: bookingProductParams, - }), - auther: ServerOnlyAuther, - dataSchema: CabinBookingSchemas.createBookingNoUser, - method: async ({ prisma, params, data, session }) => { - const result = await create.client(prisma).execute({ - params, - data: { - ...data, - numberOfMembers: 0, - numberOfNonMembers: 0, - }, - session, - bypassAuth: true, - }) - - await prisma.booking.update({ - where: { - id: result.id, - }, - data: { - guestUser: { - create: { - firstname: data.firstname, - lastname: data.lastname, - email: data.email, - mobile: data.mobile, - } - } - } - }) - - await sendSystemMail( - data.email, - mailData.title, - mailData.message - ) - } - }) - - export const createCabinBookingNoUser = ServiceMethod({ - paramsSchema: z.object({ - bookingProducts: bookingProductParams, - }), - auther: () => CabinBookingAuthers.createCabinBookingNoUser.dynamicFields({}), - dataSchema: CabinBookingSchemas.createBookingNoUser, - method: async ({ prisma, params, data, session }) => createBookingNoUser.client(prisma).execute({ - params: { - bookingType: BookingType.CABIN, - bookingProducts: params.bookingProducts, - }, - data, - session, - bypassAuth: true, - }) - }) - - export const createBedBookingNoUser = ServiceMethod({ - paramsSchema: z.object({ - bookingProducts: bookingProductParams, - }), - auther: () => CabinBookingAuthers.createBedBookingNoUser.dynamicFields({}), - dataSchema: CabinBookingSchemas.createBookingNoUser, - method: async ({ prisma, params, data, session }) => createBookingNoUser.client(prisma).execute({ - params: { - bookingType: BookingType.BED, - bookingProducts: params.bookingProducts, - }, - data, - session, - bypassAuth: true, - }) - }) - - export const readAvailability = ServiceMethod({ - auther: () => CabinBookingAuthers.readAvailability.dynamicFields({}), - method: async ({ prisma }) => { - const results = await prisma.booking.findMany({ - select: CabinBookingConfig.bookingFilerSelection, - orderBy: { - start: 'asc' - }, - where: { - canceled: null, - end: { - gte: new Date(), - }, - }, - }) - - // Anonymize the bookings a bit - for (let i = results.length - 1; i > 0; i--) { - if (results[i].start === results[i - 1].end) { - results[i - 1].end = results[i].end - results.splice(i) - } - } - - return results - } - }) - - export const readMany = ServiceMethod({ - auther: () => CabinBookingAuthers.readMany.dynamicFields({}), - method: ({ prisma }) => prisma.booking.findMany({ - orderBy: { - start: 'asc', - }, - include: CabinBookingConfig.bookingIncluder, - }), // TODO: Pager - }) - - export const read = ServiceMethod({ - auther: () => CabinBookingAuthers.read.dynamicFields({}), - paramsSchema: z.object({ - id: z.number(), - }), - method: ({ prisma, params }) => prisma.booking.findUniqueOrThrow({ - where: params, - include: CabinBookingConfig.bookingIncluder, - }) - }) - - -} diff --git a/src/services/cabin/booking/operations.ts b/src/services/cabin/booking/operations.ts new file mode 100644 index 000000000..d48ec1fac --- /dev/null +++ b/src/services/cabin/booking/operations.ts @@ -0,0 +1,365 @@ +import 'server-only' +import { calculateCabinBookingPrice, calculateTotalCabinBookingPrice } from './cabinPriceCalculator' +import { cabinBookingSchemas } from './schemas' +import { cabinBookingAuth } from './auth' +import { cabinBookingFilerSelection, cabinBookingIncluder } from './constants' +import { cabinPricePeriodOperations } from '@/services/cabin/pricePeriod/operations' +import { cabinProductPriceIncluder } from '@/services/cabin/product/constants' +import { defineOperation } from '@/services/serviceOperation' +import { ServerOnlyAuther } from '@/auth/auther/RequireServer' +import { ServerError } from '@/services/error' +import { cabinReleasePeriodOperations } from '@/services/cabin/releasePeriod/operations' +import { sendSystemMail } from '@/services/notifications/email/send' +import { notificationOperations } from '@/services/notifications/operations' +import { z } from 'zod' +import { BookingType } from '@prisma/client' +import type { CabinProductExtended } from '@/services/cabin/product/constants' + +const mailData = { + title: 'Bekreftelse på hyttebooking', + message: `Takk for din hyttebooking. +Dette skal være en bookingbekreftelse, så det bør nok komme noe nyttig info her snart.`, +} + +const cabinAvailable = defineOperation({ + paramsSchema: z.object({ + start: z.date(), + end: z.date() + }), + authorizer: ServerOnlyAuther, + operation: async ({ prisma, params }) => { + const results = await prisma.booking.findMany({ + where: { + start: { + lt: params.end, + }, + end: { + gt: params.start, + }, + } + }) + return results.length === 0 + } +}) + +const bookingProductParams = z.array(z.object({ + cabinProductId: z.number(), + quantity: z.number().int().min(1), +})) + + +const create = defineOperation({ + paramsSchema: z.object({ + bookingType: z.nativeEnum(BookingType), + bookingProducts: bookingProductParams, + }), + authorizer: ServerOnlyAuther, + dataSchema: cabinBookingSchemas.createBookingUserAttached, + operation: async ({ prisma, params, data }) => { + // TODO: Prevent Race conditions + + const latestReleaseDate = await cabinReleasePeriodOperations.getCurrentReleasePeriod({ + bypassAuth: true, + }) + + if (latestReleaseDate === null) { + throw new ServerError('SERVER ERROR', 'Hyttebooking siden er ikke tilgjengelig.') + } + + if (data.end > latestReleaseDate.releaseUntil) { + throw new ServerError('BAD PARAMETERS', 'Hytta kan ikke bookes etter siste slippdato.') + } + + if (!await cabinAvailable({ + params: data, + bypassAuth: true, + })) { + throw new ServerError('BAD PARAMETERS', 'Hytta er ikke tilgjengelig i den perioden.') + } + + const products = await prisma.cabinProduct.findMany({ + where: { + id: { + in: params.bookingProducts.map(product => product.cabinProductId), + } + }, + include: cabinProductPriceIncluder, + }) + if (products.length !== params.bookingProducts.length) { + throw new ServerError('BAD PARAMETERS', 'Kunne ikke finne alle hytta produktene. Duplikater er ikke tillat.') + } + + const productsInOrder: CabinProductExtended[] = [] + + for (const paramProduct of params.bookingProducts) { + const product = products.find(prodItem => prodItem.id === paramProduct.cabinProductId) + if (!product) { + throw new ServerError('UNKNOWN ERROR', 'Kunne ikke finne mengden av produktet.') + } + productsInOrder.push(product) + + if (product.type !== params.bookingType) { + throw new ServerError('BAD PARAMETERS', 'Alle produktene må ha samme type som bookingen.') + } + + if (product.amount < paramProduct.quantity) { + throw new ServerError('BAD PARAMETERS', 'Det er ikke nok av produktet til å oppfylle bookingen.') + } + } + + if (params.bookingType === 'EVENT' && params.bookingProducts.length !== 0) { + throw new ServerError('BAD PARAMETERS', 'Hvad der hender bookinger kan ikke inneholde produkter.') + } + + if (params.bookingType === 'CABIN' && + params.bookingProducts.length !== 1 && + params.bookingProducts[0].quantity !== 1 + ) { + throw new ServerError('BAD PARAMETERS', 'Hyttebookinger kan bare inneholde ett produkt med mengde 1.') + } + + if (params.bookingType === 'BED' && params.bookingProducts.length === 0) { + throw new ServerError('BAD PARAMETERS', 'Sengebookinger må inneholde minst ett produkt.') + } + + const pricePeriods = await cabinPricePeriodOperations.readMany({ bypassAuth: true }) + + const priceObjects = calculateCabinBookingPrice({ + pricePeriods, + products: productsInOrder, + productAmounts: params.bookingProducts.map(prod => prod.quantity), + startDate: data.start, + endDate: data.end, + numberOfMembers: data.numberOfMembers, + numberOfNonMembers: data.numberOfNonMembers + }) + + const totalPrice = calculateTotalCabinBookingPrice(priceObjects) + console.log('TOTAL PRICE FOR THE BOOKING:', totalPrice) + + return await prisma.booking.create({ + data: { + type: params.bookingType, + start: data.start, + end: data.end, + tenantNotes: data.tenantNotes, + numberOfMembers: data.numberOfMembers, + numberOfNonMembers: data.numberOfNonMembers, + BookingProduct: { + create: params.bookingProducts.map(product => ({ + cabinProductId: product.cabinProductId, + quantity: product.quantity, + })) + } + } + }) + } +}) + +const createBookingWithUser = defineOperation({ + paramsSchema: z.object({ + userId: z.number(), + bookingType: z.nativeEnum(BookingType), + bookingProducts: bookingProductParams, + }), + authorizer: ServerOnlyAuther, + dataSchema: cabinBookingSchemas.createBookingUserAttached, + operation: async ({ prisma, params, data }) => { + const result = await create({ + params, + data, + bypassAuth: true, + }) + + await prisma.booking.update({ + where: { + id: result.id, + }, + data: { + user: { + connect: { + id: params.userId, + } + } + } + }) + + await notificationOperations.createSpecial({ + params: { + special: 'CABIN_BOOKING_CONFIRMATION', + }, + data: { + ...mailData, + userIdList: [params.userId], + }, + bypassAuth: true, + }) + } +}) + +const createBookingNoUser = defineOperation({ + paramsSchema: z.object({ + bookingType: z.nativeEnum(BookingType), + bookingProducts: bookingProductParams, + }), + authorizer: ServerOnlyAuther, + dataSchema: cabinBookingSchemas.createBookingNoUser, + operation: async ({ prisma, params, data }) => { + const result = await create({ + params, + data: { + ...data, + numberOfMembers: 0, + numberOfNonMembers: 0, + }, + bypassAuth: true, + }) + + await prisma.booking.update({ + where: { + id: result.id, + }, + data: { + guestUser: { + create: { + firstname: data.firstname, + lastname: data.lastname, + email: data.email, + mobile: data.mobile, + } + } + } + }) + + await sendSystemMail( + data.email, + mailData.title, + mailData.message + ) + } +}) + +export const cabinBookingOperations = { + createCabinBookingUserAttached: defineOperation({ + paramsSchema: z.object({ + userId: z.number(), + bookingProducts: bookingProductParams, + }), + authorizer: ({ params }) => cabinBookingAuth.createCabinBookingUserAttached.dynamicFields({ + userId: params.userId, + }), + dataSchema: cabinBookingSchemas.createBookingUserAttached, + operation: async ({ params, data }) => + createBookingWithUser({ + params: { + userId: params.userId, + bookingType: BookingType.CABIN, + bookingProducts: params.bookingProducts, + }, + data, + bypassAuth: true, + }) + }), + + createBedBookingUserAttached: defineOperation({ + paramsSchema: z.object({ + userId: z.number(), + bookingProducts: bookingProductParams, + }), + authorizer: ({ params }) => cabinBookingAuth.createBedBookingUserAttached.dynamicFields({ + userId: params.userId, + }), + dataSchema: cabinBookingSchemas.createBookingUserAttached, + operation: async ({ params, data }) => + createBookingWithUser({ + params: { + userId: params.userId, + bookingType: BookingType.BED, + bookingProducts: params.bookingProducts, + }, + data, + bypassAuth: true, + }) + }), + + createCabinBookingNoUser: defineOperation({ + paramsSchema: z.object({ + bookingProducts: bookingProductParams, + }), + authorizer: () => cabinBookingAuth.createCabinBookingNoUser.dynamicFields({}), + dataSchema: cabinBookingSchemas.createBookingNoUser, + operation: async ({ params, data }) => createBookingNoUser({ + params: { + bookingType: BookingType.CABIN, + bookingProducts: params.bookingProducts, + }, + data, + bypassAuth: true, + }) + }), + + createBedBookingNoUser: defineOperation({ + paramsSchema: z.object({ + bookingProducts: bookingProductParams, + }), + authorizer: () => cabinBookingAuth.createBedBookingNoUser.dynamicFields({}), + dataSchema: cabinBookingSchemas.createBookingNoUser, + operation: async ({ params, data }) => createBookingNoUser({ + params: { + bookingType: BookingType.BED, + bookingProducts: params.bookingProducts, + }, + data, + bypassAuth: true, + }) + }), + + readAvailability: defineOperation({ + authorizer: () => cabinBookingAuth.readAvailability.dynamicFields({}), + operation: async ({ prisma }) => { + const results = await prisma.booking.findMany({ + select: cabinBookingFilerSelection, + orderBy: { + start: 'asc' + }, + where: { + canceled: null, + end: { + gte: new Date(), + }, + }, + }) + + // Anonymize the bookings a bit + for (let i = results.length - 1; i > 0; i--) { + if (results[i].start === results[i - 1].end) { + results[i - 1].end = results[i].end + results.splice(i) + } + } + + return results + } + }), + + readMany: defineOperation({ + authorizer: () => cabinBookingAuth.readMany.dynamicFields({}), + operation: ({ prisma }) => prisma.booking.findMany({ + orderBy: { + start: 'asc', + }, + include: cabinBookingIncluder, + }), // TODO: Pager + }), + + read: defineOperation({ + authorizer: () => cabinBookingAuth.read.dynamicFields({}), + paramsSchema: z.object({ + id: z.number(), + }), + operation: ({ prisma, params }) => prisma.booking.findUniqueOrThrow({ + where: params, + include: cabinBookingIncluder, + }) + }) +} diff --git a/src/services/cabin/booking/schemas.ts b/src/services/cabin/booking/schemas.ts index 745b71370..78ff9a02f 100644 --- a/src/services/cabin/booking/schemas.ts +++ b/src/services/cabin/booking/schemas.ts @@ -1,41 +1,40 @@ import { dateLessThan, dateLessThanOrEqualTo } from '@/lib/dates/comparison' -import { zpn } from '@/lib/fields/zpn' +import { Zpn } from '@/lib/fields/zpn' import { z } from 'zod' - -export namespace CabinBookingSchemas { - const fields = z.object({ - start: z.coerce.date(), - end: z.coerce.date(), - tenantNotes: z.string().optional(), - numberOfMembers: z.coerce.number().min(0), - numberOfNonMembers: z.coerce.number().min(0), - firstname: z.string().min(2), - lastname: z.string().min(2), - email: z.string().email(), - mobile: z.string().regex(/^\+?\d{4,20}$/, { message: 'Skriv kun tall, uten mellomrom.' }), - acceptedTerms: zpn.checkboxOrBoolean({ - label: '', - message: 'Du må godta vilkårene for å bruk siden.' - }) +const baseSchema = z.object({ + start: z.coerce.date(), + end: z.coerce.date(), + tenantNotes: z.string().optional(), + numberOfMembers: z.coerce.number().min(0), + numberOfNonMembers: z.coerce.number().min(0), + firstname: z.string().min(2), + lastname: z.string().min(2), + email: z.string().email(), + mobile: z.string().regex(/^\+?\d{4,20}$/, { message: 'Skriv kun tall, uten mellomrom.' }), + acceptedTerms: Zpn.checkboxOrBoolean({ + label: '', + message: 'Du må godta vilkårene for å bruk siden.' }) +}) - const startEndDateRefiner = { - fcn: (data: { start: Date, end: Date }) => - dateLessThan(data.start, data.end) && dateLessThanOrEqualTo(new Date(), data.start), - message: 'Start dato må være før sluttdato. Start daten må være i ramtiden' - } +const startEndDateRefiner = { + fcn: (data: { start: Date, end: Date }) => + dateLessThan(data.start, data.end) && dateLessThanOrEqualTo(new Date(), data.start), + message: 'Start dato må være før sluttdato. Start daten må være i ramtiden' +} - export const createBookingUserAttached = fields.pick({ +export const cabinBookingSchemas = { + createBookingUserAttached: baseSchema.pick({ start: true, end: true, tenantNotes: true, acceptedTerms: true, numberOfMembers: true, numberOfNonMembers: true, - }).refine(startEndDateRefiner.fcn, startEndDateRefiner.message) + }).refine(startEndDateRefiner.fcn, startEndDateRefiner.message), - export const createBookingNoUser = fields.pick({ + createBookingNoUser: baseSchema.pick({ start: true, end: true, tenantNotes: true, @@ -44,6 +43,6 @@ export namespace CabinBookingSchemas { lastname: true, email: true, mobile: true, - }).refine(startEndDateRefiner.fcn, startEndDateRefiner.message) + }).refine(startEndDateRefiner.fcn, startEndDateRefiner.message), } diff --git a/src/services/cabin/booking/types.ts b/src/services/cabin/booking/types.ts new file mode 100644 index 000000000..bf3c45dda --- /dev/null +++ b/src/services/cabin/booking/types.ts @@ -0,0 +1,4 @@ +import type { cabinBookingFieldsToExpose } from './constants' +import type { Booking } from '@prisma/client' + +export type BookingFiltered = Pick diff --git a/src/services/cabin/pricePeriod/auth.ts b/src/services/cabin/pricePeriod/auth.ts new file mode 100644 index 000000000..e35733873 --- /dev/null +++ b/src/services/cabin/pricePeriod/auth.ts @@ -0,0 +1,11 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +const baseAuther = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) + +export const cabinPricePeriodAuth = { + create: baseAuther, + read: baseAuther, + readPublicPeriods: RequirePermission.staticFields({ permission: 'CABIN_CALENDAR_READ' }), + update: baseAuther, + destroy: baseAuther, +} diff --git a/src/services/cabin/pricePeriod/authers.ts b/src/services/cabin/pricePeriod/authers.ts deleted file mode 100644 index 73ac134da..000000000 --- a/src/services/cabin/pricePeriod/authers.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace CabinPricePeriodAuthers { - export const create = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) - export const read = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) - export const readPublicPeriods = RequirePermission.staticFields({ permission: 'CABIN_CALENDAR_READ' }) - export const update = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) - export const destroy = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) -} - diff --git a/src/services/cabin/pricePeriod/methods.ts b/src/services/cabin/pricePeriod/operations.ts similarity index 60% rename from src/services/cabin/pricePeriod/methods.ts rename to src/services/cabin/pricePeriod/operations.ts index 731c7e1b8..50f58226f 100644 --- a/src/services/cabin/pricePeriod/methods.ts +++ b/src/services/cabin/pricePeriod/operations.ts @@ -1,21 +1,17 @@ -import { CabinPricePeriodAuthers } from './authers' -import { CabinPricePeriodSchemas } from './schemas' -import { ServiceMethod } from '@/services/ServiceMethod' import 'server-only' -import { CabinReleasePeriodMethods } from '@/services/cabin/releasePeriod/methods' +import { cabinPricePeriodAuth } from './auth' +import { cabinPricePeriodSchemas } from './schemas' +import { defineOperation } from '@/services/serviceOperation' +import { cabinReleasePeriodOperations } from '@/services/cabin/releasePeriod/operations' import { ServerError } from '@/services/error' import { z } from 'zod' -export namespace CabinPricePeriodMethods { - - export const create = ServiceMethod({ - auther: () => CabinPricePeriodAuthers.create.dynamicFields({}), - dataSchema: CabinPricePeriodSchemas.createPricePeriod, - method: async ({ prisma, data, session }) => { - const currentReleaseDate = await CabinReleasePeriodMethods.getCurrentReleasePeriod.client(prisma).execute({ - bypassAuth: true, - session, - }) +export const cabinPricePeriodOperations = { + create: defineOperation({ + authorizer: () => cabinPricePeriodAuth.create.dynamicFields({}), + dataSchema: cabinPricePeriodSchemas.createPricePeriod, + operation: async ({ prisma, data }) => { + const currentReleaseDate = await cabinReleasePeriodOperations.getCurrentReleasePeriod({ bypassAuth: true }) if (currentReleaseDate && currentReleaseDate.releaseUntil >= data.validFrom) { throw new ServerError( @@ -60,18 +56,15 @@ export namespace CabinPricePeriodMethods { return result } - }) + }), - export const destroy = ServiceMethod({ - auther: () => CabinPricePeriodAuthers.destroy.dynamicFields({}), + destroy: defineOperation({ + authorizer: () => cabinPricePeriodAuth.destroy.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - method: async ({ prisma, params, session }) => { - const currentReleasePeriod = await CabinReleasePeriodMethods.getCurrentReleasePeriod.client(prisma).execute({ - bypassAuth: true, - session, - }) + operation: async ({ prisma, params }) => { + const currentReleasePeriod = await cabinReleasePeriodOperations.getCurrentReleasePeriod({ bypassAuth: true }) const pricePeriod = await prisma.pricePeriod.findUniqueOrThrow({ where: params, @@ -85,20 +78,17 @@ export namespace CabinPricePeriodMethods { where: params, }) } - }) - - export const readMany = ServiceMethod({ - auther: () => CabinPricePeriodAuthers.read.dynamicFields({}), - method: async ({ prisma }) => prisma.pricePeriod.findMany() - }) - - export const readPublicPeriods = ServiceMethod({ - auther: () => CabinPricePeriodAuthers.readPublicPeriods.dynamicFields({}), - method: async ({ prisma, session }) => { - const releaseDate = await CabinReleasePeriodMethods.getCurrentReleasePeriod.client(prisma).execute({ - bypassAuth: true, - session, - }) + }), + + readMany: defineOperation({ + authorizer: () => cabinPricePeriodAuth.read.dynamicFields({}), + operation: async ({ prisma }) => prisma.pricePeriod.findMany() + }), + + readPublicPeriods: defineOperation({ + authorizer: () => cabinPricePeriodAuth.readPublicPeriods.dynamicFields({}), + operation: async ({ prisma }) => { + const releaseDate = await cabinReleasePeriodOperations.getCurrentReleasePeriod({ bypassAuth: true }) const [currentPeriod, futurePeriods] = await Promise.all([ prisma.pricePeriod.findFirstOrThrow({ @@ -126,15 +116,12 @@ export namespace CabinPricePeriodMethods { return [currentPeriod, ...futurePeriods] } - }) - - export const readUnreleasedPeriods = ServiceMethod({ - auther: () => CabinPricePeriodAuthers.read.dynamicFields({}), - method: async ({ prisma, session }) => { - const releaseDate = await CabinReleasePeriodMethods.getCurrentReleasePeriod.client(prisma).execute({ - bypassAuth: true, - session, - }) + }), + + readUnreleasedPeriods: defineOperation({ + authorizer: () => cabinPricePeriodAuth.read.dynamicFields({}), + operation: async ({ prisma }) => { + const releaseDate = await cabinReleasePeriodOperations.getCurrentReleasePeriod({ bypassAuth: true }) return prisma.pricePeriod.findMany({ where: { validFrom: { @@ -143,19 +130,19 @@ export namespace CabinPricePeriodMethods { } }) } - }) + }), - export const update = ServiceMethod({ - auther: () => CabinPricePeriodAuthers.update.dynamicFields({}), - dataSchema: CabinPricePeriodSchemas.updatePricePeriod, + update: defineOperation({ + authorizer: () => cabinPricePeriodAuth.update.dynamicFields({}), + dataSchema: cabinPricePeriodSchemas.updatePricePeriod, paramsSchema: z.object({ pricePeriodId: z.number(), }), - method: async ({ prisma, data, params }) => prisma.pricePeriod.update({ + operation: async ({ prisma, data, params }) => prisma.pricePeriod.update({ where: { id: params.pricePeriodId, }, data, }) - }) + }), } diff --git a/src/services/cabin/pricePeriod/schemas.ts b/src/services/cabin/pricePeriod/schemas.ts index 0c0573aa2..1875722fe 100644 --- a/src/services/cabin/pricePeriod/schemas.ts +++ b/src/services/cabin/pricePeriod/schemas.ts @@ -1,21 +1,19 @@ -import { zpn } from '@/lib/fields/zpn' +import { Zpn } from '@/lib/fields/zpn' import { z } from 'zod' -export namespace CabinPricePeriodSchemas { - const fields = z.object({ - id: z.coerce.number(), - validFrom: z.coerce.date(), - copyPreviousPrices: zpn.checkboxOrBoolean({ label: '' }), - }) +const baseSchema = z.object({ + id: z.coerce.number(), + validFrom: z.coerce.date(), + copyPreviousPrices: Zpn.checkboxOrBoolean({ label: '' }), +}) - export const createPricePeriod = fields.pick({ +export const cabinPricePeriodSchemas = { + createPricePeriod: baseSchema.pick({ validFrom: true, copyPreviousPrices: true, - }) - - export const updatePricePeriod = fields.pick({ + }), + updatePricePeriod: baseSchema.pick({ id: true, validFrom: true, - }) + }), } - diff --git a/src/services/cabin/product/auth.ts b/src/services/cabin/product/auth.ts new file mode 100644 index 000000000..cf24e9975 --- /dev/null +++ b/src/services/cabin/product/auth.ts @@ -0,0 +1,16 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const cabinProductAuth = { + read: RequirePermission.staticFields({ + permission: 'CABIN_CALENDAR_READ' + }), + + create: RequirePermission.staticFields({ + permission: 'CABIN_PRODUCTS_ADMIN' + }), + + createPrice: RequirePermission.staticFields({ + permission: 'CABIN_PRODUCTS_ADMIN' + }), +} + diff --git a/src/services/cabin/product/authers.ts b/src/services/cabin/product/authers.ts deleted file mode 100644 index 3210fe4ad..000000000 --- a/src/services/cabin/product/authers.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace CabinProductAuthers { - export const read = RequirePermission.staticFields({ - permission: 'CABIN_CALENDAR_READ' - }) - - export const create = RequirePermission.staticFields({ - permission: 'CABIN_PRODUCTS_ADMIN' - }) - - export const createPrice = RequirePermission.staticFields({ - permission: 'CABIN_PRODUCTS_ADMIN' - }) -} - diff --git a/src/services/cabin/product/config.ts b/src/services/cabin/product/config.ts deleted file mode 100644 index ead2ef5d3..000000000 --- a/src/services/cabin/product/config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { Prisma } from '@prisma/client' - - -export namespace CabinProductConfig { - - export const includer = { - CabinProductPrice: { - include: { - PricePeriod: true - } - } - } as const - - export type CabinProductExtended = Prisma.CabinProductGetPayload<{ - include: typeof includer - }> - - export type CabinProductPriceExtended = Prisma.CabinProductPriceGetPayload<{ - include: typeof includer.CabinProductPrice.include - }> -} diff --git a/src/services/cabin/product/constants.ts b/src/services/cabin/product/constants.ts new file mode 100644 index 000000000..9a5271bd8 --- /dev/null +++ b/src/services/cabin/product/constants.ts @@ -0,0 +1,17 @@ +import type { Prisma } from '@prisma/client' + +export const cabinProductPriceIncluder = { + CabinProductPrice: { + include: { + PricePeriod: true + } + } +} as const + +export type CabinProductExtended = Prisma.CabinProductGetPayload<{ + include: typeof cabinProductPriceIncluder +}> + +export type CabinProductPriceExtended = Prisma.CabinProductPriceGetPayload<{ + include: typeof cabinProductPriceIncluder.CabinProductPrice.include +}> diff --git a/src/services/cabin/product/methods.ts b/src/services/cabin/product/methods.ts deleted file mode 100644 index 00e634e76..000000000 --- a/src/services/cabin/product/methods.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { CabinProductAuthers } from './authers' -import { CabinProductSchemas } from './schemas' -import { CabinProductConfig } from './config' -import { ServiceMethod } from '@/services/ServiceMethod' -import 'server-only' -import { ServerError } from '@/services/error' -import { CabinReleasePeriodMethods } from '@/services/cabin/releasePeriod/methods' -import { CabinPricePeriodMethods } from '@/services/cabin/pricePeriod/methods' -import { z } from 'zod' - -export namespace CabinProductMethods { - - export const create = ServiceMethod({ - auther: () => CabinProductAuthers.create.dynamicFields({}), - dataSchema: CabinProductSchemas.createProduct, - method: ({ prisma, data }) => prisma.cabinProduct.create({ - data, - }) - }) - - export const createPrice = ServiceMethod({ - auther: () => CabinProductAuthers.createPrice.dynamicFields({}), - paramsSchema: z.object({ - cabinProductId: z.number(), - }), - dataSchema: CabinProductSchemas.createProductPrice, - method: async ({ prisma, params, data, session }) => { - const [pricePeriod, releasePeriod] = await Promise.all([ - prisma.pricePeriod.findUniqueOrThrow({ - where: { - id: data.pricePeriodId, - } - }), - CabinReleasePeriodMethods.getCurrentReleasePeriod.client(prisma).execute({ - bypassAuth: true, - session - }) - ]) - - if (releasePeriod && pricePeriod.validFrom <= releasePeriod.releaseUntil) { - throw new ServerError('BAD PARAMETERS', 'Cannot change prices for a product that is released') - } - - const result = await prisma.cabinProductPrice.create({ - data: { - ...data, - cabinProductId: params.cabinProductId, - } - }) - - return result - } - }) - - export const readMany = ServiceMethod({ - auther: () => CabinProductAuthers.read.dynamicFields({}), - method: ({ prisma }) => prisma.cabinProduct.findMany({ - include: CabinProductConfig.includer, - }), - }) - - export const readActive = ServiceMethod({ - auther: () => CabinProductAuthers.read.dynamicFields({}), - method: async ({ prisma, session }) => { - const pricePeriods = await CabinPricePeriodMethods.readPublicPeriods.client(prisma).execute({ - bypassAuth: true, - session, - }) - - return await prisma.cabinProduct.findMany({ - where: { - CabinProductPrice: { - some: { - pricePeriodId: { - in: pricePeriods.map(period => period.id) - } - } - } - }, - include: CabinProductConfig.includer, - }) - }, - }) - - export const read = ServiceMethod({ - auther: () => CabinProductAuthers.read.dynamicFields({}), - paramsSchema: z.object({ - id: z.number(), - }), - method: ({ prisma, params }) => prisma.cabinProduct.findUniqueOrThrow({ - where: params, - include: CabinProductConfig.includer - }) - }) -} diff --git a/src/services/cabin/product/operations.ts b/src/services/cabin/product/operations.ts new file mode 100644 index 000000000..9861309db --- /dev/null +++ b/src/services/cabin/product/operations.ts @@ -0,0 +1,93 @@ +import 'server-only' + +import { cabinProductAuth } from './auth' +import { cabinProductSchemas } from './schemas' +import { cabinProductPriceIncluder } from './constants' +import { cabinReleasePeriodOperations } from '@/services/cabin/releasePeriod/operations' +import { defineOperation } from '@/services/serviceOperation' +import { ServerError } from '@/services/error' +import { cabinPricePeriodOperations } from '@/services/cabin/pricePeriod/operations' +import { z } from 'zod' + +export const cabinProductOperations = { + + create: defineOperation({ + authorizer: () => cabinProductAuth.create.dynamicFields({}), + dataSchema: cabinProductSchemas.createProduct, + operation: ({ prisma, data }) => prisma.cabinProduct.create({ + data, + }) + }), + + createPrice: defineOperation({ + authorizer: () => cabinProductAuth.createPrice.dynamicFields({}), + paramsSchema: z.object({ + cabinProductId: z.number(), + }), + dataSchema: cabinProductSchemas.createProductPrice, + operation: async ({ prisma, params, data, session }) => { + const [pricePeriod, releasePeriod] = await Promise.all([ + prisma.pricePeriod.findUniqueOrThrow({ + where: { + id: data.pricePeriodId, + } + }), + cabinReleasePeriodOperations.getCurrentReleasePeriod({ + bypassAuth: true, + session + }) + ]) + + if (releasePeriod && pricePeriod.validFrom <= releasePeriod.releaseUntil) { + throw new ServerError('BAD PARAMETERS', 'Cannot change prices for a product that is released') + } + + const result = await prisma.cabinProductPrice.create({ + data: { + ...data, + cabinProductId: params.cabinProductId, + } + }) + + return result + } + }), + + readMany: defineOperation({ + authorizer: () => cabinProductAuth.read.dynamicFields({}), + operation: ({ prisma }) => prisma.cabinProduct.findMany({ + include: cabinProductPriceIncluder, + }), + }), + + readActive: defineOperation({ + authorizer: () => cabinProductAuth.read.dynamicFields({}), + operation: async ({ prisma }) => { + const pricePeriods = await cabinPricePeriodOperations.readPublicPeriods({ bypassAuth: true }) + + return await prisma.cabinProduct.findMany({ + where: { + CabinProductPrice: { + some: { + pricePeriodId: { + in: pricePeriods.map(period => period.id) + } + } + } + }, + include: cabinProductPriceIncluder, + }) + }, + }), + + read: defineOperation({ + authorizer: () => cabinProductAuth.read.dynamicFields({}), + paramsSchema: z.object({ + id: z.number(), + }), + operation: ({ prisma, params }) => prisma.cabinProduct.findUniqueOrThrow({ + where: params, + include: cabinProductPriceIncluder, + }) + }), +} diff --git a/src/services/cabin/product/schemas.ts b/src/services/cabin/product/schemas.ts index d9884a00f..bd8af5166 100644 --- a/src/services/cabin/product/schemas.ts +++ b/src/services/cabin/product/schemas.ts @@ -1,34 +1,32 @@ -import { zpn } from '@/lib/fields/zpn' +import { Zpn } from '@/lib/fields/zpn' import { convertPrice } from '@/lib/money/convert' import { BookingType } from '@prisma/client' import { z } from 'zod' +const baseSchema = z.object({ + type: z.nativeEnum(BookingType), + amount: z.coerce.number().int().min(0), + name: z.string().min(2), + description: z.string().min(0).max(20), + price: z.coerce.number().min(0).transform((val) => convertPrice(val)), + validFrom: z.coerce.date(), + cronInterval: Zpn.simpleCronExpression(), + memberShare: z.coerce.number().min(0).max(100), + pricePeriodId: z.coerce.number(), +}) -export namespace CabinProductSchemas { - const fields = z.object({ - type: z.nativeEnum(BookingType), - amount: z.coerce.number().int().min(0), - name: z.string().min(2), - description: z.string().min(0).max(20), - price: z.coerce.number().min(0).transform((val) => convertPrice(val)), - validFrom: z.coerce.date(), - cronInterval: zpn.simpleCronExpression(), - memberShare: z.coerce.number().min(0).max(100), - pricePeriodId: z.coerce.number(), - }) - - export const createProduct = fields.pick({ +export const cabinProductSchemas = { + createProduct: baseSchema.pick({ name: true, type: true, amount: true, - }) + }), - export const createProductPrice = fields.pick({ + createProductPrice: baseSchema.pick({ description: true, price: true, cronInterval: true, memberShare: true, pricePeriodId: true, - }) + }), } - diff --git a/src/services/cabin/releasePeriod/auth.ts b/src/services/cabin/releasePeriod/auth.ts new file mode 100644 index 000000000..898ce4d5e --- /dev/null +++ b/src/services/cabin/releasePeriod/auth.ts @@ -0,0 +1,11 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +const baseAuther = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) + +export const cabinReleasePeriodAuth = { + createReleasePeriodAuther: baseAuther, + readReleasePeriodAuther: baseAuther, + updateReleasePeriodAuther: baseAuther, + deleteReleasePeriodAuther: baseAuther, +} + diff --git a/src/services/cabin/releasePeriod/authers.ts b/src/services/cabin/releasePeriod/authers.ts deleted file mode 100644 index 448c6efed..000000000 --- a/src/services/cabin/releasePeriod/authers.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace CabinReleasePeriodAuthers { - export const createReleasePeriodAuther = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) - export const readReleasePeriodAuther = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) - export const updateReleasePeriodAuther = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) - export const deleteReleasePeriodAuther = RequirePermission.staticFields({ permission: 'CABIN_ADMIN' }) -} - diff --git a/src/services/cabin/releasePeriod/methods.ts b/src/services/cabin/releasePeriod/operations.ts similarity index 58% rename from src/services/cabin/releasePeriod/methods.ts rename to src/services/cabin/releasePeriod/operations.ts index 2e8999197..e2ac3572c 100644 --- a/src/services/cabin/releasePeriod/methods.ts +++ b/src/services/cabin/releasePeriod/operations.ts @@ -1,16 +1,16 @@ -import { CabinReleasePeriodAuthers } from './authers' -import { CabinReleasePeriodSchemas } from './schemas' -import { ServiceMethod } from '@/services/ServiceMethod' import 'server-only' +import { cabinReleasePeriodAuth } from './auth' +import { cabinReleasePeriodSchemas } from './schemas' +import { defineOperation } from '@/services/serviceOperation' import { ServerError } from '@/services/error' import { z } from 'zod' -export namespace CabinReleasePeriodMethods { +export const cabinReleasePeriodOperations = { - export const create = ServiceMethod({ - auther: () => CabinReleasePeriodAuthers.createReleasePeriodAuther.dynamicFields({}), - dataSchema: CabinReleasePeriodSchemas.createReleasePeriod, - method: async ({ prisma, data }) => { + create: defineOperation({ + authorizer: () => cabinReleasePeriodAuth.createReleasePeriodAuther.dynamicFields({}), + dataSchema: cabinReleasePeriodSchemas.createReleasePeriod, + operation: async ({ prisma, data }) => { const latestReleasePeriod = await prisma.releasePeriod.findFirst({ orderBy: { releaseTime: 'desc', @@ -29,14 +29,14 @@ export namespace CabinReleasePeriodMethods { data, }) } - }) + }), - export const destroy = ServiceMethod({ - auther: () => CabinReleasePeriodAuthers.deleteReleasePeriodAuther.dynamicFields({}), + destroy: defineOperation({ + authorizer: () => cabinReleasePeriodAuth.deleteReleasePeriodAuther.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { const releasePeriod = await prisma.releasePeriod.findUniqueOrThrow({ where: params, }) @@ -49,20 +49,20 @@ export namespace CabinReleasePeriodMethods { where: params, }) } - }) + }), - export const readMany = ServiceMethod({ - auther: () => CabinReleasePeriodAuthers.readReleasePeriodAuther.dynamicFields({}), - method: async ({ prisma }) => prisma.releasePeriod.findMany({ + readMany: defineOperation({ + authorizer: () => cabinReleasePeriodAuth.readReleasePeriodAuther.dynamicFields({}), + operation: async ({ prisma }) => prisma.releasePeriod.findMany({ orderBy: { releaseUntil: 'desc', } }) - }) + }), - export const getCurrentReleasePeriod = ServiceMethod({ - auther: () => CabinReleasePeriodAuthers.readReleasePeriodAuther.dynamicFields({}), - method: async ({ prisma }) => prisma.releasePeriod.findFirst({ + getCurrentReleasePeriod: defineOperation({ + authorizer: () => cabinReleasePeriodAuth.readReleasePeriodAuther.dynamicFields({}), + operation: async ({ prisma }) => prisma.releasePeriod.findFirst({ where: { releaseTime: { lte: new Date(), @@ -73,12 +73,12 @@ export namespace CabinReleasePeriodMethods { }, take: 1 }) - }) + }), - export const update = ServiceMethod({ - auther: () => CabinReleasePeriodAuthers.updateReleasePeriodAuther.dynamicFields({}), - dataSchema: CabinReleasePeriodSchemas.updateReleasePeriod, - method: async ({ prisma, data }) => prisma.releasePeriod.update({ + update: defineOperation({ + authorizer: () => cabinReleasePeriodAuth.updateReleasePeriodAuther.dynamicFields({}), + dataSchema: cabinReleasePeriodSchemas.updateReleasePeriod, + operation: async ({ prisma, data }) => prisma.releasePeriod.update({ where: { id: data.id, // TODO: Figure out why id is in data and not a param }, @@ -87,5 +87,5 @@ export namespace CabinReleasePeriodMethods { releaseTime: data.releaseTime, }, }) - }) + }), } diff --git a/src/services/cabin/releasePeriod/schemas.ts b/src/services/cabin/releasePeriod/schemas.ts index 7c6eb9347..d8ed47f2d 100644 --- a/src/services/cabin/releasePeriod/schemas.ts +++ b/src/services/cabin/releasePeriod/schemas.ts @@ -1,28 +1,26 @@ import { dateLessThan } from '@/lib/dates/comparison' import { z } from 'zod' +const baseSchema = z.object({ + id: z.coerce.number(), + releaseTime: z.coerce.date(), + releaseUntil: z.coerce.date(), +}) -export namespace CabinReleasePeriodSchemas { - const fields = z.object({ - id: z.coerce.number(), - releaseTime: z.coerce.date(), - releaseUntil: z.coerce.date(), - }) - - const releasePeriodRefiner = { - fcn: (data: { releaseTime: Date, releaseUntil: Date }) => dateLessThan(data.releaseTime, data.releaseUntil), - message: 'Slipp tiden må være før slutten av perioden som slippes.' - } +const releasePeriodRefiner = { + fcn: (data: { releaseTime: Date, releaseUntil: Date }) => dateLessThan(data.releaseTime, data.releaseUntil), + message: 'Slipp tiden må være før slutten av perioden som slippes.' +} - export const createReleasePeriod = fields.pick({ +export const cabinReleasePeriodSchemas = { + createReleasePeriod: baseSchema.pick({ releaseTime: true, releaseUntil: true, - }).refine(releasePeriodRefiner.fcn, releasePeriodRefiner.message) + }).refine(releasePeriodRefiner.fcn, releasePeriodRefiner.message), - export const updateReleasePeriod = fields.pick({ + updateReleasePeriod: baseSchema.pick({ id: true, releaseTime: true, releaseUntil: true, - }).refine(releasePeriodRefiner.fcn, releasePeriodRefiner.message) + }).refine(releasePeriodRefiner.fcn, releasePeriodRefiner.message), } - diff --git a/src/services/career/companies/actions.ts b/src/services/career/companies/actions.ts new file mode 100644 index 000000000..307bda7ec --- /dev/null +++ b/src/services/career/companies/actions.ts @@ -0,0 +1,12 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { companyOperations } from '@/services/career/companies/operations' + +export const createCompanyAction = makeAction(companyOperations.create) + +export const destroyCompanyAction = makeAction(companyOperations.destroy) + +export const readCompanyPageAction = makeAction(companyOperations.readPage) + +export const updateComanyAction = makeAction(companyOperations.update) diff --git a/src/services/career/companies/auth.ts b/src/services/career/companies/auth.ts new file mode 100644 index 000000000..5fe8b7a68 --- /dev/null +++ b/src/services/career/companies/auth.ts @@ -0,0 +1,8 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const companyAuth = { + create: RequirePermission.staticFields({ permission: 'COMPANY_ADMIN' }), + readPage: RequirePermission.staticFields({ permission: 'COMPANY_READ' }), + update: RequirePermission.staticFields({ permission: 'COMPANY_ADMIN' }), + destroy: RequirePermission.staticFields({ permission: 'COMPANY_ADMIN' }), +} diff --git a/src/services/career/companies/authers.ts b/src/services/career/companies/authers.ts deleted file mode 100644 index 5ecaf2e80..000000000 --- a/src/services/career/companies/authers.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace CompanyAuthers { - export const create = RequirePermission.staticFields({ permission: 'COMPANY_ADMIN' }) - export const readPage = RequirePermission.staticFields({ permission: 'COMPANY_READ' }) - export const update = RequirePermission.staticFields({ permission: 'COMPANY_ADMIN' }) - export const destroy = RequirePermission.staticFields({ permission: 'COMPANY_ADMIN' }) -} diff --git a/src/services/career/companies/config.ts b/src/services/career/companies/config.ts deleted file mode 100644 index b1ff503c1..000000000 --- a/src/services/career/companies/config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Prisma } from '@prisma/client' - -export namespace CompanyConfig { - export const relationIncluder = { - logo: { - include: { - image: true - } - } - } as const satisfies Prisma.CompanyInclude -} diff --git a/src/services/career/companies/constants.ts b/src/services/career/companies/constants.ts new file mode 100644 index 000000000..57c223cdc --- /dev/null +++ b/src/services/career/companies/constants.ts @@ -0,0 +1,9 @@ +import type { Prisma } from '@prisma/client' + +export const logoIncluder = { + logo: { + include: { + image: true + } + } +} as const satisfies Prisma.CompanyInclude diff --git a/src/services/career/companies/methods.ts b/src/services/career/companies/operations.ts similarity index 55% rename from src/services/career/companies/methods.ts rename to src/services/career/companies/operations.ts index 60c0e1759..0446e614c 100644 --- a/src/services/career/companies/methods.ts +++ b/src/services/career/companies/operations.ts @@ -1,20 +1,20 @@ import '@pn-server-only' -import { CompanySchemas } from './schemas' -import { CompanyAuthers } from './authers' -import { CompanyConfig } from './config' +import { companyAuth } from './auth' +import { logoIncluder } from './constants' +import { companySchemas } from './schemas' import { createCmsImage } from '@/services/cms/images/create' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' import { readPageInputSchemaObject } from '@/lib/paging/schema' import { v4 as uuid } from 'uuid' import { z } from 'zod' -export namespace CompanyMethods { - export const create = ServiceMethod({ - dataSchema: CompanySchemas.create, - auther: () => CompanyAuthers.create.dynamicFields({}), - method: async ({ prisma, data }) => { - //TODO: tranaction when createCmsImage is service method. +export const companyOperations = { + create: defineOperation({ + dataSchema: companySchemas.create, + authorizer: () => companyAuth.create.dynamicFields({}), + operation: async ({ prisma, data }) => { + //TODO: tranaction when createCmsImage is service operation. const logo = await createCmsImage({ name: uuid() }) return await prisma.company.create({ data: { @@ -23,8 +23,8 @@ export namespace CompanyMethods { } }) } - }) - export const readPage = ServiceMethod({ + }), + readPage: defineOperation({ paramsSchema: readPageInputSchemaObject( z.number(), z.object({ @@ -34,8 +34,8 @@ export namespace CompanyMethods { name: z.string().optional(), }), ), - auther: () => CompanyAuthers.readPage.dynamicFields({}), - method: async ({ prisma, params }) => await prisma.company.findMany({ + authorizer: () => companyAuth.readPage.dynamicFields({}), + operation: async ({ prisma, params }) => await prisma.company.findMany({ ...cursorPageingSelection(params.paging.page), where: { name: { @@ -43,33 +43,33 @@ export namespace CompanyMethods { mode: 'insensitive' } }, - include: CompanyConfig.relationIncluder + include: logoIncluder, }) - }) - export const update = ServiceMethod({ + }), + update: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - dataSchema: CompanySchemas.update, - auther: () => CompanyAuthers.update.dynamicFields({}), - method: async ({ prisma, params: { id }, data }) => { + dataSchema: companySchemas.update, + authorizer: () => companyAuth.update.dynamicFields({}), + operation: async ({ prisma, params: { id }, data }) => { await prisma.company.update({ where: { id }, data, }) }, - }) - export const destroy = ServiceMethod({ + }), + destroy: defineOperation({ paramsSchema: z.object({ id: z.number() }), - auther: () => CompanyAuthers.destroy.dynamicFields({}), - method: async ({ prisma, params: { id } }) => { + authorizer: () => companyAuth.destroy.dynamicFields({}), + operation: async ({ prisma, params: { id } }) => { await prisma.company.delete({ where: { id } }) } - }) + }), } diff --git a/src/services/career/companies/schemas.ts b/src/services/career/companies/schemas.ts index 707b896d2..17391806c 100644 --- a/src/services/career/companies/schemas.ts +++ b/src/services/career/companies/schemas.ts @@ -1,22 +1,23 @@ import { z } from 'zod' -export namespace CompanySchemas { - const fields = z.object({ - name: z.string().min( - 2, 'Navnet må være minst 3 tegn langt' - ).max( - 100, 'Navnet kan maks være 100 tegn langt' - ).trim(), - description: z.string().max( - 200, 'Beskrivelsen kan maks være 200 tegn langt' - ).trim(), - }) - export const create = fields.pick({ +const baseSchema = z.object({ + name: z.string().min( + 2, 'Navnet må være minst 3 tegn langt' + ).max( + 100, 'Navnet kan maks være 100 tegn langt' + ).trim(), + description: z.string().max( + 200, 'Beskrivelsen kan maks være 200 tegn langt' + ).trim(), +}) + +export const companySchemas = { + create: baseSchema.pick({ name: true, description: true, - }) - export const update = fields.partial().pick({ + }), + update: baseSchema.partial().pick({ name: true, description: true, - }) + }), } diff --git a/src/services/career/companies/Types.ts b/src/services/career/companies/types.ts similarity index 75% rename from src/services/career/companies/Types.ts rename to src/services/career/companies/types.ts index 47fc8f1cb..bbee7c6e2 100644 --- a/src/services/career/companies/Types.ts +++ b/src/services/career/companies/types.ts @@ -1,4 +1,4 @@ -import type { ExpandedCmsImage } from '@/services/cms/images/Types' +import type { ExpandedCmsImage } from '@/cms/images/types' import type { Company } from '@prisma/client' export type CompanyCursor = { id: number } diff --git a/src/services/career/jobAds/actions.ts b/src/services/career/jobAds/actions.ts new file mode 100644 index 000000000..040ae8daf --- /dev/null +++ b/src/services/career/jobAds/actions.ts @@ -0,0 +1,14 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { jobAdOperations } from '@/services/career/jobAds/operations' + +export const createJobAdAction = makeAction(jobAdOperations.create) + +export const destroyJobAdAction = makeAction(jobAdOperations.destroy) + +export const readJobAdAction = makeAction(jobAdOperations.read) +export const readActiveJobAdsAction = makeAction(jobAdOperations.readActive) +export const readInactiveJobAdsPageAction = makeAction(jobAdOperations.readInactivePage) + +export const updateJobAdAction = makeAction(jobAdOperations.update) diff --git a/src/services/career/jobAds/auth.ts b/src/services/career/jobAds/auth.ts new file mode 100644 index 000000000..86b6543aa --- /dev/null +++ b/src/services/career/jobAds/auth.ts @@ -0,0 +1,10 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const jobAdAuth = { + create: RequirePermission.staticFields({ permission: 'JOBAD_CREATE' }), + read: RequirePermission.staticFields({ permission: 'JOBAD_READ' }), + readActive: RequirePermission.staticFields({ permission: 'JOBAD_READ' }), + readInactivePage: RequirePermission.staticFields({ permission: 'JOBAD_READ' }), + update: RequirePermission.staticFields({ permission: 'JOBAD_UPDATE' }), + destroy: RequirePermission.staticFields({ permission: 'JOBAD_DESTROY' }), +} diff --git a/src/services/career/jobAds/authers.ts b/src/services/career/jobAds/authers.ts deleted file mode 100644 index a3f985fb7..000000000 --- a/src/services/career/jobAds/authers.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace JobAdAuthers { - export const create = RequirePermission.staticFields({ permission: 'JOBAD_CREATE' }) - export const read = RequirePermission.staticFields({ permission: 'JOBAD_READ' }) - export const readActive = RequirePermission.staticFields({ permission: 'JOBAD_READ' }) - export const readInactivePage = RequirePermission.staticFields({ permission: 'JOBAD_READ' }) - export const update = RequirePermission.staticFields({ permission: 'JOBAD_UPDATE' }) - export const destroy = RequirePermission.staticFields({ permission: 'JOBAD_DESTROY' }) -} diff --git a/src/services/career/jobAds/config.ts b/src/services/career/jobAds/config.ts deleted file mode 100644 index cdf429f89..000000000 --- a/src/services/career/jobAds/config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { articleRealtionsIncluder } from '@/cms/articles/ConfigVars' -import { JobType } from '@prisma/client' -import type { Prisma } from '@prisma/client' - - -export namespace JobAdConfig { - export const type = { - FULL_TIME: { label: 'Heltid' }, - PART_TIME: { label: 'Deltid' }, - INTERNSHIP: { label: 'Internship' }, - OTHER: { label: 'Annet' }, - CONTRACT: { label: 'Kontrakt' }, - } satisfies Record - export const relationIncluder = { - article: { - include: articleRealtionsIncluder - }, - company: true - } as const satisfies Prisma.JobAdInclude - export const simpleRelationIncluder = { - company: { - select: { - name: true, - } - }, - article: { - include: { - coverImage: { - include: { - image: true - } - } - } - } - } as const satisfies Prisma.JobAdInclude - export const options = Object.values(JobType).map((opt): { value: JobType, label: string } => ({ - value: opt, - label: type[opt].label - })) -} diff --git a/src/services/career/jobAds/constants.ts b/src/services/career/jobAds/constants.ts new file mode 100644 index 000000000..c9b7c5879 --- /dev/null +++ b/src/services/career/jobAds/constants.ts @@ -0,0 +1,41 @@ +import { articleRealtionsIncluder } from '@/cms/articles/ConfigVars' +import { JobType } from '@prisma/client' +import type { Prisma } from '@prisma/client' + + +export const jobAdType = { + FULL_TIME: { label: 'Heltid' }, + PART_TIME: { label: 'Deltid' }, + INTERNSHIP: { label: 'Internship' }, + OTHER: { label: 'Annet' }, + CONTRACT: { label: 'Kontrakt' }, +} satisfies Record + +export const articleAndCompanyIncluder = { + article: { + include: articleRealtionsIncluder + }, + company: true +} as const satisfies Prisma.JobAdInclude + +export const simpleArticleAndCompanyIncluder = { + company: { + select: { + name: true, + } + }, + article: { + include: { + coverImage: { + include: { + image: true + } + } + } + } +} as const satisfies Prisma.JobAdInclude + +export const jobAdOptions = Object.values(JobType).map((opt): { value: JobType, label: string } => ({ + value: opt, + label: jobAdType[opt].label +})) diff --git a/src/services/career/jobAds/methods.ts b/src/services/career/jobAds/operations.ts similarity index 72% rename from src/services/career/jobAds/methods.ts rename to src/services/career/jobAds/operations.ts index 9351e0205..ba0be1191 100644 --- a/src/services/career/jobAds/methods.ts +++ b/src/services/career/jobAds/operations.ts @@ -1,24 +1,24 @@ import '@pn-server-only' -import { JobAdSchemas } from './schemas' -import { JobAdAuthers } from './authers' -import { JobAdConfig } from './config' -import { ServiceMethod } from '@/services/ServiceMethod' +import { jobAdAuth } from './auth' +import { jobAdSchemas } from './schemas' +import { articleAndCompanyIncluder, simpleArticleAndCompanyIncluder } from './constants' +import { logoIncluder } from '@/services/career/companies/constants' +import { defineOperation } from '@/services/serviceOperation' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' import { createArticle } from '@/services/cms/articles/create' -import { CompanyConfig } from '@/career/companies/config' import { ServerError } from '@/services/error' import { readPageInputSchemaObject } from '@/lib/paging/schema' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' import { destroyArticle } from '@/services/cms/articles/destroy' import { z } from 'zod' import { JobType } from '@prisma/client' -import type { ExpandedJobAd, SimpleJobAd } from './Types' +import type { ExpandedJobAd, SimpleJobAd } from './types' -export namespace JobadMethods { - export const create = ServiceMethod({ - dataSchema: JobAdSchemas.create, - auther: () => JobAdAuthers.create.dynamicFields({}), - method: async ({ prisma, data: { articleName, companyId, ...data } }) => { +export const jobAdOperations = { + create: defineOperation({ + dataSchema: jobAdSchemas.create, + authorizer: () => jobAdAuth.create.dynamicFields({}), + operation: async ({ prisma, data: { articleName, companyId, ...data } }) => { const article = await createArticle({ name: articleName }) const currentOrder = await readCurrentOmegaOrder() @@ -42,13 +42,13 @@ export namespace JobadMethods { }, }) } - }) + }), /** * This handler reads a jobAd by id or articleName and order * @param idOrName - id or articleName and order of jobAd to read (id or {articleName: string, order: number}) * @returns ExpandedJobAd - the jobAd and its article */ - export const read = ServiceMethod({ + read: defineOperation({ paramsSchema: z.object({ idOrName: z.union([ z.number(), @@ -58,8 +58,8 @@ export namespace JobadMethods { }), ]), }), - auther: () => JobAdAuthers.read.dynamicFields({}), - method: async ({ prisma, params: { idOrName } }): Promise => { + authorizer: () => jobAdAuth.read.dynamicFields({}), + operation: async ({ prisma, params: { idOrName } }): Promise => { const jobAd = await prisma.jobAd.findUnique({ where: typeof idOrName === 'number' ? { id: idOrName @@ -70,23 +70,23 @@ export namespace JobadMethods { } }, include: { - ...JobAdConfig.relationIncluder, + ...articleAndCompanyIncluder, company: { - include: CompanyConfig.relationIncluder, + include: logoIncluder, } } }) if (!jobAd) throw new ServerError('NOT FOUND', `job ad ${idOrName} not found`) return jobAd } - }) + }), /** * This handler reads all active jobAds * @returns SimpleJobAd[] - all jobAds with coverImage */ - export const readActive = ServiceMethod({ - auther: () => JobAdAuthers.readActive.dynamicFields({}), - method: async ({ prisma }): Promise => { + readActive: defineOperation({ + authorizer: () => jobAdAuth.readActive.dynamicFields({}), + operation: async ({ prisma }): Promise => { const jobAds = await prisma.jobAd.findMany({ orderBy: { article: { @@ -96,7 +96,7 @@ export namespace JobadMethods { where: { active: true, }, - include: JobAdConfig.simpleRelationIncluder, + include: simpleArticleAndCompanyIncluder, }) return jobAds.map(ad => ({ ...ad, @@ -104,12 +104,12 @@ export namespace JobadMethods { companyName: ad.company.name, })) } - }) + }), /** * This handler reads a page of inactive jobAds * @param paging - the page to read, includes details to filter by name (articleName) and the type. */ - export const readInactivePage = ServiceMethod({ + readInactivePage: defineOperation({ paramsSchema: readPageInputSchemaObject( z.number(), z.object({ @@ -120,8 +120,8 @@ export namespace JobadMethods { type: z.nativeEnum(JobType).nullable(), }), ), - auther: () => JobAdAuthers.readInactivePage.dynamicFields({}), - method: async ({ prisma, params }): Promise => { + authorizer: () => jobAdAuth.readInactivePage.dynamicFields({}), + operation: async ({ prisma, params }): Promise => { const jobAds = await prisma.jobAd.findMany({ ...cursorPageingSelection(params.paging.page), where: { @@ -134,7 +134,7 @@ export namespace JobadMethods { }, type: params.paging.details.type || undefined, }, - include: JobAdConfig.simpleRelationIncluder, + include: simpleArticleAndCompanyIncluder, }) return jobAds.map(ad => ({ ...ad, @@ -142,34 +142,34 @@ export namespace JobadMethods { companyName: ad.company.name, })) } - }) + }), /** * This handler destroys a jobAd. It is also responsible for cleaning up the article, * to avoid orphaned articles. It calls destroyArticle to destroy the article and its coverImage (cmsImage) * @param id - id of news article to destroy * @returns */ - export const update = ServiceMethod({ + update: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - dataSchema: JobAdSchemas.update, - auther: () => JobAdAuthers.update.dynamicFields({}), - method: async ({ prisma, params: { id }, data }) => await prisma.jobAd.update({ + dataSchema: jobAdSchemas.update, + authorizer: () => jobAdAuth.update.dynamicFields({}), + operation: async ({ prisma, params: { id }, data }) => await prisma.jobAd.update({ where: { id }, data, }) - }) - export const destroy = ServiceMethod({ + }), + destroy: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - auther: () => JobAdAuthers.destroy.dynamicFields({}), - method: async ({ prisma, params: { id } }) => { + authorizer: () => jobAdAuth.destroy.dynamicFields({}), + operation: async ({ prisma, params: { id } }) => { const jobAd = await prisma.jobAd.delete({ where: { id }, }) await destroyArticle(jobAd.articleId) } - }) + }), } diff --git a/src/services/career/jobAds/schemas.ts b/src/services/career/jobAds/schemas.ts index a8e5e0767..835405bd6 100644 --- a/src/services/career/jobAds/schemas.ts +++ b/src/services/career/jobAds/schemas.ts @@ -1,33 +1,34 @@ -import { zpn } from '@/lib/fields/zpn' +import { Zpn } from '@/lib/fields/zpn' import { z } from 'zod' import { JobType } from '@prisma/client' -export namespace JobAdSchemas { - const fields = z.object({ - companyId: z.coerce.number({ - errorMap: () => ({ message: 'Velg en bedrift' }), - }).int().positive().int(), - articleName: z.string().max(50, 'max lengde 50').min(2, 'min lengde 2'), - description: z.string().max(200, 'max lengde 200').min(2, 'min lengde 2').or(z.literal('')), - type: z.nativeEnum(JobType), - applicationDeadline: zpn.date({ label: 'Søknadsfrist' }), - active: zpn.checkboxOrBoolean({ label: 'Aktiv' }), - location: z.string().optional(), - }) - export const create = fields.pick({ +const baseSchema = z.object({ + companyId: z.coerce.number({ + errorMap: () => ({ message: 'Velg en bedrift' }), + }).int().positive().int(), + articleName: z.string().max(50, 'max lengde 50').min(2, 'min lengde 2'), + description: z.string().max(200, 'max lengde 200').min(2, 'min lengde 2').or(z.literal('')), + type: z.nativeEnum(JobType), + applicationDeadline: Zpn.date({ label: 'Søknadsfrist' }), + active: Zpn.checkboxOrBoolean({ label: 'Aktiv' }), + location: z.string().optional(), +}) + +export const jobAdSchemas = { + create: baseSchema.pick({ companyId: true, articleName: true, description: true, type: true, applicationDeadline: true, location: true, - }) - export const update = fields.partial().pick({ + }), + update: baseSchema.partial().pick({ companyId: true, description: true, type: true, applicationDeadline: true, active: true, location: true, - }) + }), } diff --git a/src/services/career/jobAds/Types.ts b/src/services/career/jobAds/types.ts similarity index 80% rename from src/services/career/jobAds/Types.ts rename to src/services/career/jobAds/types.ts index 3b5d43303..e4d8a8fed 100644 --- a/src/services/career/jobAds/Types.ts +++ b/src/services/career/jobAds/types.ts @@ -1,5 +1,5 @@ -import type { CompanyExpanded } from '@/career/companies/Types' -import type { ExpandedArticle } from '@/cms/articles/Types' +import type { CompanyExpanded } from '@/services/career/companies/types' +import type { ExpandedArticle } from '@/cms/articles/types' import type { JobAd, Image, JobType } from '@prisma/client' export type ExpandedJobAd = JobAd & { article: ExpandedArticle, diff --git a/src/services/cms/articleCategories/actions.ts b/src/services/cms/articleCategories/actions.ts new file mode 100644 index 000000000..38ae040a3 --- /dev/null +++ b/src/services/cms/articleCategories/actions.ts @@ -0,0 +1,65 @@ +'use server' + +import { createZodActionError, safeServerCall } from '@/services/actionError' +import { createArticleCategory } from '@/services/cms/articleCategories/create' +import { destroyArticleCategory } from '@/services/cms/articleCategories/destroy' +import { readArticleCategories, readArticleCategory } from '@/services/cms/articleCategories/read' +import { updateArticleCategory } from '@/services/cms/articleCategories/update' +import { + createArticleCategoryValidation, + updateArticleCategoryValidation, +} from '@/services/cms/articleCategories/validation' +import type { + ArticleCategoryWithCover, + ExpandedArticleCategoryWithCover, + ExpandedArticleCategory, +} from '@/cms/articleCategories/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateArticleCategoryTypes, UpdateArticleCategoryTypes } from '@/services/cms/articleCategories/validation' + +export async function createArticleCategoryAction( + rawData: FormData | CreateArticleCategoryTypes['Type'] +): Promise> { + //TODO: check permission + const parse = createArticleCategoryValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createArticleCategory(data)) +} + +export async function destroyArticleCategoryAction(id: number): Promise> { + // TODO: Cheek for visibility type edit of user. + return await safeServerCall(() => destroyArticleCategory(id)) +} + +export async function readArticleCategoriesAction(): Promise> { + //TODO: only read categories that user has visibility + return await safeServerCall(() => readArticleCategories()) +} + +export async function readArticleCategoryAction(name: string): Promise> { + //TODO: only read if right visibility + return await safeServerCall(() => readArticleCategory(name)) +} + +export async function updateArticleCategoryVisibilityAction( + // disable eslint rule temporarily until function is implemented + // eslint-disable-next-line @typescript-eslint/no-unused-vars + id: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + visibility: unknown +): Promise> { + throw new Error('Not implemented') +} + +export async function updateArticleCategoryAction( + id: number, + rawData: FormData | UpdateArticleCategoryTypes['Type'] +): Promise> { + const parse = updateArticleCategoryValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => updateArticleCategory(id, data)) +} diff --git a/src/services/cms/articleCategories/create.ts b/src/services/cms/articleCategories/create.ts index 80c3f09b6..9fcb1a319 100644 --- a/src/services/cms/articleCategories/create.ts +++ b/src/services/cms/articleCategories/create.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { createArticleCategoryValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CreateArticleCategoryTypes } from './validation' -import type { ExpandedArticleCategory } from './Types' +import type { ExpandedArticleCategory } from './types' export async function createArticleCategory( rawData: CreateArticleCategoryTypes['Detailed'] diff --git a/src/services/cms/articleCategories/destroy.ts b/src/services/cms/articleCategories/destroy.ts index 1cb9fd635..eb4fde3b6 100644 --- a/src/services/cms/articleCategories/destroy.ts +++ b/src/services/cms/articleCategories/destroy.ts @@ -1,7 +1,7 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' -import type { ExpandedArticleCategory } from '@/cms/articleCategories/Types' +import type { ExpandedArticleCategory } from '@/cms/articleCategories/types' export async function destroyArticleCategory(id: number): Promise { return await prismaCall(() => prisma.articleCategory.delete({ diff --git a/src/services/cms/articleCategories/read.ts b/src/services/cms/articleCategories/read.ts index 9e6d46bfb..246783a58 100644 --- a/src/services/cms/articleCategories/read.ts +++ b/src/services/cms/articleCategories/read.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import type { Image } from '@prisma/client' @@ -7,7 +7,7 @@ import type { ExpandedArticleCategory, ExpandedArticleCategoryWithCover, ArticleCategoryWithCover, -} from '@/cms/articleCategories/Types' +} from '@/cms/articleCategories/types' /** * Reads all article categories, and assaigs each of them a cover based on the cover of first article diff --git a/src/services/cms/articleCategories/Types.ts b/src/services/cms/articleCategories/types.ts similarity index 100% rename from src/services/cms/articleCategories/Types.ts rename to src/services/cms/articleCategories/types.ts diff --git a/src/services/cms/articleCategories/update.ts b/src/services/cms/articleCategories/update.ts index 4605d7aa8..ab9f61ca0 100644 --- a/src/services/cms/articleCategories/update.ts +++ b/src/services/cms/articleCategories/update.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { updateArticleCategoryValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { UpdateArticleCategoryTypes } from './validation' -import type { ExpandedArticleCategory } from '@/cms/articleCategories/Types' +import type { ExpandedArticleCategory } from '@/cms/articleCategories/types' export async function updateArticleCategory( id: number, diff --git a/src/services/cms/articleSections/actions.ts b/src/services/cms/articleSections/actions.ts new file mode 100644 index 000000000..abadb9574 --- /dev/null +++ b/src/services/cms/articleSections/actions.ts @@ -0,0 +1,57 @@ +'use server' + +import { createZodActionError, safeServerCall } from '@/services/actionError' +import { createArticleSection } from '@/services/cms/articleSections/create' +import { destroyArticleSection } from '@/services/cms/articleSections/destroy' +import { readArticleSection } from '@/services/cms/articleSections/read' +import { addArticleSectionPart, removeArticleSectionPart, updateArticleSection } from '@/services/cms/articleSections/update' +import { createArticleSectionValidation } from '@/services/cms/articleSections/validation' +import type { ArticleSectionPart, ExpandedArticleSection } from '@/cms/articleSections/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateArticleSectionTypes } from '@/services/cms/articleSections/validation' +import type { ArticleSection, Position } from '@prisma/client' + +export async function createArticleSectionAction( + rawData: FormData | CreateArticleSectionTypes['Type'], +): Promise> { + //TODO: Auth on general cms permission + const parse = createArticleSectionValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createArticleSection(data)) +} + +export async function destroyArticleSectionAction(nameOrId: string): Promise> { + //Auth by visibility + return await safeServerCall(() => destroyArticleSection(nameOrId)) +} + +export async function readArticleSectionAction(name: string): Promise> { + //TODO: Auth by visibility + return await safeServerCall(() => readArticleSection(name)) +} + +export async function updateArticleSectionAction(name: string, changes: { + imageSize?: number, + imagePosition?: Position, +}): Promise> { + //Todo: Auth by visibilty + return await safeServerCall(() => updateArticleSection(name, changes)) +} + +export async function addArticleSectionPartAction( + name: string, + part: ArticleSectionPart +): Promise> { + //Todo: Auth by visibilty + return await safeServerCall(() => addArticleSectionPart(name, part)) +} + +export async function removeArticleSectionPartAction( + name: string, + part: ArticleSectionPart +): Promise> { + //TODO: Auth by visibility + return await safeServerCall(() => removeArticleSectionPart(name, part)) +} diff --git a/src/services/cms/articleSections/create.ts b/src/services/cms/articleSections/create.ts index 4cad9cbef..4dfa268d0 100644 --- a/src/services/cms/articleSections/create.ts +++ b/src/services/cms/articleSections/create.ts @@ -1,10 +1,10 @@ import '@pn-server-only' import { articleSectionsRealtionsIncluder } from './ConfigVars' import { createArticleSectionValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CreateArticleSectionTypes } from './validation' -import type { ExpandedArticleSection } from './Types' +import type { ExpandedArticleSection } from './types' /** * A function to create a new articleSection diff --git a/src/services/cms/articleSections/destroy.ts b/src/services/cms/articleSections/destroy.ts index 2d09859e3..1ffbce482 100644 --- a/src/services/cms/articleSections/destroy.ts +++ b/src/services/cms/articleSections/destroy.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { ArticleSection } from '@prisma/client' diff --git a/src/services/cms/articleSections/read.ts b/src/services/cms/articleSections/read.ts index 458d7ceef..49a1d6ed2 100644 --- a/src/services/cms/articleSections/read.ts +++ b/src/services/cms/articleSections/read.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { articleSectionsRealtionsIncluder } from '@/cms/articleSections/ConfigVars' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' -import type { ExpandedArticleSection } from '@/cms/articleSections/Types' +import type { ExpandedArticleSection } from '@/cms/articleSections/types' /** * Reads an article section diff --git a/src/services/cms/articleSections/Types.ts b/src/services/cms/articleSections/types.ts similarity index 84% rename from src/services/cms/articleSections/Types.ts rename to src/services/cms/articleSections/types.ts index 8808f3608..581bb3b61 100644 --- a/src/services/cms/articleSections/Types.ts +++ b/src/services/cms/articleSections/types.ts @@ -1,4 +1,4 @@ -import type { ExpandedCmsImage } from '@/cms/images/Types' +import type { ExpandedCmsImage } from '@/cms/images/types' import type { ArticleSection, CmsParagraph, CmsLink } from '@prisma/client' export type ArticleSectionPart = 'cmsLink' | 'cmsParagraph' | 'cmsImage' diff --git a/src/services/cms/articleSections/update.ts b/src/services/cms/articleSections/update.ts index f71fd9f44..d051b7de8 100644 --- a/src/services/cms/articleSections/update.ts +++ b/src/services/cms/articleSections/update.ts @@ -4,14 +4,14 @@ import { destroyCmsImage } from '@/services/cms/images/destoy' import { destroyCmsLink } from '@/services/cms/links/destroy' import { destroyCmsParagraph } from '@/services/cms/paragraphs/destroy' import { maxImageSize, minImageSize, articleSectionsRealtionsIncluder } from '@/cms/articleSections/ConfigVars' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { createCmsImage } from '@/services/cms/images/create' import { createCmsParagraph } from '@/services/cms/paragraphs/create' import { createCmsLink } from '@/services/cms/links/create' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import type { ImageSize, ArticleSection, Position, Prisma } from '@prisma/client' -import type { ExpandedArticleSection, ArticleSectionPart } from '@/cms/articleSections/Types' +import type { ExpandedArticleSection, ArticleSectionPart } from '@/cms/articleSections/types' /** * This is the function that updates an article section metadata about how the (cms)image is displayed diff --git a/src/services/cms/articles/actions.ts b/src/services/cms/articles/actions.ts new file mode 100644 index 000000000..03777a275 --- /dev/null +++ b/src/services/cms/articles/actions.ts @@ -0,0 +1,68 @@ +'use server' + +import { createZodActionError, safeServerCall } from '@/services/actionError' +import { createArticle } from '@/services/cms/articles/create' +import { destroyArticle } from '@/services/cms/articles/destroy' +import { readArticle } from '@/services/cms/articles/read' +import { addSectionToArticle, moveSectionOrder, updateArticle } from '@/services/cms/articles/update' +import { createArticleValidation, updateArticleValidation } from '@/services/cms/articles/validation' +import type { ExpandedArticle } from '@/cms/articles/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateArticleTypes, UpdateArticleTypes } from '@/services/cms/articles/validation' +import type { ArticleSectionPart } from '@/cms/articleSections/types' +import type { Article, ArticleSection } from '@prisma/client' + +export async function createArticleAction( + rawData: FormData | CreateArticleTypes['Type'], + categoryId?: number, +): Promise> { + //TODO: auth on permission or visibility to categoryId + + const parse = createArticleValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createArticle(data, categoryId)) +} + +export async function destroyArticleAction(id: number): Promise> { + //TODO: auth + return await safeServerCall(() => destroyArticle(id)) +} + +export async function readArticleAction(idOrName: number | { + name: string, + category: string +}): Promise> { + //TODO: auth + return await safeServerCall(() => readArticle(idOrName)) +} + +export async function updateArticleAction( + id: number, + rawData: FormData | UpdateArticleTypes['Type'] +): Promise> { + //TODO: auth on visability + const parse = updateArticleValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => updateArticle(id, data)) +} + +export async function addSectionToArticleAction( + id: number, + include: Partial> +): Promise> { + //TODO: auth on visability + return await safeServerCall(() => addSectionToArticle(id, include)) +} + +export async function moveSectionOrderAction( + id: number, + sectionId: number, + direction: 'UP' | 'DOWN' +): Promise> { + //TODO: auth on visability + return await safeServerCall(() => moveSectionOrder(id, sectionId, direction)) +} diff --git a/src/services/cms/articles/create.ts b/src/services/cms/articles/create.ts index a8bfdeb66..cbb48669f 100644 --- a/src/services/cms/articles/create.ts +++ b/src/services/cms/articles/create.ts @@ -1,10 +1,10 @@ import '@pn-server-only' import { articleRealtionsIncluder } from './ConfigVars' import { createArticleValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CreateArticleTypes } from './validation' -import type { ExpandedArticle } from './Types' +import type { ExpandedArticle } from './types' /** * A function to create a new article. It will have no content (sections) and cover image will relate diff --git a/src/services/cms/articles/destroy.ts b/src/services/cms/articles/destroy.ts index c27acb3e2..22fa99d6c 100644 --- a/src/services/cms/articles/destroy.ts +++ b/src/services/cms/articles/destroy.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { destroyCmsImage } from '@/cms/images/destoy' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { Article } from '@prisma/client' diff --git a/src/services/cms/articles/read.ts b/src/services/cms/articles/read.ts index 6d6fd17d2..dcbd5bb2f 100644 --- a/src/services/cms/articles/read.ts +++ b/src/services/cms/articles/read.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { articleRealtionsIncluder } from './ConfigVars' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' -import type { ExpandedArticle } from './Types' +import type { ExpandedArticle } from './types' /** * A function that reads an article with all the neccessary data included like paragraphs, images, etc... diff --git a/src/services/cms/articles/Types.ts b/src/services/cms/articles/types.ts similarity index 77% rename from src/services/cms/articles/Types.ts rename to src/services/cms/articles/types.ts index d24926301..2f0c91790 100644 --- a/src/services/cms/articles/Types.ts +++ b/src/services/cms/articles/types.ts @@ -1,6 +1,6 @@ -import type { ExpandedArticleSection } from '@/cms/articleSections/Types' +import type { ExpandedArticleSection } from '@/cms/articleSections/types' import type { Article } from '@prisma/client' -import type { ExpandedCmsImage } from '@/cms/images/Types' +import type { ExpandedCmsImage } from '@/cms/images/types' export type ExpandedArticle = Article & { articleSections: ExpandedArticleSection[], diff --git a/src/services/cms/articles/update.ts b/src/services/cms/articles/update.ts index 1d585d05b..6ad5bf086 100644 --- a/src/services/cms/articles/update.ts +++ b/src/services/cms/articles/update.ts @@ -1,14 +1,14 @@ import '@pn-server-only' import { updateArticleValidation } from './validation' import { articleRealtionsIncluder, maxSections } from '@/cms/articles/ConfigVars' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { addArticleSectionPart } from '@/services/cms/articleSections/update' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import type { UpdateArticleTypes } from './validation' -import type { ArticleSectionPart } from '@/services/cms/articleSections/Types' +import type { ArticleSectionPart } from '@/cms/articleSections/types' import type { ArticleSection } from '@prisma/client' -import type { ExpandedArticle } from '@/cms/articles/Types' +import type { ExpandedArticle } from '@/cms/articles/types' /** * A function to update metadata of an article. This includes for ex. name. diff --git a/src/services/cms/images/actions.ts b/src/services/cms/images/actions.ts new file mode 100644 index 000000000..31f0c0bd4 --- /dev/null +++ b/src/services/cms/images/actions.ts @@ -0,0 +1,71 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { createCmsImage } from '@/services/cms/images/create' +import { readCmsImage, readSpecialCmsImage } from '@/services/cms/images/read' +import { updateCmsImage, updateCmsImageConfig } from '@/services/cms/images/update' +import { createCmsImageActionValidation } from '@/services/cms/images/validation' +import { SpecialCmsImage } from '@prisma/client' +import type { CreateCmsImageActionTypes } from '@/services/cms/images/validation' +import type { ExpandedCmsImage } from '@/cms/images/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CmsImage, Image, ImageSize } from '@prisma/client' + +export async function createCmsImageAction( + rawData: FormData | CreateCmsImageActionTypes['Type'], + image?: Image, +): Promise> { + //TODO: Auth route (very few people should be able to create stand alone cmsImages...) + const parse = createCmsImageActionValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createCmsImage(data, image)) +} + +/** + * A action to read a cms image including the image associated with it + * @param name - name of the cms image the image + * @returns + */ +export async function readCmsImageAction(name: string): Promise> { + //TODO: auth on visibilty + return await safeServerCall(() => readCmsImage(name)) +} + +/** + * Action to reads a special cmsImage, if it does not exist it creates it + * @param special SpecialCmsImage + * @returns ActionReturn + */ +export async function readSpecialCmsImageAction(special: SpecialCmsImage): Promise> { + if (!Object.values(SpecialCmsImage).includes(special)) { + return createActionError('BAD PARAMETERS', `${special} is not special`) + } + const specialRes = await safeServerCall(() => readSpecialCmsImage(special)) + if (!specialRes.success) { + if (specialRes.errorCode === 'NOT FOUND') { + return await safeServerCall(() => createCmsImage({ + name: special, + special, + })) + } + return specialRes + } + const cmsImage = specialRes.data + //TODO: Auth on visibilty + return { success: true, data: cmsImage } +} + +export async function updateCmsImageAction(cmsImageId: number, imageId: number): Promise> { + //TODO: Auth on visibility (or permission if special) + return await safeServerCall(() => updateCmsImage(cmsImageId, imageId)) +} + +export async function updateCmsImageConfigAction( + cmsImageId: number, + config: {imageSize: ImageSize} +): Promise> { + //TODO: Auth on visibility (or permission if special) + return await safeServerCall(() => updateCmsImageConfig(cmsImageId, config)) +} diff --git a/src/services/cms/images/create.ts b/src/services/cms/images/create.ts index be854cfbe..d86f2cb34 100644 --- a/src/services/cms/images/create.ts +++ b/src/services/cms/images/create.ts @@ -1,10 +1,10 @@ import '@pn-server-only' import { createCmsImageValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CreateCmsImageTypes } from './validation' import type { Image } from '@prisma/client' -import type { ExpandedCmsImage } from './Types' +import type { ExpandedCmsImage } from './types' /** * A function to create a cmsImage diff --git a/src/services/cms/images/destoy.ts b/src/services/cms/images/destoy.ts index 1a3eee322..c2fca396a 100644 --- a/src/services/cms/images/destoy.ts +++ b/src/services/cms/images/destoy.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CmsImage } from '@prisma/client' diff --git a/src/services/cms/images/read.ts b/src/services/cms/images/read.ts index 05256d228..ee9f402aa 100644 --- a/src/services/cms/images/read.ts +++ b/src/services/cms/images/read.ts @@ -1,9 +1,9 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' import type { SpecialCmsImage } from '@prisma/client' -import type { ExpandedCmsImage } from '@/cms/images/Types' +import type { ExpandedCmsImage } from '@/cms/images/types' /** * Read a cms image including the image associated with it diff --git a/src/services/cms/images/Types.ts b/src/services/cms/images/types.ts similarity index 100% rename from src/services/cms/images/Types.ts rename to src/services/cms/images/types.ts diff --git a/src/services/cms/images/update.ts b/src/services/cms/images/update.ts index 36706e4e8..15eba1158 100644 --- a/src/services/cms/images/update.ts +++ b/src/services/cms/images/update.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CmsImage, ImageSize } from '@prisma/client' diff --git a/src/services/cms/images/validation.ts b/src/services/cms/images/validation.ts index 30f4fc6e9..eb2ac266a 100644 --- a/src/services/cms/images/validation.ts +++ b/src/services/cms/images/validation.ts @@ -20,3 +20,10 @@ export const createCmsImageValidation = baseCmsImageValidation.createValidation( }) export type CreateCmsImageTypes = ValidationTypes + +export const createCmsImageActionValidation = baseCmsImageValidation.createValidation({ + keys: ['name'], + transformer: data => data, +}) + +export type CreateCmsImageActionTypes = ValidationTypes diff --git a/src/services/cms/links/actions.ts b/src/services/cms/links/actions.ts new file mode 100644 index 000000000..163bfe625 --- /dev/null +++ b/src/services/cms/links/actions.ts @@ -0,0 +1,38 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { createZodActionError, safeServerCall } from '@/services/actionError' +import { createCmsLink } from '@/services/cms/links/create' +import { readSpecialCmsLink } from '@/services/cms/links/read' +import { updateCmsLink } from '@/services/cms/links/update' +import { createCmsLinkValidation, updateCmsLinkValidation } from '@/services/cms/links/validation' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateCmsLinkTypes, UpdateCmsLinkTypes } from '@/services/cms/links/validation' +import type { CmsLink } from '@prisma/client' + +export async function createCmsLinkAction( + rawData: FormData | CreateCmsLinkTypes['Type'] +): Promise> { + //TODO: Auth on permission to create cms + const parse = createCmsLinkValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createCmsLink(data)) +} + +export const readSpecialCmsLinkAction = makeAction(readSpecialCmsLink) + +export async function updateCmsLinkAction( + id: number, + rawData: FormData | UpdateCmsLinkTypes['Type'] +): Promise> { + //TODO: auth on visibility + const parse = updateCmsLinkValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + if (data.url && data.url.includes('.') && !data.url.startsWith('http://') && !data.url.startsWith('https://')) { + data.url = `https://${data.url}` + } + return await safeServerCall(() => updateCmsLink(id, data)) +} diff --git a/src/services/cms/links/auth.ts b/src/services/cms/links/auth.ts new file mode 100644 index 000000000..7da372fc7 --- /dev/null +++ b/src/services/cms/links/auth.ts @@ -0,0 +1,3 @@ +import { RequireNothing } from '@/auth/auther/RequireNothing' + +export const readSpecialCmsLinkAuth = RequireNothing.staticFields({}) diff --git a/src/services/cms/links/authers.ts b/src/services/cms/links/authers.ts deleted file mode 100644 index 692502bf7..000000000 --- a/src/services/cms/links/authers.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { RequireNothing } from '@/auth/auther/RequireNothing' - -export const readSpecialCmsLinkAuther = RequireNothing.staticFields({}) diff --git a/src/services/cms/links/create.ts b/src/services/cms/links/create.ts index 4e1fc4c17..3ebd7c3b0 100644 --- a/src/services/cms/links/create.ts +++ b/src/services/cms/links/create.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { createCmsLinkValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CreateCmsLinkTypes } from './validation' import type { CmsLink } from '@prisma/client' diff --git a/src/services/cms/links/destroy.ts b/src/services/cms/links/destroy.ts index 222a52328..48946aa6d 100644 --- a/src/services/cms/links/destroy.ts +++ b/src/services/cms/links/destroy.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CmsLink } from '@prisma/client' diff --git a/src/services/cms/links/read.ts b/src/services/cms/links/read.ts index e1fb13de8..0219e0edd 100644 --- a/src/services/cms/links/read.ts +++ b/src/services/cms/links/read.ts @@ -1,16 +1,16 @@ import '@pn-server-only' -import { readSpecialCmsLinkAuther } from './authers' +import { readSpecialCmsLinkAuth } from './auth' import logger from '@/lib/logger' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import { SpecialCmsLink } from '@prisma/client' import { z } from 'zod' -export const readSpecialCmsLink = ServiceMethod({ +export const readSpecialCmsLink = defineOperation({ paramsSchema: z.object({ special: z.nativeEnum(SpecialCmsLink), }), - auther: () => readSpecialCmsLinkAuther.dynamicFields({}), - method: async ({ prisma, params: { special } }) => { + authorizer: () => readSpecialCmsLinkAuth.dynamicFields({}), + operation: async ({ prisma, params: { special } }) => { const cmsLink = await prisma.cmsLink.findUnique({ where: { special } }) diff --git a/src/services/cms/links/update.ts b/src/services/cms/links/update.ts index 420199789..f49e86916 100644 --- a/src/services/cms/links/update.ts +++ b/src/services/cms/links/update.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { updateCmsLinkValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { UpdateCmsLinkTypes } from './validation' import type { CmsLink } from '@prisma/client' diff --git a/src/actions/cms/paragraphs/read.ts b/src/services/cms/paragraphs/actions.ts similarity index 53% rename from src/actions/cms/paragraphs/read.ts rename to src/services/cms/paragraphs/actions.ts index f485398be..ab6ba0dad 100644 --- a/src/actions/cms/paragraphs/read.ts +++ b/src/services/cms/paragraphs/actions.ts @@ -1,12 +1,26 @@ 'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' import { createCmsParagraph } from '@/services/cms/paragraphs/create' -import { createActionError } from '@/actions/error' import { readCmsParagraph, readSpecialCmsParagraph } from '@/services/cms/paragraphs/read' -import { safeServerCall } from '@/actions/safeServerCall' +import { updateCmsParagraphContents } from '@/services/cms/paragraphs/update' +import { createCmsParagraphActionValidation } from '@/services/cms/paragraphs/validation' import { SpecialCmsParagraph } from '@prisma/client' -import type { ActionReturn } from '@/actions/Types' +import type { CreateCmsParagraphActionTypes } from '@/services/cms/paragraphs/validation' +import type { ActionReturn } from '@/services/actionTypes' import type { CmsParagraph } from '@prisma/client' +export async function createCmsParagraphAction( + rawData: FormData | CreateCmsParagraphActionTypes['Type'] +): Promise> { + //TDOD: Auth on cms permission (few should be able to create a paragraph standalone) + const parse = createCmsParagraphActionValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createCmsParagraph(data)) +} + export async function readCmsParagraphAction(name: string): Promise> { //TODO: Auth on visibility (or permission if special) return await safeServerCall(() => readCmsParagraph(name)) @@ -32,3 +46,8 @@ export async function readSpecialCmsParagraphAction(special: SpecialCmsParagraph } return specialRes } + +export async function updateCmsParagraphAction(id: number, contentMd: string): Promise> { + //TODO: Auth on visibility + return await safeServerCall(() => updateCmsParagraphContents(id, contentMd)) +} diff --git a/src/services/cms/paragraphs/create.ts b/src/services/cms/paragraphs/create.ts index bb8d8ae8c..1e73944f5 100644 --- a/src/services/cms/paragraphs/create.ts +++ b/src/services/cms/paragraphs/create.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { createCmsParagraphValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CreateCmsParagraphTypes } from './validation' import type { CmsParagraph } from '@prisma/client' diff --git a/src/services/cms/paragraphs/destroy.ts b/src/services/cms/paragraphs/destroy.ts index 224d2e29a..a5092b497 100644 --- a/src/services/cms/paragraphs/destroy.ts +++ b/src/services/cms/paragraphs/destroy.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { CmsParagraph } from '@prisma/client' diff --git a/src/services/cms/paragraphs/methods.ts b/src/services/cms/paragraphs/operations.ts similarity index 70% rename from src/services/cms/paragraphs/methods.ts rename to src/services/cms/paragraphs/operations.ts index e6528babf..a2700c0c5 100644 --- a/src/services/cms/paragraphs/methods.ts +++ b/src/services/cms/paragraphs/operations.ts @@ -1,17 +1,16 @@ -import { SubServiceMethod } from '@/services/ServiceMethod' +import { defineSubOperation } from '@/services/serviceOperation' import '@pn-server-only' import { z } from 'zod' export namespace CmsParagraphMethods { - export const create = SubServiceMethod({ - opensTransaction: false, + export const create = defineSubOperation({ paramsSchema: () => z.object({ id: z.number() }), dataSchema: () => z.object({ title: z.string().min(2).max(100), content: z.string().min(2).max(10000), }), - method: () => ({ prisma, data, params }) => { + operation: () => ({ params, data, prisma }) => { // Implementation for creating a CMS paragraph console.log('Creating CMS paragraph with data:', { data, params, prisma }) } diff --git a/src/services/cms/paragraphs/read.ts b/src/services/cms/paragraphs/read.ts index b745c9967..07091ad91 100644 --- a/src/services/cms/paragraphs/read.ts +++ b/src/services/cms/paragraphs/read.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' import type { SpecialCmsParagraph, CmsParagraph } from '@prisma/client' diff --git a/src/services/cms/paragraphs/update.ts b/src/services/cms/paragraphs/update.ts index 6a0a50651..770288467 100644 --- a/src/services/cms/paragraphs/update.ts +++ b/src/services/cms/paragraphs/update.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import { unified } from 'unified' diff --git a/src/services/cms/paragraphs/validation.ts b/src/services/cms/paragraphs/validation.ts index 82406a9f8..e1bcc8c27 100644 --- a/src/services/cms/paragraphs/validation.ts +++ b/src/services/cms/paragraphs/validation.ts @@ -20,3 +20,10 @@ export const createCmsParagraphValidation = baseCmsParagraphValidation.createVal }) export type CreateCmsParagraphTypes = ValidationTypes + +export const createCmsParagraphActionValidation = baseCmsParagraphValidation.createValidation({ + keys: ['name'], + transformer: data => data +}) + +export type CreateCmsParagraphActionTypes = ValidationTypes diff --git a/src/actions/configureAction.ts b/src/services/configureAction.ts similarity index 100% rename from src/actions/configureAction.ts rename to src/services/configureAction.ts diff --git a/src/services/dots/actions.ts b/src/services/dots/actions.ts new file mode 100644 index 000000000..d7a2bfcae --- /dev/null +++ b/src/services/dots/actions.ts @@ -0,0 +1,10 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { dotOperations } from '@/services/dots/operations' + +export const createDotAction = makeAction(dotOperations.create) + +export const readDotPageAction = makeAction(dotOperations.readPage) + +export const readDotWrappersForUserAction = makeAction(dotOperations.readWrappersForUser) diff --git a/src/services/dots/auth.ts b/src/services/dots/auth.ts new file mode 100644 index 000000000..386421be0 --- /dev/null +++ b/src/services/dots/auth.ts @@ -0,0 +1,12 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' +import { RequirePermissionAndUserId } from '@/auth/auther/RequirePermissionAndUserId' +import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' + +export const dotAuth = { + create: RequirePermissionAndUserId.staticFields({ permission: 'DOTS_ADMIN' }), + update: RequirePermission.staticFields({ permission: 'DOTS_ADMIN' }), + destroy: RequirePermission.staticFields({ permission: 'DOTS_ADMIN' }), + readForUser: RequireUserIdOrPermission.staticFields({ permission: 'DOTS_ADMIN' }), + readPage: RequirePermission.staticFields({ permission: 'DOTS_ADMIN' }), + readWrapperForUser: RequireUserIdOrPermission.staticFields({ permission: 'DOTS_ADMIN' }), +} diff --git a/src/services/dots/authers.ts b/src/services/dots/authers.ts deleted file mode 100644 index 46a9de325..000000000 --- a/src/services/dots/authers.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' -import { RequirePermissionAndUserId } from '@/auth/auther/RequirePermissionAndUserId' -import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' - -export namespace DotAuthers { - export const create = RequirePermissionAndUserId.staticFields({ permission: 'DOTS_ADMIN' }) - export const update = RequirePermission.staticFields({ permission: 'DOTS_ADMIN' }) - export const destroy = RequirePermission.staticFields({ permission: 'DOTS_ADMIN' }) - export const readForUser = RequireUserIdOrPermission.staticFields({ permission: 'DOTS_ADMIN' }) - export const readPage = RequirePermission.staticFields({ permission: 'DOTS_ADMIN' }) - export const readWrapperForUser = RequireUserIdOrPermission.staticFields({ permission: 'DOTS_ADMIN' }) -} diff --git a/src/services/dots/config.ts b/src/services/dots/config.ts deleted file mode 100644 index 689afd327..000000000 --- a/src/services/dots/config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Prisma } from '@prisma/client' - -export namespace DotConfig { - const baseDurationDays = 14 - export const baseDuration = 1000 * 60 * 60 * 24 * baseDurationDays - export const wrapperWithDotsIncluder = { - dots: { - orderBy: { - expiresAt: 'desc' - } - }, - user: { - select: { - firstname: true, - lastname: true, - username: true - } - }, - accuser: { - select: { - firstname: true, - lastname: true, - username: true - } - } - } as const satisfies Prisma.DotWrapperInclude -} diff --git a/src/services/dots/constants.ts b/src/services/dots/constants.ts new file mode 100644 index 000000000..44eeb318e --- /dev/null +++ b/src/services/dots/constants.ts @@ -0,0 +1,27 @@ +import type { Prisma } from '@prisma/client' + +const dotBaseDurationDays = 14 + +export const dotBaseDuration = 1000 * 60 * 60 * 24 * dotBaseDurationDays + +export const dotsIncluder = { + dots: { + orderBy: { + expiresAt: 'desc' + } + }, + user: { + select: { + firstname: true, + lastname: true, + username: true + } + }, + accuser: { + select: { + firstname: true, + lastname: true, + username: true + } + } +} as const satisfies Prisma.DotWrapperInclude diff --git a/src/services/dots/methods.ts b/src/services/dots/operations.ts similarity index 72% rename from src/services/dots/methods.ts rename to src/services/dots/operations.ts index d229f607c..32a7a5652 100644 --- a/src/services/dots/methods.ts +++ b/src/services/dots/operations.ts @@ -1,8 +1,8 @@ import '@pn-server-only' -import { DotConfig } from './config' -import { DotAuthers } from './authers' -import { DotSchemas } from './schemas' -import { ServiceMethod } from '@/services/ServiceMethod' +import { dotAuth } from './auth' +import { dotSchemas } from './schemas' +import { dotBaseDuration, dotsIncluder } from './constants' +import { defineOperation } from '@/services/serviceOperation' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' import { readPageInputSchemaObject } from '@/lib/paging/schema' import { z } from 'zod' @@ -12,13 +12,13 @@ import { z } from 'zod' * @param userId - The user id to read dots for * @returns All dots for the user in ascending order of expiration. i.e the dot that expires first will be first in the list */ -const readForUser = ServiceMethod({ - auther: ({ params }) => DotAuthers.readForUser.dynamicFields({ userId: params.userId }), +const readForUser = defineOperation({ + authorizer: ({ params }) => dotAuth.readForUser.dynamicFields({ userId: params.userId }), paramsSchema: z.object({ userId: z.number(), onlyActive: z.boolean(), }), - method: async ({ prisma, params: { userId, onlyActive } }) => prisma.dot.findMany({ + operation: async ({ prisma, params: { userId, onlyActive } }) => prisma.dot.findMany({ where: { wrapper: { userId, @@ -33,24 +33,23 @@ const readForUser = ServiceMethod({ }) }) -const create = ServiceMethod({ - dataSchema: DotSchemas.create, - auther: ({ data }) => DotAuthers.create.dynamicFields({ userId: data.userId }), +const create = defineOperation({ + dataSchema: dotSchemas.create, + authorizer: ({ data }) => dotAuth.create.dynamicFields({ userId: data.userId }), paramsSchema: z.object({ accuserId: z.number(), }), opensTransaction: true, - method: async ({ prisma, params, data: { value, ...data }, session }) => { - const activeDots = await readForUser.client(prisma).execute({ - params: { userId: data.userId, onlyActive: true }, - session, + operation: async ({ prisma, params, data: { value, ...data } }) => { + const activeDots = await readForUser({ + params: { userId: data.userId, onlyActive: true } }) const dotData : { expiresAt: Date }[] = [] let prevExpiresAt = activeDots.length > 0 ? activeDots[activeDots.length - 1].expiresAt : new Date() for (let i = 0; i < value; i++) { //TODO: Take freezes into account - const expiresAt = new Date(prevExpiresAt.getTime() + DotConfig.baseDuration) + const expiresAt = new Date(prevExpiresAt.getTime() + dotBaseDuration) dotData.push({ expiresAt }) prevExpiresAt = expiresAt } @@ -71,17 +70,17 @@ const create = ServiceMethod({ } }) -const readWrappersForUser = ServiceMethod({ - auther: ({ params }) => DotAuthers.readWrapperForUser.dynamicFields({ userId: params.userId }), +const readWrappersForUser = defineOperation({ + authorizer: ({ params }) => dotAuth.readWrapperForUser.dynamicFields({ userId: params.userId }), paramsSchema: z.object({ userId: z.number(), }), - method: async ({ prisma, params: { userId } }) => { + operation: async ({ prisma, params: { userId } }) => { const wrappers = await prisma.dotWrapper.findMany({ where: { userId }, - include: DotConfig.wrapperWithDotsIncluder, + include: dotsIncluder, }) return wrappers.sort((a, b) => { @@ -95,8 +94,8 @@ const readWrappersForUser = ServiceMethod({ } }) -const readPage = ServiceMethod({ - auther: () => DotAuthers.readPage.dynamicFields({}), +const readPage = defineOperation({ + authorizer: () => dotAuth.readPage.dynamicFields({}), paramsSchema: readPageInputSchemaObject( z.number(), z.object({ @@ -107,7 +106,7 @@ const readPage = ServiceMethod({ onlyActive: z.boolean(), }), ), - method: async ({ prisma, params }) => (await prisma.dotWrapper.findMany({ + operation: async ({ prisma, params }) => (await prisma.dotWrapper.findMany({ ...cursorPageingSelection(params.paging.page), where: { userId: params.paging.details.userId ?? undefined, @@ -124,14 +123,14 @@ const readPage = ServiceMethod({ username: 'asc' } }, - include: DotConfig.wrapperWithDotsIncluder, + include: dotsIncluder, })).map(wrapper => ({ ...wrapper, dots: extendWithActive(wrapper.dots) })) }) -export const dotMethods = { +export const dotOperations = { create, readForUser, readWrappersForUser, diff --git a/src/services/dots/schemas.ts b/src/services/dots/schemas.ts index c60a25b0d..81363e1ac 100644 --- a/src/services/dots/schemas.ts +++ b/src/services/dots/schemas.ts @@ -1,23 +1,23 @@ import { z } from 'zod' +const dotSchema = z.object({ + value: z.coerce.number().int().min( + 1, 'Verdi må være et positivt heltall' + ).max( + 100, 'Verdi kan ikke være større enn 100' + ), + reason: z.string().max(200, 'Begrunnelse kan ha maks 200 tegn').trim(), + userId: z.coerce.number().int(), +}) -export namespace DotSchemas { - const dotSchemaFields = z.object({ - value: z.coerce.number().int().min( - 1, 'Verdi må være et positivt heltall' - ).max( - 100, 'Verdi kan ikke være større enn 100' - ), - reason: z.string().max(200, 'Begrunnelse kan ha maks 200 tegn').trim(), - userId: z.coerce.number().int(), - }) - export const create = dotSchemaFields.pick({ +export const dotSchemas = { + create: dotSchema.pick({ value: true, reason: true, userId: true, - }) - export const update = dotSchemaFields.partial().pick({ + }), + update: dotSchema.partial().pick({ value: true, reason: true, - }) + }), } diff --git a/src/services/dots/Types.ts b/src/services/dots/types.ts similarity index 100% rename from src/services/dots/Types.ts rename to src/services/dots/types.ts diff --git a/src/actions/education/courses/create.ts b/src/services/education/courses/actions.ts similarity index 83% rename from src/actions/education/courses/create.ts rename to src/services/education/courses/actions.ts index 59a175d55..dd53c6904 100644 --- a/src/actions/education/courses/create.ts +++ b/src/services/education/courses/actions.ts @@ -1,5 +1,6 @@ 'use server' -import { safeServerCall } from '@/actions/safeServerCall' + +import { safeServerCall } from '@/services/actionError' import type { CreateCourseTypes } from '@/education/courses/validation' export async function createCourseAction(rawdata: FormData | CreateCourseTypes['Type']) { diff --git a/src/services/education/schools/actions.ts b/src/services/education/schools/actions.ts new file mode 100644 index 000000000..0478138ad --- /dev/null +++ b/src/services/education/schools/actions.ts @@ -0,0 +1,95 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createSchoolValidation, updateSchoolValidation } from '@/education/schools/validation' +import { createSchool } from '@/services/education/schools/create' +import { destroySchool } from '@/services/education/schools/destroy' +import { readSchool, readSchools, readSchoolsPage, readStandardSchools } from '@/services/education/schools/read' +import { updateSchool } from '@/services/education/schools/update' +import type { ReadPageInput } from '@/lib/paging/types' +import type { CreateSchoolTypes, UpdateSchoolTypes } from '@/education/schools/validation' +import type { ExpandedSchool, SchoolCursor, SchoolFiltered } from '@/services/education/schools/types' +import type { ActionReturn } from '@/services/actionTypes' + +export async function createSchoolAction( + rawdata: FormData | CreateSchoolTypes['Type'] +): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCHOOLS_ADMIN']] + }) + if (!authorized) return createActionError(status) + + const parse = createSchoolValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createSchool(data)) +} + +export async function destroySchoolAction(id: number): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCHOOLS_ADMIN']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => destroySchool(id)) +} + +export async function readSchoolsPageAction( + pageReadInput: ReadPageInput +): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCHOOLS_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readSchoolsPage(pageReadInput)) +} + +export async function readStandardSchoolsAction(): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCHOOLS_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readStandardSchools()) +} + +export async function readSchoolsAction({ + onlyNonStandard +}: { + onlyNonStandard: boolean +}): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCHOOLS_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readSchools({ onlyNonStandard })) +} + +export async function readSchoolAction(shortname: string): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCHOOLS_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readSchool(shortname)) +} + +export async function updateSchoolAction( + id: number, + rawdata: FormData | UpdateSchoolTypes['Type'] +): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCHOOLS_ADMIN']] + }) + if (!authorized) return createActionError(status) + + const parse = updateSchoolValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => updateSchool(id, data)) +} diff --git a/src/services/education/schools/create.ts b/src/services/education/schools/create.ts index 8465069fc..41d9f73b3 100644 --- a/src/services/education/schools/create.ts +++ b/src/services/education/schools/create.ts @@ -5,11 +5,11 @@ import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import { createCmsImage } from '@/services/cms/images/create' import { createCmsParagraph } from '@/services/cms/paragraphs/create' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { createCmsLink } from '@/services/cms/links/create' import { v4 as uuid } from 'uuid' import { StandardSchool } from '@prisma/client' -import type { SchoolFiltered } from './Types' +import type { SchoolFiltered } from './types' import type { CreateSchoolTypes } from './validation' export async function createSchool(rawdata: CreateSchoolTypes['Detailed']): Promise { diff --git a/src/services/education/schools/destroy.ts b/src/services/education/schools/destroy.ts index 5ca8d68d9..bcf6a6df9 100644 --- a/src/services/education/schools/destroy.ts +++ b/src/services/education/schools/destroy.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' export async function destroySchool(id: number): Promise { const school = await prismaCall(() => prisma.school.findUniqueOrThrow({ diff --git a/src/services/education/schools/read.ts b/src/services/education/schools/read.ts index 99b6f92eb..2324a205e 100644 --- a/src/services/education/schools/read.ts +++ b/src/services/education/schools/read.ts @@ -3,11 +3,11 @@ import { createStandardSchool } from './create' import { SchoolFilteredSelection, SchoolRelationIncluder } from './ConfigVars' import { prismaCall } from '@/services/prismaCall' import logger from '@/lib/logger' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' import { StandardSchool } from '@prisma/client' -import type { ExpandedSchool, SchoolCursor, SchoolFiltered } from './Types' -import type { ReadPageInput } from '@/lib/paging/Types' +import type { ExpandedSchool, SchoolCursor, SchoolFiltered } from './types' +import type { ReadPageInput } from '@/lib/paging/types' export async function readSchoolsPage({ page, diff --git a/src/services/education/schools/Types.ts b/src/services/education/schools/types.ts similarity index 86% rename from src/services/education/schools/Types.ts rename to src/services/education/schools/types.ts index 3fb81ef52..8069a63bb 100644 --- a/src/services/education/schools/Types.ts +++ b/src/services/education/schools/types.ts @@ -1,4 +1,4 @@ -import type { ExpandedCmsImage } from '@/cms/images/Types' +import type { ExpandedCmsImage } from '@/cms/images/types' import type { SchoolFieldsToExpose } from './ConfigVars' import type { CmsLink, CmsParagraph, School } from '@prisma/client' diff --git a/src/services/education/schools/update.ts b/src/services/education/schools/update.ts index faab8af3b..542159bc3 100644 --- a/src/services/education/schools/update.ts +++ b/src/services/education/schools/update.ts @@ -2,8 +2,8 @@ import '@pn-server-only' import { SchoolFilteredSelection } from './ConfigVars' import { updateSchoolValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { SchoolFiltered } from './Types' +import { prisma } from '@/prisma/client' +import type { SchoolFiltered } from './types' import type { UpdateSchoolTypes } from './validation' export async function updateSchool(id: number, rawdata: UpdateSchoolTypes['Detailed']): Promise { diff --git a/src/services/error.ts b/src/services/error.ts index 85158e89d..a4d59fabf 100644 --- a/src/services/error.ts +++ b/src/services/error.ts @@ -1,5 +1,5 @@ import type { SafeParseError } from 'zod' -import type { AuthStatus } from '@/auth/getUser' +import type { AuthStatus } from '@/auth/session/getUser' export const errorCodes = [ { @@ -77,7 +77,11 @@ export const errorCodes = [ httpCode: 403, defaultMessage: 'Du har ikke lov til å gjøre dette', } -] as const +] as const satisfies { + name: string + httpCode: number + defaultMessage: string +}[] export type ErrorCode = typeof errorCodes[number]['name'] diff --git a/src/services/events/actions.ts b/src/services/events/actions.ts new file mode 100644 index 000000000..0ecb22161 --- /dev/null +++ b/src/services/events/actions.ts @@ -0,0 +1,14 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { eventOperations } from '@/services/events/operations' + +export const createEventAction = makeAction(eventOperations.create) + +export const destroyEventAction = makeAction(eventOperations.destroy) + +export const readCurrentEventsAction = makeAction(eventOperations.readManyCurrent) +export const readEventAction = makeAction(eventOperations.read) +export const readArchivedEventsPageAction = makeAction(eventOperations.readManyArchivedPage) + +export const updateEventAction = makeAction(eventOperations.update) diff --git a/src/services/events/auth.ts b/src/services/events/auth.ts new file mode 100644 index 000000000..7f705b74e --- /dev/null +++ b/src/services/events/auth.ts @@ -0,0 +1,12 @@ +import { RequireNothing } from '@/auth/auther/RequireNothing' +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const eventAuth = { + create: RequirePermission.staticFields({ permission: 'EVENT_CREATE' }), + // TODO: Replace below with proper authers + read: RequireNothing.staticFields({}), + readManyCurrent: RequireNothing.staticFields({}), + readManyArchivedPage: RequireNothing.staticFields({}), + update: RequireNothing.staticFields({}), + destroy: RequireNothing.staticFields({}), +} diff --git a/src/services/events/authers.ts b/src/services/events/authers.ts deleted file mode 100644 index a41786e97..000000000 --- a/src/services/events/authers.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { RequireNothing } from '@/auth/auther/RequireNothing' -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace EventAuthers { - export const create = RequirePermission.staticFields({ permission: 'EVENT_CREATE' }) - // TODO: Replace below with proper authers - export const read = RequireNothing.staticFields({}) - export const readManyCurrent = RequireNothing.staticFields({}) - export const readManyArchivedPage = RequireNothing.staticFields({}) - export const update = RequireNothing.staticFields({}) - export const destroy = RequireNothing.staticFields({}) -} diff --git a/src/services/events/config.ts b/src/services/events/config.ts deleted file mode 100644 index ab3726bd9..000000000 --- a/src/services/events/config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createSelection } from '@/services/createSelection' -import { EventCanView } from '@prisma/client' -import type { Event } from '@prisma/client' - -export namespace EventConfig { - export const canBeViewdBy = { - ALL: { label: 'Alle' }, - CAN_REGISTER: { label: 'Alle som kan melde seg på' } - } satisfies Record - - export const canBeViewdByOptions = Object.values(EventCanView).map(opt => ({ - value: opt, - label: canBeViewdBy[opt].label - })) - - export const fieldsToExpose = [ - 'id', - 'name', - 'location', - 'order', - 'eventStart', - 'eventEnd', - 'places', - 'waitingList', - 'registrationStart', - 'registrationEnd', - 'canBeViewdBy', - 'takesRegistration' - ] as const satisfies (keyof Event)[] - - export const filterSeletion = { - ...createSelection(fieldsToExpose), - _count: { - select: { - eventRegistrations: true, - }, - }, - } as const -} - diff --git a/src/services/events/constants.ts b/src/services/events/constants.ts new file mode 100644 index 000000000..a79f24934 --- /dev/null +++ b/src/services/events/constants.ts @@ -0,0 +1,37 @@ +import { createSelection } from '@/services/createSelection' +import { EventCanView } from '@prisma/client' +import type { Event } from '@prisma/client' + +export const eventCanBeViewdBy = { + ALL: { label: 'Alle' }, + CAN_REGISTER: { label: 'Alle som kan melde seg på' } +} satisfies Record + +export const eventCanBeViewdByOptions = Object.values(EventCanView).map(opt => ({ + value: opt, + label: eventCanBeViewdBy[opt].label +})) + +export const eventFieldsToExpose = [ + 'id', + 'name', + 'location', + 'order', + 'eventStart', + 'eventEnd', + 'places', + 'waitingList', + 'registrationStart', + 'registrationEnd', + 'canBeViewdBy', + 'takesRegistration' +] as const satisfies (keyof Event)[] + +export const eventFilterSelection = { + ...createSelection(eventFieldsToExpose), + _count: { + select: { + eventRegistrations: true, + }, + }, +} as const diff --git a/src/services/events/methods.ts b/src/services/events/operations.ts similarity index 85% rename from src/services/events/methods.ts rename to src/services/events/operations.ts index 72794577d..3f10aa19e 100644 --- a/src/services/events/methods.ts +++ b/src/services/events/operations.ts @@ -1,26 +1,26 @@ import '@pn-server-only' -import { EventSchemas } from './schemas' -import { EventConfig } from './config' -import { EventAuthers } from './authers' +import { eventAuth } from './auth' +import { eventSchemas } from './schemas' +import { eventFilterSelection } from './constants' +import { notificationOperations } from '@/services/notifications/operations' import { createCmsParagraph } from '@/services/cms/paragraphs/create' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' import { createCmsImage } from '@/services/cms/images/create' import { getOsloTime } from '@/lib/dates/getOsloTime' import { ServerError } from '@/services/error' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import { readPageInputSchemaObject } from '@/lib/paging/schema' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' -import { NotificationMethods } from '@/services/notifications/methods' import { displayDate } from '@/lib/dates/displayDate' import { v4 as uuid } from 'uuid' import { z } from 'zod' -import type { EventExpanded } from './Types' +import type { EventExpanded } from './types' -export namespace EventMethods { - export const create = ServiceMethod({ - dataSchema: EventSchemas.create, - auther: () => EventAuthers.create.dynamicFields({}), - method: async ({ prisma, data, session }) => { +export const eventOperations = { + create: defineOperation({ + dataSchema: eventSchemas.create, + authorizer: () => eventAuth.create.dynamicFields({}), + operation: async ({ prisma, data, session }) => { const cmsParagraph = await createCmsParagraph({ name: uuid() }) const cmsImage = await createCmsImage({ name: uuid() }) @@ -79,7 +79,7 @@ export namespace EventMethods { })) }) - await NotificationMethods.createSpecial.client(prisma).execute({ + await notificationOperations.createSpecial({ params: { special: 'NEW_EVENT', }, @@ -87,19 +87,18 @@ export namespace EventMethods { title: `Hva der hender: ${event.name}`, message: `${event.name}, 🕓 ${displayDate(event.eventStart, false)},📍 ${event.location}`, }, - session, bypassAuth: true, }) return event } - }) - export const read = ServiceMethod({ + }), + read: defineOperation({ paramsSchema: z.object({ order: z.number(), name: z.string(), }), - auther: () => EventAuthers.read.dynamicFields({}), - method: async ({ prisma, params, session }) => { + authorizer: () => eventAuth.read.dynamicFields({}), + operation: async ({ prisma, params, session }) => { const event = await prisma.event.findUniqueOrThrow({ where: { order_name: { @@ -155,16 +154,16 @@ export namespace EventMethods { tags: event.eventTagEvents.map(ete => ete.tag) } } - }) - export const readManyCurrent = ServiceMethod({ + }), + readManyCurrent: defineOperation({ paramsSchema: z.object({ tags: z.array(z.string()).nullable(), }), - auther: () => EventAuthers.readManyCurrent.dynamicFields({}), - method: async ({ prisma, params }): Promise => { + authorizer: () => eventAuth.readManyCurrent.dynamicFields({}), + operation: async ({ prisma, params }): Promise => { const events = await prisma.event.findMany({ select: { - ...EventConfig.filterSeletion, + ...eventFilterSelection, coverImage: { include: { image: true @@ -190,8 +189,8 @@ export namespace EventMethods { tags: event.eventTagEvents.map(ete => ete.tag) })) } - }) - export const readManyArchivedPage = ServiceMethod({ + }), + readManyArchivedPage: defineOperation({ paramsSchema: readPageInputSchemaObject( z.number(), z.object({ @@ -202,8 +201,8 @@ export namespace EventMethods { tags: z.array(z.string()).nullable(), }), ), // Converted from ReadPageInput - auther: () => EventAuthers.readManyArchivedPage.dynamicFields({}), - method: async ({ prisma, params }): Promise => { + authorizer: () => eventAuth.readManyArchivedPage.dynamicFields({}), + operation: async ({ prisma, params }): Promise => { const events = await prisma.event.findMany({ ...cursorPageingSelection(params.paging.page), where: { @@ -217,7 +216,7 @@ export namespace EventMethods { eventTagEvents: eventTagSelector(params.paging.details.tags) }, select: { - ...EventConfig.filterSeletion, + ...eventFilterSelection, coverImage: { include: { image: true @@ -237,14 +236,14 @@ export namespace EventMethods { tags: event.eventTagEvents.map(ete => ete.tag) })) } - }) - export const update = ServiceMethod({ + }), + update: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - dataSchema: EventSchemas.update, - auther: () => EventAuthers.update.dynamicFields({}), - method: async ({ prisma, params, data: { tagIds, ...data } }) => { + dataSchema: eventSchemas.update, + authorizer: () => eventAuth.update.dynamicFields({}), + operation: async ({ prisma, params, data: { tagIds, ...data } }) => { const event = await prisma.event.findUniqueOrThrow({ where: { id: params.id } }) @@ -288,21 +287,21 @@ export namespace EventMethods { // TODO: Send email to users that get promoted from waiting list return eventUpdate } - }) + }), - export const destroy = ServiceMethod({ + destroy: defineOperation({ paramsSchema: z.object({ id: z.number() }), - auther: () => EventAuthers.destroy.dynamicFields({}), - method: async ({ prisma, params }) => { + authorizer: () => eventAuth.destroy.dynamicFields({}), + operation: async ({ prisma, params }) => { await prisma.event.delete({ where: { id: params.id } }) } - }) + }), } function eventTagSelector(tags: string[] | null) { diff --git a/src/services/events/registration/actions.ts b/src/services/events/registration/actions.ts new file mode 100644 index 000000000..108850041 --- /dev/null +++ b/src/services/events/registration/actions.ts @@ -0,0 +1,11 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { eventRegistrationOperations } from '@/services/events/registration/operations' + +export const createEventRegistrationAction = makeAction(eventRegistrationOperations.create) +export const createGuestEventRegistrationAction = makeAction(eventRegistrationOperations.createGuest) +export const readManyEventRegistrationAction = makeAction(eventRegistrationOperations.readMany) +export const eventRegistrationReadManyDetailedAction = makeAction(eventRegistrationOperations.readManyDetailed) +export const eventRegistrationUpdateNotesAction = makeAction(eventRegistrationOperations.updateNotes) +export const eventRegistrationDestroyAction = makeAction(eventRegistrationOperations.destroy) diff --git a/src/services/events/registration/auth.ts b/src/services/events/registration/auth.ts new file mode 100644 index 000000000..9001b5872 --- /dev/null +++ b/src/services/events/registration/auth.ts @@ -0,0 +1,15 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' +import { RequirePermissionAndUser } from '@/auth/auther/RequirePermissionAndUser' +import { RequireUser } from '@/auth/auther/RequireUser' +import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' + +export const eventRegistrationAuth = { + // TODO: Fix authing + create: RequireUserIdOrPermission.staticFields({ permission: 'EVENT_REGISTRATION_CREATE' }), + createGuest: RequirePermission.staticFields({ permission: 'EVENT_ADMIN' }), + readMany: RequirePermissionAndUser.staticFields({ permission: 'EVENT_REGISTRATION_READ' }), + readManyDetailed: RequirePermissionAndUser.staticFields({ permission: 'EVENT_REGISTRATION_READ' }), + destroy: RequirePermissionAndUser.staticFields({ permission: 'EVENT_REGISTRATION_DESROY' }), + + updateRegistrationNotes: RequireUser.staticFields({}), // TODO: bypass permission +} diff --git a/src/services/events/registration/authers.ts b/src/services/events/registration/authers.ts deleted file mode 100644 index e45a57248..000000000 --- a/src/services/events/registration/authers.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' -import { RequirePermissionAndUser } from '@/auth/auther/RequirePermissionAndUser' -import { RequireUser } from '@/auth/auther/RequireUser' -import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' - -export namespace EventRegistrationAuthers { - // TODO: Fix authing - export const create = RequireUserIdOrPermission.staticFields({ permission: 'EVENT_REGISTRATION_CREATE' }) - export const createGuest = RequirePermission.staticFields({ permission: 'EVENT_ADMIN' }) - export const readMany = RequirePermissionAndUser.staticFields({ permission: 'EVENT_REGISTRATION_READ' }) - export const readManyDetailed = RequirePermissionAndUser.staticFields({ permission: 'EVENT_REGISTRATION_READ' }) - export const destroy = RequirePermissionAndUser.staticFields({ permission: 'EVENT_REGISTRATION_DESROY' }) - - export const updateRegistrationNotes = RequireUser.staticFields({}) // TODO: bypass permission -} diff --git a/src/services/events/registration/config.ts b/src/services/events/registration/config.ts deleted file mode 100644 index 65b6116c1..000000000 --- a/src/services/events/registration/config.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { UserConfig } from '@/services/users/config' -import type { Prisma } from '@prisma/client' - -export namespace EventRegistrationConfig { - - export const selection = { - user: { - select: { - ...UserConfig.filterSelection, - image: true, - }, - }, - contact: { - select: { - name: true, - }, - } - } satisfies Prisma.EventRegistrationSelect - - export const includerDetailed = { - ...selection, - contact: true, - } satisfies Prisma.EventRegistrationInclude - - export enum REGISTRATION_READER_TYPE { - REGISTRATIONS = 'REGISTRATIONS', - WAITING_LIST = 'WAITING_LIST', - } -} diff --git a/src/services/events/registration/constants.ts b/src/services/events/registration/constants.ts new file mode 100644 index 000000000..e86d27fb5 --- /dev/null +++ b/src/services/events/registration/constants.ts @@ -0,0 +1,26 @@ +import { userFilterSelection } from '@/services/users/constants' +import type { Prisma } from '@prisma/client' + +export const eventRegistrationSelection = { + user: { + select: { + ...userFilterSelection, + image: true, + }, + }, + contact: { + select: { + name: true, + }, + } +} satisfies Prisma.EventRegistrationSelect + +export const eventRegistrationIncluderDetailed = { + ...eventRegistrationSelection, + contact: true, +} satisfies Prisma.EventRegistrationInclude + +export enum REGISTRATION_READER_TYPE { + REGISTRATIONS = 'REGISTRATIONS', + WAITING_LIST = 'WAITING_LIST', +} diff --git a/src/services/events/registration/methods.ts b/src/services/events/registration/operations.ts similarity index 81% rename from src/services/events/registration/methods.ts rename to src/services/events/registration/operations.ts index ad3db8a76..9e80ddca4 100644 --- a/src/services/events/registration/methods.ts +++ b/src/services/events/registration/operations.ts @@ -1,16 +1,16 @@ import '@pn-server-only' -import { EventRegistrationAuthers } from './authers' -import { EventRegistrationConfig } from './config' -import { EventRegistrationSchemas } from './schemas' -import { ServiceMethod } from '@/services/ServiceMethod' +import { eventRegistrationIncluderDetailed, eventRegistrationSelection, REGISTRATION_READER_TYPE } from './constants' +import { eventRegistrationAuth } from './auth' +import { eventRegistrationSchemas } from './schemas' import { Smorekopp } from '@/services/error' -import { ImageMethods } from '@/services/images/methods' -import { NotificationMethods } from '@/services/notifications/methods' -import { UserConfig } from '@/services/users/config' +import { imageOperations } from '@/services/images/operations' +import { notificationOperations } from '@/services/notifications/operations' import { sendSystemMail } from '@/services/notifications/email/send' +import { userFilterSelection } from '@/services/users/constants' +import { defineOperation } from '@/services/serviceOperation' import { z } from 'zod' import type { Prisma } from '@prisma/client' -import type { EventRegistrationExpanded } from './Types' +import type { EventRegistrationExpanded } from './types' async function preValidateRegistration( prisma: Prisma.TransactionClient, @@ -93,7 +93,7 @@ async function calculateTakeSkip(prisma: Prisma.TransactionClient, params: { eventId: number, take?: number, skip?: number, - type?: EventRegistrationConfig.REGISTRATION_READER_TYPE, + type?: REGISTRATION_READER_TYPE, }) { let take = params.take let skip = params.skip @@ -105,7 +105,7 @@ async function calculateTakeSkip(prisma: Prisma.TransactionClient, params: { }, }) - if (params.type === EventRegistrationConfig.REGISTRATION_READER_TYPE.REGISTRATIONS) { + if (params.type === REGISTRATION_READER_TYPE.REGISTRATIONS) { skip = Math.min(skip ?? 0, event.places) take = Math.min(take, event.places - skip) } else { @@ -123,17 +123,18 @@ async function calculateTakeSkip(prisma: Prisma.TransactionClient, params: { } } -export namespace EventRegistrationMethods { - export const create = ServiceMethod({ +export const eventRegistrationOperations = { + + create: defineOperation({ paramsSchema: z.object({ userId: z.number().min(0), eventId: z.number().min(0), }), - auther: ({ params }) => EventRegistrationAuthers.create.dynamicFields({ + authorizer: ({ params }) => eventRegistrationAuth.create.dynamicFields({ userId: params.userId, }), opensTransaction: true, - method: async ({ prisma, params, session }) => { + operation: async ({ prisma, params, session }) => { const isAdmin = session.permissions.includes('EVENT_ADMIN') await preValidateRegistration(prisma, params.eventId, isAdmin) @@ -159,16 +160,16 @@ export namespace EventRegistrationMethods { onWaitingList: updatedEvent.places < updatedEvent._count.eventRegistrations, } }, - }) + }), - export const createGuest = ServiceMethod({ - auther: () => EventRegistrationAuthers.createGuest.dynamicFields({}), + createGuest: defineOperation({ + authorizer: () => eventRegistrationAuth.createGuest.dynamicFields({}), paramsSchema: z.object({ eventId: z.number(), }), - dataSchema: EventRegistrationSchemas.createGuest, + dataSchema: eventRegistrationSchemas.createGuest, opensTransaction: true, - method: async ({ prisma, params, data }) => { + operation: async ({ prisma, params, data }) => { await preValidateRegistration(prisma, params.eventId, true) const result = await prisma.eventRegistration.create({ data: { @@ -193,20 +194,19 @@ export namespace EventRegistrationMethods { onWaitingList: updatedEvent.places < updatedEvent._count.eventRegistrations, } }, - }) + }), - export const readMany = ServiceMethod({ - auther: () => EventRegistrationAuthers.readMany.dynamicFields({}), + readMany: defineOperation({ + authorizer: () => eventRegistrationAuth.readMany.dynamicFields({}), paramsSchema: z.object({ eventId: z.number().min(0), skip: z.number().optional(), take: z.number().optional(), - type: z.nativeEnum(EventRegistrationConfig.REGISTRATION_READER_TYPE).optional(), + type: z.nativeEnum(REGISTRATION_READER_TYPE).optional(), }), - method: async ({ prisma, params, session }): Promise => { - const defaultImage = await ImageMethods.readSpecial.client(prisma).execute({ + operation: async ({ prisma, params }): Promise => { + const defaultImage = await imageOperations.readSpecial({ params: { special: 'DEFAULT_PROFILE_IMAGE' }, - session, }) const skipTake = await calculateTakeSkip(prisma, params) @@ -220,7 +220,7 @@ export namespace EventRegistrationMethods { createdAt: 'asc', }, ...skipTake, - select: EventRegistrationConfig.selection, + select: eventRegistrationSelection, }) return reults.map(registration => ({ @@ -228,17 +228,17 @@ export namespace EventRegistrationMethods { image: registration.user?.image || defaultImage, })) }, - }) + }), - export const readManyDetailed = ServiceMethod({ - auther: () => EventRegistrationAuthers.readManyDetailed.dynamicFields({}), + readManyDetailed: defineOperation({ + authorizer: () => eventRegistrationAuth.readManyDetailed.dynamicFields({}), paramsSchema: z.object({ eventId: z.number().min(0), skip: z.number().optional(), take: z.number().optional(), - type: z.nativeEnum(EventRegistrationConfig.REGISTRATION_READER_TYPE).optional(), + type: z.nativeEnum(REGISTRATION_READER_TYPE).optional(), }), - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { const skiptake = await calculateTakeSkip(prisma, params) if (skiptake.take === 0) return [] @@ -250,18 +250,18 @@ export namespace EventRegistrationMethods { createdAt: 'asc', }, ...skiptake, - include: EventRegistrationConfig.includerDetailed, + include: eventRegistrationIncluderDetailed, }) } - }) + }), - export const updateNotes = ServiceMethod({ - auther: () => EventRegistrationAuthers.updateRegistrationNotes.dynamicFields({}), + updateNotes: defineOperation({ + authorizer: () => eventRegistrationAuth.updateRegistrationNotes.dynamicFields({}), paramsSchema: z.object({ registrationId: z.number().min(0), }), - dataSchema: EventRegistrationSchemas.updateNotes, - method: async ({ prisma, params, data, session }) => { + dataSchema: eventRegistrationSchemas.updateNotes, + operation: async ({ prisma, params, data, session }) => { const registration = await prisma.eventRegistration.findUnique({ where: { id: params.registrationId, @@ -289,14 +289,14 @@ export namespace EventRegistrationMethods { }, }) } - }) + }), - export const destroy = ServiceMethod({ - auther: () => EventRegistrationAuthers.destroy.dynamicFields({}), + destroy: defineOperation({ + authorizer: () => eventRegistrationAuth.destroy.dynamicFields({}), paramsSchema: z.object({ registrationId: z.number().min(0), }), - method: async ({ prisma, params, session }) => { + operation: async ({ prisma, params, session }) => { const isAdmin = session.permissions.includes('EVENT_ADMIN') const registration = await prisma.eventRegistration.findUniqueOrThrow({ where: { @@ -360,7 +360,7 @@ export namespace EventRegistrationMethods { }, include: { user: { - select: UserConfig.filterSelection, + select: userFilterSelection, }, contact: true, } @@ -372,7 +372,7 @@ export namespace EventRegistrationMethods { const message = `Gratulerer! Du har rykket opp fra venteliste på arrangementet ${registration.event.name}.` if (nextInLine.user) { - await NotificationMethods.createSpecial.newClient().execute({ + await notificationOperations.createSpecial({ params: { special: 'EVENT_WAITINGLIST_PROMOTION', }, @@ -382,7 +382,6 @@ export namespace EventRegistrationMethods { userIdList: [nextInLine.user.id], }, bypassAuth: true, - session, }) } diff --git a/src/services/events/registration/schemas.ts b/src/services/events/registration/schemas.ts index f2971ea92..755ae4332 100644 --- a/src/services/events/registration/schemas.ts +++ b/src/services/events/registration/schemas.ts @@ -1,17 +1,17 @@ import '@pn-server-only' import { z } from 'zod' -export namespace EventRegistrationSchemas { - const fields = z.object({ - note: z.string().max(200, 'Merknader kan ha maks 200 tegn'), - name: z.string().min(2), - }) +const baseSchema = z.object({ + note: z.string().max(200, 'Merknader kan ha maks 200 tegn'), + name: z.string().min(2), +}) - export const updateNotes = fields.pick({ +export const eventRegistrationSchemas = { + updateNotes: baseSchema.pick({ note: true, - }) + }), - export const createGuest = fields.pick({ + createGuest: baseSchema.pick({ name: true, note: true, }) diff --git a/src/services/events/registration/Types.ts b/src/services/events/registration/types.ts similarity index 59% rename from src/services/events/registration/Types.ts rename to src/services/events/registration/types.ts index a346bb2d9..c63064da2 100644 --- a/src/services/events/registration/Types.ts +++ b/src/services/events/registration/types.ts @@ -1,18 +1,18 @@ -import type { EventRegistrationConfig } from './config' +import type { eventRegistrationIncluderDetailed, eventRegistrationSelection, REGISTRATION_READER_TYPE } from './constants' import type { Image, Prisma } from '@prisma/client' // This type will just make sure that the image is not null export type EventRegistrationExpanded = Prisma.EventRegistrationGetPayload<{ - select: typeof EventRegistrationConfig.selection + select: typeof eventRegistrationSelection }> & { image: Image } export type EventRegistrationDetailedExpanded = Prisma.EventRegistrationGetPayload<{ - include: typeof EventRegistrationConfig.includerDetailed + include: typeof eventRegistrationIncluderDetailed, }> export type EventRegistrationFetcherDetails = { eventId: number, - type?: EventRegistrationConfig.REGISTRATION_READER_TYPE, + type?: REGISTRATION_READER_TYPE, } diff --git a/src/services/events/schemas.ts b/src/services/events/schemas.ts index 06d7bf5d6..7854d55d7 100644 --- a/src/services/events/schemas.ts +++ b/src/services/events/schemas.ts @@ -1,33 +1,34 @@ -import { zpn } from '@/lib/fields/zpn' +import { Zpn } from '@/lib/fields/zpn' import { z } from 'zod' import { EventCanView } from '@prisma/client' -export namespace EventSchemas { - const fields = z.object({ - name: z.string().min(5, 'Navnet må være minst 5 tegn').max(70, 'Navnet må være maks 70 tegn'), - location: z.string().min(2, 'Stedet må være minst 2 tegn'), - order: z.coerce.number().int().optional(), - eventStart: zpn.date({ label: 'Starttid' }), - eventEnd: zpn.date({ label: 'Sluttid' }), - canBeViewdBy: z.nativeEnum(EventCanView), +const baseSchema = z.object({ + name: z.string().min(5, 'Navnet må være minst 5 tegn').max(70, 'Navnet må være maks 70 tegn'), + location: z.string().min(2, 'Stedet må være minst 2 tegn'), + order: z.coerce.number().int().optional(), + eventStart: Zpn.date({ label: 'Starttid' }), + eventEnd: Zpn.date({ label: 'Sluttid' }), + canBeViewdBy: z.nativeEnum(EventCanView), - takesRegistration: zpn.checkboxOrBoolean({ label: 'Tar påmelding' }), - places: z.coerce.number().int().optional(), - registrationStart: zpn.date({ label: 'Påmelding start' }).optional(), - registrationEnd: zpn.date({ label: 'Påmelding slutt' }).optional(), + takesRegistration: Zpn.checkboxOrBoolean({ label: 'Tar påmelding' }), + places: z.coerce.number().int().optional(), + registrationStart: Zpn.date({ label: 'Påmelding start' }).optional(), + registrationEnd: Zpn.date({ label: 'Påmelding slutt' }).optional(), - waitingList: zpn.checkboxOrBoolean({ label: 'Venteliste' }), + waitingList: Zpn.checkboxOrBoolean({ label: 'Venteliste' }), - tagIds: zpn.numberListCheckboxFriendly({ label: 'tags' }) - }) + tagIds: Zpn.numberListCheckboxFriendly({ label: 'tags' }) +}) - const waitingListRefiner = (data: { - waitingList?: boolean, - takesRegistration?: boolean - }) => (data.takesRegistration || !data.waitingList) - const waitingListMessage = 'Kan ikke ha venteliste uten påmelding' +const waitingListRefiner = (data: { + waitingList?: boolean, + takesRegistration?: boolean +}) => (data.takesRegistration || !data.waitingList) - export const create = fields.pick({ +const waitingListMessage = 'Kan ikke ha venteliste uten påmelding' + +export const eventSchemas = { + create: baseSchema.pick({ name: true, location: true, order: true, @@ -40,9 +41,9 @@ export namespace EventSchemas { registrationEnd: true, tagIds: true, waitingList: true, - }).refine(waitingListRefiner, waitingListMessage) + }).refine(waitingListRefiner, waitingListMessage), - export const update = fields.partial().pick({ + update: baseSchema.partial().pick({ name: true, location: true, order: true, @@ -55,5 +56,5 @@ export namespace EventSchemas { registrationEnd: true, tagIds: true, waitingList: true, - }).refine(waitingListRefiner, waitingListMessage) + }).refine(waitingListRefiner, waitingListMessage), } diff --git a/src/services/events/tags/actions.ts b/src/services/events/tags/actions.ts new file mode 100644 index 000000000..76e3bc0f4 --- /dev/null +++ b/src/services/events/tags/actions.ts @@ -0,0 +1,14 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { eventTagOperations } from '@/services/events/tags/operations' + +export const createEventTagAction = makeAction(eventTagOperations.create) + +export const destroyEventTagAction = makeAction(eventTagOperations.destroy) + +export const readEventTagsAction = makeAction(eventTagOperations.readAll) +export const readSpecialEventTagAction = makeAction(eventTagOperations.readSpecial) +export const readEventTagAction = makeAction(eventTagOperations.read) + +export const updateEventTagAction = makeAction(eventTagOperations.update) diff --git a/src/services/events/tags/auth.ts b/src/services/events/tags/auth.ts new file mode 100644 index 000000000..ef6bbeaed --- /dev/null +++ b/src/services/events/tags/auth.ts @@ -0,0 +1,11 @@ +import { RequireNothing } from '@/auth/auther/RequireNothing' +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const eventTagAuth = { + create: RequirePermission.staticFields({ permission: 'EVENT_ADMIN' }), + readSpecial: RequireNothing.staticFields({}), + read: RequireNothing.staticFields({}), + readAll: RequireNothing.staticFields({}), + update: RequirePermission.staticFields({ permission: 'EVENT_ADMIN' }), + destroy: RequirePermission.staticFields({ permission: 'EVENT_ADMIN' }), +} diff --git a/src/services/events/tags/authers.ts b/src/services/events/tags/authers.ts deleted file mode 100644 index 4f9a9ebcf..000000000 --- a/src/services/events/tags/authers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RequireNothing } from '@/auth/auther/RequireNothing' -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace EventTagAuthers { - export const create = RequirePermission.staticFields({ permission: 'EVENT_ADMIN' }) - export const readSpecial = RequireNothing.staticFields({}) - export const read = RequireNothing.staticFields({}) - export const readAll = RequireNothing.staticFields({}) - export const update = RequirePermission.staticFields({ permission: 'EVENT_ADMIN' }) - export const destroy = RequirePermission.staticFields({ permission: 'EVENT_ADMIN' }) -} diff --git a/src/services/events/tags/config.ts b/src/services/events/tags/config.ts deleted file mode 100644 index 6ddd3d0ab..000000000 --- a/src/services/events/tags/config.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { SpecialEventTags } from '@prisma/client' - -export namespace EvantTagConfig { - export const specials = { - COMPANY_PRESENTATION: { - name: 'Bedpress', - description: 'Bedpress', - colorR: 255, - colorG: 0, - colorB: 0 - } - } satisfies Record -} diff --git a/src/services/events/tags/constants.ts b/src/services/events/tags/constants.ts new file mode 100644 index 000000000..875fe8756 --- /dev/null +++ b/src/services/events/tags/constants.ts @@ -0,0 +1,17 @@ +import type { SpecialEventTags } from '@prisma/client' + +export const specialEventTags = { + COMPANY_PRESENTATION: { + name: 'Bedpress', + description: 'Bedpress', + colorR: 255, + colorG: 0, + colorB: 0 + } +} satisfies Record diff --git a/src/services/events/tags/methods.ts b/src/services/events/tags/operations.ts similarity index 60% rename from src/services/events/tags/methods.ts rename to src/services/events/tags/operations.ts index 64aa846c3..582fdf6f8 100644 --- a/src/services/events/tags/methods.ts +++ b/src/services/events/tags/operations.ts @@ -1,32 +1,32 @@ import '@pn-server-only' -import { EventTagAuthers } from './authers' -import { EvantTagConfig } from './config' -import { EventTagSchemas } from './schemas' -import { EventAuthers } from '@/services/events/authers' +import { eventTagAuth } from './auth' +import { specialEventTags } from './constants' +import { eventTagSchemas } from './schemas' +import { eventAuth } from '@/services/events/auth' import logger from '@/lib/logger' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import { ServerError } from '@/services/error' import { SpecialEventTags } from '@prisma/client' import { z } from 'zod' -export namespace EventTagMethods { - export const read = ServiceMethod({ +export const eventTagOperations = { + read: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - auther: () => EventTagAuthers.read.dynamicFields({}), - method: async ({ prisma, params: { id } }) => await prisma.eventTag.findUniqueOrThrow({ + authorizer: () => eventTagAuth.read.dynamicFields({}), + operation: async ({ prisma, params: { id } }) => await prisma.eventTag.findUniqueOrThrow({ where: { id } }) - }) - export const readSpecial = ServiceMethod({ + }), + readSpecial: defineOperation({ paramsSchema: z.object({ special: z.nativeEnum(SpecialEventTags), }), - auther: () => EventTagAuthers.readSpecial.dynamicFields({}), - method: async ({ prisma, params: { special } }) => { + authorizer: () => eventTagAuth.readSpecial.dynamicFields({}), + operation: async ({ prisma, params: { special } }) => { const tag = await prisma.eventTag.findUnique({ where: { special @@ -37,21 +37,21 @@ export namespace EventTagMethods { return await prisma.eventTag.create({ data: { special, - ...EvantTagConfig.specials[special] + ...specialEventTags[special] } }) } return tag } - }) - export const readAll = ServiceMethod({ - auther: () => EventTagAuthers.readAll.dynamicFields({}), - method: async ({ prisma }) => await prisma.eventTag.findMany() - }) - export const create = ServiceMethod({ - dataSchema: EventTagSchemas.create, - auther: () => EventTagAuthers.create.dynamicFields({}), - method: async ({ prisma, data: { color, ...data } }) => { + }), + readAll: defineOperation({ + authorizer: () => eventTagAuth.readAll.dynamicFields({}), + operation: async ({ prisma }) => await prisma.eventTag.findMany() + }), + create: defineOperation({ + dataSchema: eventTagSchemas.create, + authorizer: () => eventTagAuth.create.dynamicFields({}), + operation: async ({ prisma, data: { color, ...data } }) => { const colorR = parseInt(color.slice(1, 3), 16) const colorG = parseInt(color.slice(3, 5), 16) const colorB = parseInt(color.slice(5, 7), 16) @@ -64,14 +64,14 @@ export namespace EventTagMethods { } }) } - }) - export const update = ServiceMethod({ + }), + update: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - dataSchema: EventTagSchemas.update, - auther: () => EventAuthers.update.dynamicFields({}), - method: async ({ prisma, params: { id }, data: { color, ...data } }) => { + dataSchema: eventTagSchemas.update, + authorizer: () => eventAuth.update.dynamicFields({}), + operation: async ({ prisma, params: { id }, data: { color, ...data } }) => { const colorR = color ? parseInt(color.slice(1, 3), 16) : undefined const colorG = color ? parseInt(color.slice(3, 5), 16) : undefined const colorB = color ? parseInt(color.slice(5, 7), 16) : undefined @@ -87,13 +87,13 @@ export namespace EventTagMethods { } }) } - }) - export const destroy = ServiceMethod({ + }), + destroy: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - auther: () => EventAuthers.destroy.dynamicFields({}), - method: async ({ prisma, params }) => { + authorizer: () => eventAuth.destroy.dynamicFields({}), + operation: async ({ prisma, params }) => { const tag = await prisma.eventTag.findUniqueOrThrow({ where: { id: params.id } }) @@ -104,5 +104,5 @@ export namespace EventTagMethods { where: { id: params.id } }) } - }) + }), } diff --git a/src/services/events/tags/schemas.ts b/src/services/events/tags/schemas.ts index 78ed36871..104021df2 100644 --- a/src/services/events/tags/schemas.ts +++ b/src/services/events/tags/schemas.ts @@ -1,21 +1,22 @@ import { z } from 'zod' -export namespace EventTagSchemas { - const fields = z.object({ - name: z.string().min(3, 'Navn må ha minst 3 tegn').max(30, 'Navn kan ha maks 30 tegn').trim(), - description: z.string().max(200, 'Beskrivelse kan ha maks 200 tegn').trim(), - color: z.string().regex( - /^#[0-9A-Fa-f]{6}$/, 'Farge må være en gyldig hex-farge' - ).transform(value => value.toUpperCase()), - }) - export const create = fields.pick({ +const baseSchemas = z.object({ + name: z.string().min(3, 'Navn må ha minst 3 tegn').max(30, 'Navn kan ha maks 30 tegn').trim(), + description: z.string().max(200, 'Beskrivelse kan ha maks 200 tegn').trim(), + color: z.string().regex( + /^#[0-9A-Fa-f]{6}$/, 'Farge må være en gyldig hex-farge' + ).transform(value => value.toUpperCase()), +}) + +export const eventTagSchemas = { + create: baseSchemas.pick({ name: true, description: true, color: true, - }) - export const update = fields.partial().pick({ + }), + update: baseSchemas.partial().pick({ name: true, description: true, color: true, - }) + }), } diff --git a/src/services/events/Types.ts b/src/services/events/types.ts similarity index 74% rename from src/services/events/Types.ts rename to src/services/events/types.ts index b2e6ae74d..3a70c1f07 100644 --- a/src/services/events/Types.ts +++ b/src/services/events/types.ts @@ -1,10 +1,10 @@ -import type { EventConfig } from './config' -import type { ExpandedCmsImage } from '@/cms/images/Types' +import type { eventFilterSelection } from './constants' +import type { ExpandedCmsImage } from '@/cms/images/types' import type { EventTag, Prisma } from '@prisma/client' export type EventFiltered = Prisma.EventGetPayload<{ - select: typeof EventConfig.filterSeletion + select: typeof eventFilterSelection }> & { numOfRegistrations: number, numOnWaitingList: number, diff --git a/src/services/groups/actions.ts b/src/services/groups/actions.ts new file mode 100644 index 000000000..04b2847fe --- /dev/null +++ b/src/services/groups/actions.ts @@ -0,0 +1,9 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { groupOperations } from '@/services/groups/operations' + +export const readGroupsAction = makeAction(groupOperations.readGroups) +export const readGroupExpandedAction = makeAction(groupOperations.readGroupExpanded) +export const readGroupsExpandedAction = makeAction(groupOperations.readGroupsExpanded) +export const readGroupsStructuredAction = makeAction(groupOperations.readGroupsStructured) diff --git a/src/services/groups/auth.ts b/src/services/groups/auth.ts new file mode 100644 index 000000000..ec5781366 --- /dev/null +++ b/src/services/groups/auth.ts @@ -0,0 +1,5 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const groupAuth = { + read: RequirePermission.staticFields({ permission: 'GROUP_READ' }), +} diff --git a/src/services/groups/authers.ts b/src/services/groups/authers.ts deleted file mode 100644 index 578825ca4..000000000 --- a/src/services/groups/authers.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace GroupAuthers { - export const read = RequirePermission.staticFields({ permission: 'GROUP_READ' }) -} diff --git a/src/services/groups/classes/read.ts b/src/services/groups/classes/read.ts index ba701c798..71277dcc2 100644 --- a/src/services/groups/classes/read.ts +++ b/src/services/groups/classes/read.ts @@ -1,6 +1,6 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ExpandedClass } from './Types' +import { prisma } from '@/prisma/client' +import type { ExpandedClass } from './types' export async function readClasses(): Promise { return await prismaCall(() => prisma.class.findMany({})) diff --git a/src/services/groups/classes/Types.ts b/src/services/groups/classes/types.ts similarity index 100% rename from src/services/groups/classes/Types.ts rename to src/services/groups/classes/types.ts diff --git a/src/services/groups/committees/actions.ts b/src/services/groups/committees/actions.ts new file mode 100644 index 000000000..96b2470d2 --- /dev/null +++ b/src/services/groups/committees/actions.ts @@ -0,0 +1,50 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createCommittee } from '@/services/groups/committees/create' +import { committeeOperations } from '@/services/groups/committees/operations' +import { updateCommittee } from '@/services/groups/committees/update' +import { createCommitteeValidation, updateCommitteeValidation } from '@/services/groups/committees/validation' +import type { ExpandedCommittee } from '@/services/groups/committees/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateCommitteeTypes, UpdateCommitteeTypes } from '@/services/groups/committees/validation' + +export async function createCommitteeAction( + rawData: FormData | CreateCommitteeTypes['Type'] +): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['COMMITTEE_CREATE']], + shouldRedirect: false, + }) + + if (!authorized) return createActionError(status) + + const parse = createCommitteeValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + + return await safeServerCall(() => createCommittee(parse.data)) +} + +export const readCommitteesAction = makeAction(committeeOperations.readCommittees) +export const readCommitteeAction = makeAction(committeeOperations.readCommittee) +export const readCommitteeArticleAction = makeAction(committeeOperations.readCommitteArticle) +export const readCommitteeParagraphAction = makeAction(committeeOperations.readCommitteeParagraph) +export const readCommitteeMembersAction = makeAction(committeeOperations.readCommitteeMembers) + +export async function updateCommitteeAction( + id: number, + rawdata: FormData | UpdateCommitteeTypes['Type'] +) { + const { status, authorized } = await getUser({ + requiredPermissions: [['COMMITTEE_UPDATE']] + }) + if (!authorized) return createActionError(status) + + const parse = updateCommitteeValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => updateCommittee(id, data)) +} diff --git a/src/services/groups/committees/auth.ts b/src/services/groups/committees/auth.ts new file mode 100644 index 000000000..79566f8bf --- /dev/null +++ b/src/services/groups/committees/auth.ts @@ -0,0 +1,5 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const committeeAuth = { + read: RequirePermission.staticFields({ permission: 'COMMITTEE_READ' }), +} diff --git a/src/services/groups/committees/authers.ts b/src/services/groups/committees/authers.ts deleted file mode 100644 index 20b6fad4e..000000000 --- a/src/services/groups/committees/authers.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace CommitteeAuthers { - export const read = RequirePermission.staticFields({ permission: 'COMMITTEE_READ' }) -} diff --git a/src/services/groups/committees/config.ts b/src/services/groups/committees/config.ts deleted file mode 100644 index 2f7161646..000000000 --- a/src/services/groups/committees/config.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { UserConfig } from '@/services/users/config' -import type { Prisma } from '@prisma/client' - - -export namespace CommitteeConfig { - - export const committeeLogoIncluder = { - logoImage: { - include: { - image: true - } - } - } satisfies Prisma.CommitteeInclude - - export const membershipIncluder = { - user: { - select: { - ...UserConfig.filterSelection, - image: true - } - } - } satisfies Prisma.MembershipInclude - - export const committeeExpandedIncluder = { - ...committeeLogoIncluder, - committeeArticle: { - include: { - coverImage: { - include: { - image: true, - } - } - } - }, - group: { - include: { - memberships: { - include: membershipIncluder, - where: { - active: true, - } - } - } - } - } satisfies Prisma.CommitteeInclude -} diff --git a/src/services/groups/committees/constants.ts b/src/services/groups/committees/constants.ts new file mode 100644 index 000000000..fd224b864 --- /dev/null +++ b/src/services/groups/committees/constants.ts @@ -0,0 +1,42 @@ +import { userFilterSelection } from '@/services/users/constants' +import type { Prisma } from '@prisma/client' + +export const committeeLogoIncluder = { + logoImage: { + include: { + image: true + } + } +} satisfies Prisma.CommitteeInclude + +export const membershipIncluder = { + user: { + select: { + ...userFilterSelection, + image: true + } + } +} satisfies Prisma.MembershipInclude + +export const committeeExpandedIncluder = { + ...committeeLogoIncluder, + committeeArticle: { + include: { + coverImage: { + include: { + image: true, + } + } + } + }, + group: { + include: { + memberships: { + include: membershipIncluder, + where: { + active: true, + } + } + } + } +} satisfies Prisma.CommitteeInclude diff --git a/src/services/groups/committees/create.ts b/src/services/groups/committees/create.ts index 4d2ce8b02..31273e634 100644 --- a/src/services/groups/committees/create.ts +++ b/src/services/groups/committees/create.ts @@ -1,20 +1,20 @@ import { createCommitteeValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import { createArticle } from '@/services/cms/articles/create' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' import { createCmsParagraph } from '@/services/cms/paragraphs/create' -import { ImageMethods } from '@/services/images/methods' +import { imageOperations } from '@/services/images/operations' import { GroupType } from '@prisma/client' -import type { ExpandedCommittee } from './Types' +import type { ExpandedCommittee } from './types' import type { CreateCommitteeTypes } from './validation' export async function createCommittee(rawdata: CreateCommitteeTypes['Detailed']): Promise { const { name, shortName, logoImageId } = createCommitteeValidation.detailedValidate(rawdata) let defaultLogoImageId: number if (!logoImageId) { - defaultLogoImageId = await ImageMethods.readSpecial.client(prisma).execute({ - params: { special: 'DAFAULT_COMMITTEE_LOGO' }, session: null //TODO: pass session + defaultLogoImageId = await imageOperations.readSpecial({ + params: { special: 'DAFAULT_COMMITTEE_LOGO' }, //TODO: pass session }).then(res => res.id) } const article = await createArticle({}) diff --git a/src/services/groups/committees/destroy.ts b/src/services/groups/committees/destroy.ts index caa514d6a..097254486 100644 --- a/src/services/groups/committees/destroy.ts +++ b/src/services/groups/committees/destroy.ts @@ -1,6 +1,6 @@ -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' -import type { ExpandedCommittee } from './Types' +import type { ExpandedCommittee } from './types' export async function destroyCommittee(id: number): Promise { return await prismaCall(() => prisma.committee.delete({ diff --git a/src/services/groups/committees/methods.ts b/src/services/groups/committees/operations.ts similarity index 59% rename from src/services/groups/committees/methods.ts rename to src/services/groups/committees/operations.ts index 7aea30c20..2816f70ac 100644 --- a/src/services/groups/committees/methods.ts +++ b/src/services/groups/committees/operations.ts @@ -1,36 +1,35 @@ -import { CommitteeAuthers } from './authers' -import { CommitteeConfig } from './config' +import { committeeAuth } from './auth' +import { committeeExpandedIncluder, committeeLogoIncluder, membershipIncluder } from './constants' import { ServerOnlyAuther } from '@/auth/auther/RequireServer' -import { ImageMethods } from '@/services/images/methods' -import { ServiceMethod } from '@/services/ServiceMethod' import { articleRealtionsIncluder } from '@/cms/articles/ConfigVars' +import { imageOperations } from '@/services/images/operations' +import { defineOperation } from '@/services/serviceOperation' import { z } from 'zod' -export namespace CommitteeMethods { +export const committeeOperations = { - export const readCommittees = ServiceMethod({ - auther: () => CommitteeAuthers.read.dynamicFields({}), - method: async ({ prisma }) => prisma.committee.findMany({ - include: CommitteeConfig.committeeLogoIncluder, + readCommittees: defineOperation({ + authorizer: () => committeeAuth.read.dynamicFields({}), + operation: async ({ prisma }) => prisma.committee.findMany({ + include: committeeLogoIncluder, }) - }) + }), - export const readCommittee = ServiceMethod({ - auther: () => CommitteeAuthers.read.dynamicFields({}), + readCommittee: defineOperation({ + authorizer: () => committeeAuth.read.dynamicFields({}), paramsSchema: z.union([ z.object({ id: z.number() }), z.object({ shortName: z.string() }) ]), - method: async ({ prisma, params }) => { - const defaultImage = await ImageMethods.readSpecial.client(prisma).execute({ + operation: async ({ prisma, params }) => { + const defaultImage = await imageOperations.readSpecial({ params: { special: 'DEFAULT_PROFILE_IMAGE' }, - session: null, bypassAuth: true }) const result = await prisma.committee.findUniqueOrThrow({ where: params, - include: CommitteeConfig.committeeExpandedIncluder + include: committeeExpandedIncluder, }) return { @@ -48,14 +47,14 @@ export namespace CommitteeMethods { } } } - }) + }), - export const readCommitteArticle = ServiceMethod({ - auther: () => CommitteeAuthers.read.dynamicFields({}), + readCommitteArticle: defineOperation({ + authorizer: () => committeeAuth.read.dynamicFields({}), paramsSchema: z.object({ shortName: z.string(), }), - method: async ({ prisma, params }) => (await prisma.committee.findUniqueOrThrow({ + operation: async ({ prisma, params }) => (await prisma.committee.findUniqueOrThrow({ where: params, select: { committeeArticle: { @@ -63,46 +62,45 @@ export namespace CommitteeMethods { } } })).committeeArticle - }) + }), - export const readCommitteesFromGroupIds = ServiceMethod({ - auther: ServerOnlyAuther, + readCommitteesFromGroupIds: defineOperation({ + authorizer: ServerOnlyAuther, paramsSchema: z.object({ ids: z.number().int().array() }), - method: async ({ prisma, params }) => await prisma.committee.findMany({ + operation: async ({ prisma, params }) => await prisma.committee.findMany({ where: { groupId: { in: params.ids } }, - include: CommitteeConfig.committeeLogoIncluder, + include: committeeLogoIncluder, }) - }) + }), - export const readCommitteeParagraph = ServiceMethod({ - auther: () => CommitteeAuthers.read.dynamicFields({}), + readCommitteeParagraph: defineOperation({ + authorizer: () => committeeAuth.read.dynamicFields({}), paramsSchema: z.object({ shortName: z.string(), }), - method: async ({ prisma, params }) => (await prisma.committee.findUniqueOrThrow({ + operation: async ({ prisma, params }) => (await prisma.committee.findUniqueOrThrow({ where: params, select: { paragraph: true, } })).paragraph - }) + }), - export const readCommitteeMembers = ServiceMethod({ - auther: () => CommitteeAuthers.read.dynamicFields({}), + readCommitteeMembers: defineOperation({ + authorizer: () => committeeAuth.read.dynamicFields({}), paramsSchema: z.object({ shortName: z.string(), active: z.boolean().optional(), }), - method: async ({ prisma, session, params }) => { - const defaultImage = await ImageMethods.readSpecial.client(prisma).execute({ + operation: async ({ prisma, params }) => { + const defaultImage = await imageOperations.readSpecial({ params: { special: 'DEFAULT_PROFILE_IMAGE' }, - session, }) @@ -114,7 +112,7 @@ export namespace CommitteeMethods { group: { select: { memberships: { - include: CommitteeConfig.membershipIncluder, + include: membershipIncluder, where: { active: params.active } @@ -133,5 +131,5 @@ export namespace CommitteeMethods { } })) } - }) + }), } diff --git a/src/services/groups/committees/read.ts b/src/services/groups/committees/read.ts index 1cdf3127e..0901ad3c2 100644 --- a/src/services/groups/committees/read.ts +++ b/src/services/groups/committees/read.ts @@ -1,12 +1,12 @@ -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import { articleRealtionsIncluder } from '@/services/cms/articles/ConfigVars' -import { UserConfig } from '@/services/users/config' -import { ImageMethods } from '@/services/images/methods' -import type { ExpandedArticle } from '@/services/cms/articles/Types' +import { imageOperations } from '@/services/images/operations' +import { userFilterSelection } from '@/services/users/constants' +import type { ExpandedArticle } from '@/cms/articles/types' import type { CmsParagraph } from '@prisma/client' -import type { ExpandedCommittee, ExpandedCommitteeWithCover } from './Types' +import type { ExpandedCommittee, ExpandedCommitteeWithCover } from './types' export async function readCommittees(): Promise { return await prismaCall(() => prisma.committee.findMany({ @@ -88,11 +88,10 @@ export async function readCommitteeParagraph(shortName: string) : Promise res.id) } diff --git a/src/services/groups/config.ts b/src/services/groups/constants.ts similarity index 97% rename from src/services/groups/config.ts rename to src/services/groups/constants.ts index a4c619a6d..196d19418 100644 --- a/src/services/groups/config.ts +++ b/src/services/groups/constants.ts @@ -1,10 +1,10 @@ import type { GroupType, OmegaMembershipLevel, Prisma } from '@prisma/client' -import type { GroupTypeInfo } from './Types' +import type { GroupTypeInfo } from './types' /** * A object that describes the different group types in a friendly way */ -export const GroupTypesConfig = { +export const groupTypesConfig = { OMEGA_MEMBERSHIP_GROUP: { name: 'Medlemsgruppe', namePlural: 'Medlemsgrupper', diff --git a/src/services/groups/interestGroups/actions.ts b/src/services/groups/interestGroups/actions.ts new file mode 100644 index 000000000..3ff7cac45 --- /dev/null +++ b/src/services/groups/interestGroups/actions.ts @@ -0,0 +1,12 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { interestGroupOperations } from '@/services/groups/interestGroups/operations' + +export const createInterestGroupAction = makeAction(interestGroupOperations.create) + +export const destroyInterestGroupAction = makeAction(interestGroupOperations.destroy) + +export const readInterestGroupsAction = makeAction(interestGroupOperations.readMany) + +export const updateInterestGroupAction = makeAction(interestGroupOperations.update) diff --git a/src/services/groups/interestGroups/auth.ts b/src/services/groups/interestGroups/auth.ts new file mode 100644 index 000000000..b61dd6cf7 --- /dev/null +++ b/src/services/groups/interestGroups/auth.ts @@ -0,0 +1,10 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' +import { RequirePermissionOrGroupAdmin } from '@/auth/auther/RequirePermissionOrGroupAdmin' + +export const interestGroupAuth = { + create: RequirePermission.staticFields({ permission: 'INTEREST_GROUP_ADMIN' }), + read: RequirePermission.staticFields({ permission: 'INTEREST_GROUP_READ' }), + readMany: RequirePermission.staticFields({ permission: 'INTEREST_GROUP_READ' }), + update: RequirePermissionOrGroupAdmin.staticFields({ permission: 'INTEREST_GROUP_ADMIN' }), + destroy: RequirePermission.staticFields({ permission: 'INTEREST_GROUP_ADMIN' }), +} diff --git a/src/services/groups/interestGroups/authers.ts b/src/services/groups/interestGroups/authers.ts deleted file mode 100644 index 4ae3bb7bb..000000000 --- a/src/services/groups/interestGroups/authers.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' -import { RequirePermissionOrGroupAdmin } from '@/auth/auther/RequirePermissionOrGroupAdmin' - -export namespace InterestGroupAuthers { - export const create = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_ADMIN' }) - export const read = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_READ' }) - export const readMany = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_READ' }) - export const update = RequirePermissionOrGroupAdmin.staticFields({ permission: 'INTEREST_GROUP_ADMIN' }) - export const destroy = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_ADMIN' }) -} diff --git a/src/services/groups/interestGroups/methods.ts b/src/services/groups/interestGroups/operations.ts similarity index 56% rename from src/services/groups/interestGroups/methods.ts rename to src/services/groups/interestGroups/operations.ts index 93d1da797..3a4382efd 100644 --- a/src/services/groups/interestGroups/methods.ts +++ b/src/services/groups/interestGroups/operations.ts @@ -1,17 +1,17 @@ import '@pn-server-only' -import { InterestGroupAuthers } from './authers' -import { InterestGroupSchemas } from './schemas' +import { interestGroupAuth } from './auth' +import { interestGroupSchemas } from './schemas' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' -import { ServiceMethod } from '@/services/ServiceMethod' import { articleSectionsRealtionsIncluder } from '@/services/cms/articleSections/ConfigVars' +import { defineOperation } from '@/services/serviceOperation' import { z } from 'zod' -import type { ExpandedInterestGroup } from './Types' +import type { ExpandedInterestGroup } from './types' -export namespace InterestGroupMethods { - export const create = ServiceMethod({ - dataSchema: InterestGroupSchemas.create, - auther: () => InterestGroupAuthers.create.dynamicFields({}), - method: async ({ prisma, data }) => { +export const interestGroupOperations = { + create: defineOperation({ + dataSchema: interestGroupSchemas.create, + authorizer: () => interestGroupAuth.create.dynamicFields({}), + operation: async ({ prisma, data }) => { const { order } = await readCurrentOmegaOrder() await prisma.interestGroup.create({ @@ -33,11 +33,11 @@ export namespace InterestGroupMethods { } }) } - }) + }), - export const readMany = ServiceMethod({ - auther: () => InterestGroupAuthers.readMany.dynamicFields({}), - method: async ({ prisma }): Promise => prisma.interestGroup.findMany({ + readMany: defineOperation({ + authorizer: () => interestGroupAuth.readMany.dynamicFields({}), + operation: async ({ prisma }): Promise => prisma.interestGroup.findMany({ include: { articleSection: { include: articleSectionsRealtionsIncluder, @@ -48,15 +48,15 @@ export namespace InterestGroupMethods { { id: 'asc' }, ] }) - }) + }), - export const read = ServiceMethod({ + read: defineOperation({ paramsSchema: z.object({ id: z.number().optional(), shortName: z.string().optional(), }), - auther: () => InterestGroupAuthers.read.dynamicFields({}), - method: async ({ prisma, params: { id, shortName } }) => await prisma.interestGroup.findUniqueOrThrow({ + authorizer: () => interestGroupAuth.read.dynamicFields({}), + operation: async ({ prisma, params: { id, shortName } }) => await prisma.interestGroup.findUniqueOrThrow({ where: { id, shortName, @@ -67,35 +67,36 @@ export namespace InterestGroupMethods { }, } }) - }) + }), - export const update = ServiceMethod({ + update: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - dataSchema: InterestGroupSchemas.update, - auther: async ({ params }) => InterestGroupAuthers.update.dynamicFields({ - groupId: ( - await read.newClient().execute({ - params: { id: params.id }, - session: null, - bypassAuth: true, - }) - ).groupId, - }), - method: async ({ prisma, params: { id }, data }) => prisma.interestGroup.update({ + dataSchema: interestGroupSchemas.update, + authorizer: async ({ prisma, params }) => { + const { groupId } = await prisma.interestGroup.findUniqueOrThrow({ + where: { id: params.id }, + select: { groupId: true }, + }) + + return interestGroupAuth.update.dynamicFields({ + groupId, + }) + }, + operation: async ({ prisma, params: { id }, data }) => prisma.interestGroup.update({ where: { id }, data, }), - }) + }), - export const destroy = ServiceMethod({ + destroy: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - auther: () => InterestGroupAuthers.destroy.dynamicFields({}), + authorizer: () => interestGroupAuth.destroy.dynamicFields({}), opensTransaction: true, - method: async ({ prisma, params: { id } }) => { + operation: async ({ prisma, params: { id } }) => { await prisma.$transaction(async tx => { const intrestGroup = await tx.interestGroup.delete({ where: { id } @@ -105,5 +106,5 @@ export namespace InterestGroupMethods { }) }) } - }) + }), } diff --git a/src/services/groups/interestGroups/schemas.ts b/src/services/groups/interestGroups/schemas.ts index 9c044de54..c0ffcd08a 100644 --- a/src/services/groups/interestGroups/schemas.ts +++ b/src/services/groups/interestGroups/schemas.ts @@ -1,26 +1,24 @@ import { z } from 'zod' -export namespace InterestGroupSchemas { - const fields = z.object({ - name: z.string().min( - 3, 'Navn må ha minst 3 tegn' - ).max( - 30, 'Navn kan ha maks 30 tegn' - ).trim(), - shortName: z.string().min( - 3, 'Kortnavn må ha minst 3 tegn' - ).max( - 10, 'Kortnavn kan ha maks 10 tegn' - ).trim(), - }) +const baseSchema = z.object({ + name: z.string() + .min(3, 'Navn må ha minst 3 tegn') + .max(30, 'Navn kan ha maks 30 tegn') + .trim(), + shortName: z.string() + .min(3, 'Kortnavn må ha minst 3 tegn') + .max(10, 'Kortnavn kan ha maks 10 tegn') + .trim(), +}) - export const create = fields.pick({ +export const interestGroupSchemas = { + create: baseSchema.pick({ name: true, shortName: true, - }) + }), - export const update = fields.partial().pick({ + update: baseSchema.partial().pick({ name: true, shortName: true, - }) + }), } diff --git a/src/services/groups/interestGroups/Types.ts b/src/services/groups/interestGroups/types.ts similarity index 64% rename from src/services/groups/interestGroups/Types.ts rename to src/services/groups/interestGroups/types.ts index 4ab8df3dd..d839fa100 100644 --- a/src/services/groups/interestGroups/Types.ts +++ b/src/services/groups/interestGroups/types.ts @@ -1,4 +1,4 @@ -import type { ExpandedArticleSection } from '@/services/cms/articleSections/Types' +import type { ExpandedArticleSection } from '@/cms/articleSections/types' import type { InterestGroup } from '@prisma/client' export type ExpandedInterestGroup = InterestGroup & { diff --git a/src/services/groups/manualGroups/create.ts b/src/services/groups/manualGroups/create.ts index 520afc58c..4a31ba92d 100644 --- a/src/services/groups/manualGroups/create.ts +++ b/src/services/groups/manualGroups/create.ts @@ -1,7 +1,7 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' -import type { ExpandedManualGroup } from './Types' +import type { ExpandedManualGroup } from './types' type CreateManualGroupArgs = { name: string, diff --git a/src/services/groups/manualGroups/destroy.ts b/src/services/groups/manualGroups/destroy.ts index 34ae07737..91c3f6fb6 100644 --- a/src/services/groups/manualGroups/destroy.ts +++ b/src/services/groups/manualGroups/destroy.ts @@ -1,6 +1,6 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ExpandedManualGroup } from './Types' +import { prisma } from '@/prisma/client' +import type { ExpandedManualGroup } from './types' export async function destroyManualGroup(id: number): Promise { return await prismaCall(() => prisma.manualGroup.delete({ diff --git a/src/services/groups/manualGroups/read.ts b/src/services/groups/manualGroups/read.ts index f75f4e4b0..83300d2e3 100644 --- a/src/services/groups/manualGroups/read.ts +++ b/src/services/groups/manualGroups/read.ts @@ -1,6 +1,6 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ExpandedManualGroup } from './Types' +import { prisma } from '@/prisma/client' +import type { ExpandedManualGroup } from './types' export async function readManualGroups(): Promise { return await prismaCall(() => prisma.manualGroup.findMany()) diff --git a/src/services/groups/manualGroups/Types.ts b/src/services/groups/manualGroups/types.ts similarity index 100% rename from src/services/groups/manualGroups/Types.ts rename to src/services/groups/manualGroups/types.ts diff --git a/src/services/groups/manualGroups/update.ts b/src/services/groups/manualGroups/update.ts index b2bc43d86..04bc5cf9e 100644 --- a/src/services/groups/manualGroups/update.ts +++ b/src/services/groups/manualGroups/update.ts @@ -1,5 +1,5 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' type UpdateManualGroupArgs = { name?: string, diff --git a/src/services/groups/memberships/actions.ts b/src/services/groups/memberships/actions.ts new file mode 100644 index 000000000..9510ca9a3 --- /dev/null +++ b/src/services/groups/memberships/actions.ts @@ -0,0 +1,75 @@ +'use server' + +import { createActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createMembershipsForGroup } from '@/services/groups/memberships/create' +import { destoryMembershipOfUser } from '@/services/groups/memberships/destroy' +import { updateMembership } from '@/services/groups/memberships/update' +import type { ExpandedMembership } from '@/services/groups/memberships/types' +import type { ActionReturn } from '@/services/actionTypes' + +/** + * WARNING: This action will lead to error if used with group types not in CanEasalyManageMembership + */ +export async function createMembershipsForGroupAction({ + groupId, + users +}: { + groupId: number, + users: { + userId: number, + admin: boolean + }[] +}): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['GROUP_ADMIN']] + }) + if (!authorized) return createActionError(status) + + return safeServerCall(() => createMembershipsForGroup(groupId, users)) +} + +/** + * WARNING: Do not use this action, usually you want updateMemebershipInactivate + * @param + * @returns + */ +export async function destroyMembership({ + groupId, + userId, + orderArg, +}: { + groupId: number, + userId: number, + orderArg: number +}): Promise> { + //TODO: make function to check that. user is admin of group + + return await safeServerCall(() => destoryMembershipOfUser({ + groupId, + userId, + orderArg + })) +} + +export async function updateMembershipAdminAcion(membership: { + groupId: number + userId: number +}, admin: boolean): Promise> { + //TODO: make function to check that user is admin of group + return await safeServerCall(() => updateMembership({ + ...membership, + orderArg: 'ACTIVE' + }, { admin })) +} + +export async function updateMembershipActiveAction(membership: { + groupId: number + userId: number +}, active: boolean): Promise> { + //TODO: make function to check that user is admin of group + return await safeServerCall(() => updateMembership({ + ...membership, + orderArg: 'ACTIVE' + }, { active })) +} diff --git a/src/services/groups/memberships/canEasilyManageMembership.ts b/src/services/groups/memberships/canEasilyManageMembership.ts index 3876dda84..d628c1125 100644 --- a/src/services/groups/memberships/canEasilyManageMembership.ts +++ b/src/services/groups/memberships/canEasilyManageMembership.ts @@ -1,6 +1,6 @@ import { CanEasilyManageMembership } from './ConfigVars' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' export async function canEasilyManageMembershipOfGroup(groupId: number): Promise { const group = await prismaCall(() => prisma.group.findUniqueOrThrow({ diff --git a/src/services/groups/memberships/create.ts b/src/services/groups/memberships/create.ts index 5c271fa1d..8803f7193 100644 --- a/src/services/groups/memberships/create.ts +++ b/src/services/groups/memberships/create.ts @@ -3,10 +3,10 @@ import { canEasilyManageMembershipOfGroup, canEasilyManageMembershipOfGroups } f import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { invalidateManyUserSessionData, invalidateOneUserSessionData } from '@/services/auth/invalidateSession' -import { GroupMethods } from '@/services/groups/methods' -import type { ExpandedMembership } from './Types' +import { groupOperations } from '@/services/groups/operations' +import type { ExpandedMembership } from './types' export async function createMembershipForUser( groupId: number, @@ -18,8 +18,7 @@ export async function createMembershipForUser( throw new ServerError('BAD PARAMETERS', 'Denne Gruppetypen kan ikke enkelt opprette medlemskap') } - const order = orderArg ?? await GroupMethods.readCurrentGroupOrder.newClient().execute({ - session: null, + const order = orderArg ?? await groupOperations.readCurrentGroupOrder({ bypassAuth: true, params: { id: groupId, @@ -65,8 +64,7 @@ export async function createMembershipsForGroup( if (!await canEasilyManageMembershipOfGroup(groupId)) { throw new ServerError('BAD PARAMETERS', 'Denne Gruppetypen kan ikke enkelt opprette medlemskap') } - const order = orderArg ?? await GroupMethods.readCurrentGroupOrder.newClient().execute({ - session: null, + const order = orderArg ?? await groupOperations.readCurrentGroupOrder({ bypassAuth: true, params: { id: groupId, @@ -113,8 +111,7 @@ export async function createMembershipsForUser( if (!await canEasilyManageMembershipOfGroups(data.map(group => group.groupId))) { throw new ServerError('BAD PARAMETERS', 'Denne Gruppetypen kan ikke enkelt opprette medlemskap') } - const ordersMap = await GroupMethods.readCurrentGroupOrders.newClient().execute({ - session: null, + const ordersMap = await groupOperations.readCurrentGroupOrders({ bypassAuth: true, params: { ids: data.map(group => group.groupId) diff --git a/src/services/groups/memberships/destroy.ts b/src/services/groups/memberships/destroy.ts index be62cd702..694fa7492 100644 --- a/src/services/groups/memberships/destroy.ts +++ b/src/services/groups/memberships/destroy.ts @@ -2,10 +2,10 @@ import '@pn-server-only' import { canEasilyManageMembershipOfGroup } from './canEasilyManageMembership' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { invalidateManyUserSessionData, invalidateOneUserSessionData } from '@/services/auth/invalidateSession' -import { GroupMethods } from '@/services/groups/methods' -import type { ExpandedMembership } from './Types' +import { groupOperations } from '@/services/groups/operations' +import type { ExpandedMembership } from './types' export async function destoryMembershipOfUser({ groupId, @@ -19,9 +19,8 @@ export async function destoryMembershipOfUser({ if (!await canEasilyManageMembershipOfGroup(groupId)) { throw new ServerError('BAD PARAMETERS', 'Denne Gruppetypen kan ikke enkelt opprette medlemskap') } - const order = orderArg ?? await GroupMethods.readCurrentGroupOrder.newClient().execute({ + const order = orderArg ?? await groupOperations.readCurrentGroupOrder({ bypassAuth: true, - session: null, params: { id: groupId } @@ -48,8 +47,7 @@ export async function destroyMembershipOfUsers( if (!await canEasilyManageMembershipOfGroup(groupId)) { throw new ServerError('BAD PARAMETERS', 'Denne Gruppetypen kan ikke enkelt opprette medlemskap') } - const order = orderArg ?? await GroupMethods.readCurrentGroupOrder.newClient().execute({ - session: null, + const order = orderArg ?? await groupOperations.readCurrentGroupOrder({ bypassAuth: true, params: { id: groupId, diff --git a/src/services/groups/memberships/read.ts b/src/services/groups/memberships/read.ts index 537d181d6..f604b9e21 100644 --- a/src/services/groups/memberships/read.ts +++ b/src/services/groups/memberships/read.ts @@ -3,8 +3,8 @@ import { membershipFilterSelection } from './ConfigVars' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import { getMembershipFilter } from '@/auth/getMembershipFilter' -import prisma from '@/prisma' -import type { ExpandedMembership, MembershipFiltered, MembershipSelectorType } from './Types' +import { prisma } from '@/prisma/client' +import type { ExpandedMembership, MembershipFiltered, MembershipSelectorType } from './types' export async function readMembershipsOfGroup(id: number): Promise { const count = await prismaCall(() => prisma.group.count({ diff --git a/src/services/groups/memberships/Types.ts b/src/services/groups/memberships/types.ts similarity index 100% rename from src/services/groups/memberships/Types.ts rename to src/services/groups/memberships/types.ts diff --git a/src/services/groups/memberships/update.ts b/src/services/groups/memberships/update.ts index 048787876..576fca2a4 100644 --- a/src/services/groups/memberships/update.ts +++ b/src/services/groups/memberships/update.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { invalidateOneUserSessionData } from '@/services/auth/invalidateSession' -import { GroupMethods } from '@/services/groups/methods' -import type { ExpandedMembership } from './Types' +import { groupOperations } from '@/services/groups/operations' +import type { ExpandedMembership } from './types' export async function updateMembership({ groupId, @@ -18,9 +18,8 @@ export async function updateMembership({ active?: boolean }): Promise { const order = (orderArg && typeof orderArg === 'number') ? orderArg : ( - await GroupMethods.readCurrentGroupOrder.newClient().execute({ + await groupOperations.readCurrentGroupOrder({ bypassAuth: true, - session: null, params: { id: groupId } diff --git a/src/services/groups/omegaMembershipGroups/read.ts b/src/services/groups/omegaMembershipGroups/read.ts index f90dd934f..e332e010c 100644 --- a/src/services/groups/omegaMembershipGroups/read.ts +++ b/src/services/groups/omegaMembershipGroups/read.ts @@ -1,8 +1,8 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { ServerError } from '@/services/error' import type { OmegaMembershipLevel } from '@prisma/client' -import type { ExpandedOmegaMembershipGroup } from './Types' +import type { ExpandedOmegaMembershipGroup } from './types' export async function readOmegaMembershipGroups(): Promise { return await prismaCall(() => prisma.omegaMembershipGroup.findMany()) diff --git a/src/services/groups/omegaMembershipGroups/Types.ts b/src/services/groups/omegaMembershipGroups/types.ts similarity index 100% rename from src/services/groups/omegaMembershipGroups/Types.ts rename to src/services/groups/omegaMembershipGroups/types.ts diff --git a/src/services/groups/omegaMembershipGroups/update.ts b/src/services/groups/omegaMembershipGroups/update.ts index e3d6d4f78..184d07eb5 100644 --- a/src/services/groups/omegaMembershipGroups/update.ts +++ b/src/services/groups/omegaMembershipGroups/update.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { readOmegaMembershipGroup, readUserOmegaMembershipLevel } from './read' -import { OMEGA_MEMBERSHIP_LEVEL_RANKING } from '@/services/groups/config' -import prisma from '@/prisma' +import { OMEGA_MEMBERSHIP_LEVEL_RANKING } from '@/services/groups/constants' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' import { ServerError } from '@/services/error' diff --git a/src/services/groups/methods.ts b/src/services/groups/operations.ts similarity index 80% rename from src/services/groups/methods.ts rename to src/services/groups/operations.ts index 6bc53dce7..ac4c5c8fa 100644 --- a/src/services/groups/methods.ts +++ b/src/services/groups/operations.ts @@ -1,9 +1,9 @@ import '@pn-server-only' -import { groupsExpandedIncluder, GroupTypesConfig, OmegaMembershipLevelConfig, readGroupsOfUserIncluder } from './config' -import { GroupAuthers } from './authers' -import { UserConfig } from '@/services/users/config' +import { groupsExpandedIncluder, groupTypesConfig, OmegaMembershipLevelConfig, readGroupsOfUserIncluder } from './constants' +import { groupAuth } from './auth' +import { userFilterSelection } from '@/services/users/constants' import { ServerError } from '@/services/error' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import { ServerOnlyAuther } from '@/auth/auther/RequireServer' import { getMembershipFilter } from '@/auth/getMembershipFilter' import logger from '@/lib/logger' @@ -23,8 +23,8 @@ import type { GroupWithDumbRelations, GroupWithRelations, GroupWithRelationsNameInferencer -} from './Types' -import type { UserFiltered } from '@/services/users/Types' +} from './types' +import type { UserFiltered } from '@/services/users/types' async function expandGroup(group: GroupWithRelationsNameInferencer & { membershipsToInferFirstOrder: { order: number }[] @@ -187,19 +187,18 @@ export function checkGroupValidity< } } -export namespace GroupMethods { +export const groupOperations = { + readGroups: defineOperation({ + authorizer: () => groupAuth.read.dynamicFields({}), + operation: async ({ prisma }) => prisma.group.findMany() + }), - export const readGroups = ServiceMethod({ - auther: () => GroupAuthers.read.dynamicFields({}), - method: async ({ prisma }) => prisma.group.findMany() - }) - - export const readCurrentGroupOrder = ServiceMethod({ - auther: ServerOnlyAuther, + readCurrentGroupOrder: defineOperation({ + authorizer: ServerOnlyAuther, paramsSchema: z.object({ id: z.number(), }), - method: async ({ prisma, params }) => (await prisma.group.findUniqueOrThrow({ + operation: async ({ prisma, params }) => (await prisma.group.findUniqueOrThrow({ where: { id: params.id, }, @@ -207,14 +206,14 @@ export namespace GroupMethods { order: true, } })).order - }) + }), - export const readCurrentGroupOrders = ServiceMethod({ - auther: ServerOnlyAuther, + readCurrentGroupOrders: defineOperation({ + authorizer: ServerOnlyAuther, paramsSchema: z.object({ ids: z.number().array(), }), - method: async ({ prisma, params }) => prisma.group.findMany({ + operation: async ({ prisma, params }) => prisma.group.findMany({ where: { id: { in: params.ids, @@ -225,26 +224,26 @@ export namespace GroupMethods { order: true, } }) - }) + }), - export const readGroup = ServiceMethod({ - auther: ServerOnlyAuther, + readGroup: defineOperation({ + authorizer: ServerOnlyAuther, paramsSchema: z.object({ id: z.number(), }), - method: async ({ prisma, params }) => prisma.group.findUniqueOrThrow({ + operation: async ({ prisma, params }) => prisma.group.findUniqueOrThrow({ where: { id: params.id, }, }) - }) + }), - export const readGroupExpanded = ServiceMethod({ - auther: () => GroupAuthers.read.dynamicFields({}), + readGroupExpanded: defineOperation({ + authorizer: () => groupAuth.read.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { const group = await prisma.group.findFirstOrThrow({ where: { id: params.id, @@ -253,70 +252,68 @@ export namespace GroupMethods { }).then(checkGroupValidity).then(grp => ({ ...grp, membershipsToInferFirstOrder: grp.memberships })) return expandGroup(group, prisma) } - }) + }), - export const readGroupsExpanded = ServiceMethod({ - auther: () => GroupAuthers.read.dynamicFields({}), - method: async ({ prisma }) => { + readGroupsExpanded: defineOperation({ + authorizer: () => groupAuth.read.dynamicFields({}), + operation: async ({ prisma }) => { const groups = (await prisma.group.findMany({ include: groupsExpandedIncluder, })).map(checkGroupValidity).map(grp => ({ ...grp, membershipsToInferFirstOrder: grp.memberships })) return await Promise.all(groups.map(group => expandGroup(group, prisma))) } - }) + }), - export const readGroupsStructured = ServiceMethod({ - auther: () => GroupAuthers.read.dynamicFields({}), - method: async ({ prisma, session }) => { + readGroupsStructured: defineOperation({ + authorizer: () => groupAuth.read.dynamicFields({}), + operation: async () => { const groupsStructured: GroupsStructured = { CLASS: { - ...GroupTypesConfig.CLASS, + ...groupTypesConfig.CLASS, groups: [], }, COMMITTEE: { - ...GroupTypesConfig.COMMITTEE, + ...groupTypesConfig.COMMITTEE, groups: [], }, INTEREST_GROUP: { - ...GroupTypesConfig.INTEREST_GROUP, + ...groupTypesConfig.INTEREST_GROUP, groups: [], }, MANUAL_GROUP: { - ...GroupTypesConfig.MANUAL_GROUP, + ...groupTypesConfig.MANUAL_GROUP, groups: [], }, OMEGA_MEMBERSHIP_GROUP: { - ...GroupTypesConfig.OMEGA_MEMBERSHIP_GROUP, + ...groupTypesConfig.OMEGA_MEMBERSHIP_GROUP, groups: [], }, STUDY_PROGRAMME: { - ...GroupTypesConfig.STUDY_PROGRAMME, + ...groupTypesConfig.STUDY_PROGRAMME, groups: [], }, } satisfies GroupsStructured - const groupExpanded = await readGroupsExpanded.client(prisma).execute({ - bypassAuth: true, - session, - }) + const groupExpanded = await groupOperations.readGroupsExpanded({ bypassAuth: true }) + groupExpanded.forEach(group => { groupsStructured[group.groupType].groups.push(group) }) return groupsStructured } - }) + }), - export const readUsersOfGroups = ServiceMethod({ - auther: ServerOnlyAuther, + readUsersOfGroups: defineOperation({ + authorizer: ServerOnlyAuther, paramsSchema: z.object({ groups: z.array(z.object({ groupId: z.number(), admin: z.boolean(), })), }), - method: async ({ prisma, params }): Promise => { + operation: async ({ prisma, params }): Promise => { const memberships = await prisma.membership.findMany({ where: { OR: params.groups.map(({ admin, groupId }) => ({ @@ -326,21 +323,21 @@ export namespace GroupMethods { }, select: { user: { - select: UserConfig.filterSelection, + select: userFilterSelection, } } }) return memberships.map(({ user }) => user) } - }) + }), - export const readGroupsOfUser = ServiceMethod({ - auther: ServerOnlyAuther, + readGroupsOfUser: defineOperation({ + authorizer: ServerOnlyAuther, paramsSchema: z.object({ userId: z.number(), }), - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { const memberships = await prisma.membership.findMany({ where: { userId: params.userId, @@ -356,5 +353,5 @@ export namespace GroupMethods { return groups }, - }) + }), } diff --git a/src/services/groups/studyProgrammes/actions.ts b/src/services/groups/studyProgrammes/actions.ts new file mode 100644 index 000000000..f4363d6b1 --- /dev/null +++ b/src/services/groups/studyProgrammes/actions.ts @@ -0,0 +1,46 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createStudyProgramme } from '@/services/groups/studyProgrammes/create' +import { readStudyProgrammes } from '@/services/groups/studyProgrammes/read' +import { updateStudyProgramme } from '@/services/groups/studyProgrammes/update' +import { createStudyProgrammeValidation, updateStudyProgrammeValidation } from '@/services/groups/studyProgrammes/validation' +import type { ActionReturn } from '@/services/actionTypes' +import type { StudyProgramme } from '@prisma/client' + +export async function createStudyProgrammeAction(rawdata: FormData): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['STUDY_PROGRAMME_CREATE']], + }) + if (!authorized) return createActionError(status) + + const parse = createStudyProgrammeValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + + return await safeServerCall(() => createStudyProgramme(parse.data)) +} + +export async function readStudyProgrammesAction(): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['STUDY_PROGRAMME_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readStudyProgrammes()) +} + +export async function updateStudyProgrammeAction(id: number, rawdata: FormData): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['STUDY_PROGRAMME_UPDATE']], + }) + if (!authorized) return createActionError(status) + + const parse = updateStudyProgrammeValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + + return await safeServerCall(() => updateStudyProgramme({ + ...parse.data, + id, + })) +} diff --git a/src/services/groups/studyProgrammes/create.ts b/src/services/groups/studyProgrammes/create.ts index ab8f92102..667b06082 100644 --- a/src/services/groups/studyProgrammes/create.ts +++ b/src/services/groups/studyProgrammes/create.ts @@ -1,9 +1,9 @@ import { createStudyProgrammeValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' import type { CreateStudyProgrammeTypes } from './validation' -import type { ExpandedStudyProgramme } from './Types' +import type { ExpandedStudyProgramme } from './types' export async function createStudyProgramme(data: CreateStudyProgrammeTypes['Detailed']): Promise { const parse = createStudyProgrammeValidation.detailedValidate(data) diff --git a/src/services/groups/studyProgrammes/destroy.ts b/src/services/groups/studyProgrammes/destroy.ts index 3ef66e961..bbe130736 100644 --- a/src/services/groups/studyProgrammes/destroy.ts +++ b/src/services/groups/studyProgrammes/destroy.ts @@ -1,6 +1,6 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ExpandedStudyProgramme } from './Types' +import { prisma } from '@/prisma/client' +import type { ExpandedStudyProgramme } from './types' export async function destroyStudyProgramme(id: number): Promise { return await prismaCall(() => prisma.studyProgramme.delete({ diff --git a/src/services/groups/studyProgrammes/read.ts b/src/services/groups/studyProgrammes/read.ts index f53e2b09c..80e05f4c2 100644 --- a/src/services/groups/studyProgrammes/read.ts +++ b/src/services/groups/studyProgrammes/read.ts @@ -1,6 +1,6 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ExpandedStudyProgramme } from './Types' +import { prisma } from '@/prisma/client' +import type { ExpandedStudyProgramme } from './types' export async function readStudyProgrammes(): Promise { return await prismaCall(() => prisma.studyProgramme.findMany()) diff --git a/src/services/groups/studyProgrammes/Types.ts b/src/services/groups/studyProgrammes/types.ts similarity index 100% rename from src/services/groups/studyProgrammes/Types.ts rename to src/services/groups/studyProgrammes/types.ts diff --git a/src/services/groups/studyProgrammes/update.ts b/src/services/groups/studyProgrammes/update.ts index 84b965135..8cc4d8bfa 100644 --- a/src/services/groups/studyProgrammes/update.ts +++ b/src/services/groups/studyProgrammes/update.ts @@ -1,8 +1,8 @@ import { updateStudyProgrammeValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { UpdateStudyProgrammeTypes } from './validation' -import type { ExpandedStudyProgramme } from './Types' +import type { ExpandedStudyProgramme } from './types' export async function updateStudyProgramme( data: UpdateStudyProgrammeTypes['Detailed'] diff --git a/src/services/groups/Types.ts b/src/services/groups/types.ts similarity index 100% rename from src/services/groups/Types.ts rename to src/services/groups/types.ts diff --git a/src/services/images/actions.ts b/src/services/images/actions.ts new file mode 100644 index 000000000..9d91c2142 --- /dev/null +++ b/src/services/images/actions.ts @@ -0,0 +1,24 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { imageOperations } from '@/services/images/operations' + +export const createImageAction = makeAction(imageOperations.create) +export const createImagesAction = makeAction(imageOperations.createMany) + +export const destroyImageAction = makeAction(imageOperations.destroy) + +/** + * Read one image. +*/ +export const readImageAction = makeAction(imageOperations.read) +/** + * Read one page of images. + */ +export const readImagesPageAction = makeAction(imageOperations.readPage) +/** + * Read one special image. + */ +export const readSpecialImageAction = makeAction(imageOperations.readSpecial) + +export const updateImageAction = makeAction(imageOperations.update) diff --git a/src/services/images/auth.ts b/src/services/images/auth.ts new file mode 100644 index 000000000..5f4a7cf2d --- /dev/null +++ b/src/services/images/auth.ts @@ -0,0 +1,13 @@ +import { RequireNothing } from '@/auth/auther/RequireNothing' + +//TODO: Implement proper authers +export const imageAuth = { + create: RequireNothing.staticFields({}), + createMany: RequireNothing.staticFields({}), + createSourcelessImage: RequireNothing.staticFields({}), + read: RequireNothing.staticFields({}), + readPage: RequireNothing.staticFields({}), + readSpecial: RequireNothing.staticFields({}), + update: RequireNothing.staticFields({}), + destroy: RequireNothing.staticFields({}), +} diff --git a/src/services/images/authers.ts b/src/services/images/authers.ts deleted file mode 100644 index df564e683..000000000 --- a/src/services/images/authers.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { RequireNothing } from '@/auth/auther/RequireNothing' - -//TODO: Implement proper authers -export namespace ImageAuthers { - export const create = RequireNothing.staticFields({}) - export const createMany = RequireNothing.staticFields({}) - export const createSourcelessImage = RequireNothing.staticFields({}) - export const read = RequireNothing.staticFields({}) - export const readPage = RequireNothing.staticFields({}) - export const readSpecial = RequireNothing.staticFields({}) - export const update = RequireNothing.staticFields({}) - export const destroy = RequireNothing.staticFields({}) -} diff --git a/src/actions/images/collections/read.ts b/src/services/images/collections/actions.ts similarity index 51% rename from src/actions/images/collections/read.ts rename to src/services/images/collections/actions.ts index a9404774c..bb12b2255 100644 --- a/src/actions/images/collections/read.ts +++ b/src/services/images/collections/actions.ts @@ -1,25 +1,47 @@ 'use server' -import { createActionError } from '@/actions/error' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { checkVisibility } from '@/auth/checkVisibility' +import { getUser } from '@/auth/session/getUser' +import { getVisibilityFilter } from '@/auth/getVisibilityFilter' +import { createImageCollection } from '@/services/images/collections/create' +import { destroyImageCollection } from '@/services/images/collections/destroy' import { readImageCollection, readImageCollectionsPage, - readSpecialImageCollection -} from '@/services/images/collections/read' -import { safeServerCall } from '@/actions/safeServerCall' -import { getUser } from '@/auth/getUser' -import { getVisibilityFilter } from '@/auth/getVisibilityFilter' + readSpecialImageCollection } from '@/services/images/collections/read' +import { updateImageCollection } from '@/services/images/collections/update' +import { createImageCollectionValidation, updateImageCollectionValidation } from '@/services/images/collections/validation' import { includeVisibility } from '@/services/visibility/read' -import { checkVisibility } from '@/auth/checkVisibility' import { SpecialCollection } from '@prisma/client' -import type { VisibilityCollapsed } from '@/services/visibility/Types' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { ImageCollection } from '@prisma/client' -import type { - ExpandedImageCollection, +import type { CreateImageCollectionTypes, UpdateImageCollectionTypes } from '@/services/images/collections/validation' +import type { VisibilityCollapsed } from '@/services/visibility/types' +import type { ExpandedImageCollection, ImageCollectionCursor, - ImageCollectionPageReturn -} from '@/services/images/collections/Types' -import type { ActionReturn } from '@/actions/Types' + ImageCollectionPageReturn } from '@/services/images/collections/types' +import type { ReadPageInput } from '@/lib/paging/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { ImageCollection } from '@prisma/client' + +export async function createImageCollectionAction( + rawdata: FormData | CreateImageCollectionTypes['Type'] +): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['IMAGE_COLLECTION_CREATE']] + }) + if (!authorized) return createActionError(status) + + const parse = createImageCollectionValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + return await safeServerCall(() => createImageCollection(data)) +} + +export async function destroyImageCollectionAction(collectionId: number): Promise> { + //TODO: Visibility check + + return await safeServerCall(() => destroyImageCollection(collectionId)) +} /** * Action that reads an image collection by id or name @@ -70,3 +92,23 @@ export async function readSpecialImageCollectionAction(special: SpecialCollectio //TODO: Check permission associated with the special collection return await safeServerCall(() => readSpecialImageCollection(special)) } + +/** + * A action that updates an image collection + * @param collectionId - the id of the collection to update + * @param coverImageId - the id of the image to set as the cover image (optional) + * @param rawdata - the data to update the collection with + * @returns - the updated collection in ActionReturn + */ +export async function updateImageCollectionAction( + collectionId: number, + coverImageId: number | undefined, + rawdata: FormData | UpdateImageCollectionTypes['Type'] +): Promise> { + const parse = updateImageCollectionValidation.typeValidate(rawdata) + + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => updateImageCollection(collectionId, coverImageId, data)) +} diff --git a/src/services/images/collections/create.ts b/src/services/images/collections/create.ts index 337c5e38e..66eaa424a 100644 --- a/src/services/images/collections/create.ts +++ b/src/services/images/collections/create.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { createImageCollectionValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import { createVisibility } from '@/services/visibility/create' import type { CreateImageCollectionTypes } from './validation' diff --git a/src/services/images/collections/destroy.ts b/src/services/images/collections/destroy.ts index 3a4fc3bfa..07e1b7a53 100644 --- a/src/services/images/collections/destroy.ts +++ b/src/services/images/collections/destroy.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' import { destroyVisibility } from '@/services/visibility/destroy' diff --git a/src/services/images/collections/read.ts b/src/services/images/collections/read.ts index 79d318417..83cb2dcf8 100644 --- a/src/services/images/collections/read.ts +++ b/src/services/images/collections/read.ts @@ -1,20 +1,20 @@ import '@pn-server-only' import { specialCollectionsSpecialVisibilityMap } from './ConfigVars' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import logger from '@/lib/logger' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import { readSpecialVisibility } from '@/services/visibility/read' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' -import { ImageMethods } from '@/services/images/methods' +import { imageOperations } from '@/services/images/operations' import type { SpecialCollection, ImageCollection, Image } from '@prisma/client' import type { ExpandedImageCollection, ImageCollectionCursor, ImageCollectionPageReturn -} from '@/services/images/collections/Types' +} from '@/services/images/collections/types' import type { VisibilityFilter } from '@/auth/getVisibilityFilter' -import type { ReadPageInput } from '@/lib/paging/Types' +import type { ReadPageInput } from '@/lib/paging/types' /** @@ -68,11 +68,10 @@ export async function readImageCollectionsPage( ...cursorPageingSelection(page) })) - const lensCamera = await ImageMethods.readSpecial.client(prisma).execute({ + const lensCamera = await imageOperations.readSpecial({ params: { special: 'DEFAULT_IMAGE_COLLECTION_COVER' }, - session: null //TODO: pass session }) const chooseCoverImage = (collection: { diff --git a/src/services/images/collections/Types.ts b/src/services/images/collections/types.ts similarity index 100% rename from src/services/images/collections/Types.ts rename to src/services/images/collections/types.ts diff --git a/src/services/images/collections/update.ts b/src/services/images/collections/update.ts index 74e371fb7..427e61234 100644 --- a/src/services/images/collections/update.ts +++ b/src/services/images/collections/update.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { updateImageCollectionValidation } from './validation' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import type { ImageCollection } from '@prisma/client' import type { UpdateImageCollectionTypes } from './validation' diff --git a/src/services/images/config.ts b/src/services/images/config.ts deleted file mode 100644 index c216c56ab..000000000 --- a/src/services/images/config.ts +++ /dev/null @@ -1,20 +0,0 @@ -export namespace ImageConfig { - export const maxFileSize = 100 * 1024 * 1024 // 100mb - - export const maxNumberInOneBatch = 10 - - export const sizes = { - small: 180, - medium: 360, - large: 720, - } - - export const avifConvertionOptions = { - quality: 50, - lossless: false, - speed: 8, // default is 5 - chromaSubsampling: '4:2:0', - } - - export const allowedExtUpload = ['png', 'jpg', 'jpeg', 'heic', 'avif', 'webp'] -} diff --git a/src/services/images/constants.ts b/src/services/images/constants.ts new file mode 100644 index 000000000..f39a18619 --- /dev/null +++ b/src/services/images/constants.ts @@ -0,0 +1,18 @@ +export const maxImageFileSize = 100 * 1024 * 1024 // 100mb + +export const maxImageCountInOneBatch = 10 + +export const imageSizes = { + small: 180, + medium: 360, + large: 720, +} + +export const avifConvertionOptions = { + quality: 50, + lossless: false, + speed: 8, // default is 5 + chromaSubsampling: '4:2:0', +} + +export const allowedExtensions = ['png', 'jpg', 'jpeg', 'heic', 'avif', 'webp'] diff --git a/src/services/images/methods.ts b/src/services/images/operations.ts similarity index 57% rename from src/services/images/methods.ts rename to src/services/images/operations.ts index cf50250b6..ff0563a56 100644 --- a/src/services/images/methods.ts +++ b/src/services/images/operations.ts @@ -1,19 +1,74 @@ import '@pn-server-only' import { readSpecialImageCollection } from './collections/read' -import { ImageConfig } from './config' -import { ImageAuthers } from './authers' -import { ImageSchemas } from './schemas' +import { imageAuth } from './auth' +import { imageSchemas } from './schemas' +import { allowedExtensions, avifConvertionOptions, imageSizes } from './constants' +import { defineOperation } from '@/services/serviceOperation' import { ServerError } from '@/services/error' import { createFile } from '@/services/store/createFile' import logger from '@/lib/logger' -import { ServiceMethod } from '@/services/ServiceMethod' import { readPageInputSchemaObject } from '@/lib/paging/schema' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' import sharp from 'sharp' import { SpecialImage } from '@prisma/client' import { z } from 'zod' -export namespace ImageMethods { +/** + * Creates one image from a file. + * @param file - The file to create the image from + * @param allowedExt - The allowed extensions for the file + * @param size - The size to resize the image to + * @returns + */ +async function createOneInStore(file: File, allowedExt: string[], size: number) { + const ret = await createFile(file, 'images', allowedExt, async (buffer) => await sharp(buffer).resize(size, size, { + fit: sharp.fit.inside, + withoutEnlargement: true + }).toBuffer()) + return ret +} + +/** + * WARNING: This function should only be used in extreme cases, Therefore it is not exported. + * Creates bad image this should really only happen in worst case scenario + * where the server has lost a image and needs to be replaced with a bad image. + * A bad image is an image that has no correct fsLocation attributes. + * @param name - the name of the image + * @param config - the config for the image (special) + */ +const createSourceless = defineOperation({ + authorizer: () => imageAuth.createSourcelessImage.dynamicFields({}), + paramsSchema: z.object({ + name: z.string(), + special: z.nativeEnum(SpecialImage), + }), + operation: async ({ prisma, params: { name, special } }) => { + const standardCollection = await readSpecialImageCollection('STANDARDIMAGES') + logger.warn(` + creating a bad image, Something has caused the server to lose a neccesary image. + It was replaced with an image model with no correct fsLocation atrributes. + `) + return await prisma.image.create({ + data: { + name, + special, + fsLocationOriginal: 'not_found', + fsLocationMediumSize: 'not_found', + fsLocationSmallSize: 'not_found', + fsLocationLargeSize: 'not_found', + extOriginal: 'jpg', + alt: 'not found', + collection: { + connect: { + id: standardCollection.id + } + } + }, + }) + } +}) + +export const imageOperations = { /** * Creates an image. * The method will resize the image to the correct sizes and save it to the store. @@ -21,23 +76,23 @@ export namespace ImageMethods { * All images are saved as avif (except the original). * @param collectionId - The id of the collection to add the image to */ - export const create = ServiceMethod({ - auther: () => ImageAuthers.create.dynamicFields({}), + create: defineOperation({ + authorizer: () => imageAuth.create.dynamicFields({}), paramsSchema: z.object({ collectionId: z.number(), }), - dataSchema: ImageSchemas.create, - method: async ({ prisma, params: { collectionId }, data }) => { + dataSchema: imageSchemas.create, + operation: async ({ prisma, params: { collectionId }, data }) => { const { file, ...meta } = data const buffer = Buffer.from(await file.arrayBuffer()) - const avifBuffer = await sharp(buffer).toFormat('avif').avif(ImageConfig.avifConvertionOptions).toBuffer() + const avifBuffer = await sharp(buffer).toFormat('avif').avif(avifConvertionOptions).toBuffer() const avifFile = new File([avifBuffer], 'image.avif', { type: 'image/avif' }) const uploadPromises = [ - createOneInStore(avifFile, ['avif'], ImageConfig.sizes.small), - createOneInStore(avifFile, ['avif'], ImageConfig.sizes.medium), - createOneInStore(avifFile, ['avif'], ImageConfig.sizes.large), - createFile(file, 'images', [...ImageConfig.allowedExtUpload]), + createOneInStore(avifFile, ['avif'], imageSizes.small), + createOneInStore(avifFile, ['avif'], imageSizes.medium), + createOneInStore(avifFile, ['avif'], imageSizes.large), + createFile(file, 'images', [...allowedExtensions]), ] const [smallSize, mediumSize, largeSize, original] = await Promise.all(uploadPromises) @@ -65,86 +120,43 @@ export namespace ImageMethods { } }) } - }) + }), /** * Creates many images from files. * The method will resize the images to the correct sizes and save them to the store. */ - export const createMany = ServiceMethod({ - auther: () => ImageAuthers.createMany.dynamicFields({}), + createMany: defineOperation({ + authorizer: () => imageAuth.createMany.dynamicFields({}), paramsSchema: z.object({ useFileName: z.boolean(), collectionId: z.number(), }), - dataSchema: ImageSchemas.createMany, - method: async ({ - prisma, + dataSchema: imageSchemas.createMany, + operation: async ({ params: { useFileName, collectionId }, data, - session, }) => { console.log('data', data) for (const file of data.files) { console.log('file', file) const name = useFileName ? file.name.split('.')[0] : undefined - await create.client(prisma).execute({ + await imageOperations.create({ params: { collectionId }, data: { file, name, alt: file.name.split('.')[0], licenseId: data.licenseId, credit: data.credit }, - session }) } } - }) - - /** - * WARNING: This function should only be used in extreme cases, Therefore it is not exported. - * Creates bad image this should really only happen in worst case scenario - * where the server has lost a image and needs to be replaced with a bad image. - * A bad image is an image that has no correct fsLocation attributes. - * @param name - the name of the image - * @param config - the config for the image (special) - */ - const createSourceless = ServiceMethod({ - auther: () => ImageAuthers.createSourcelessImage.dynamicFields({}), - paramsSchema: z.object({ - name: z.string(), - special: z.nativeEnum(SpecialImage), - }), - method: async ({ prisma, params: { name, special } }) => { - const standardCollection = await readSpecialImageCollection('STANDARDIMAGES') - logger.warn(` - creating a bad image, Something has caused the server to lose a neccesary image. - It was replaced with an image model with no correct fsLocation atrributes. - `) - return await prisma.image.create({ - data: { - name, - special, - fsLocationOriginal: 'not_found', - fsLocationMediumSize: 'not_found', - fsLocationSmallSize: 'not_found', - fsLocationLargeSize: 'not_found', - extOriginal: 'jpg', - alt: 'not found', - collection: { - connect: { - id: standardCollection.id - } - } - }, - }) - } - }) + }), /** * Reads an image by id. */ - export const read = ServiceMethod({ - auther: () => ImageAuthers.read.dynamicFields({}), + read: defineOperation({ + authorizer: () => imageAuth.read.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - method: async ({ prisma, params: { id } }) => { + operation: async ({ prisma, params: { id } }) => { const image = await prisma.image.findUnique({ where: { id, @@ -154,13 +166,13 @@ export namespace ImageMethods { if (!image) throw new ServerError('NOT FOUND', 'Image not found') return image } - }) + }), /** * Reads a page of images in a collection by collectionId. */ - export const readPage = ServiceMethod({ - auther: () => ImageAuthers.readPage.dynamicFields({}), + readPage: defineOperation({ + authorizer: () => imageAuth.readPage.dynamicFields({}), paramsSchema: readPageInputSchemaObject( z.number(), z.object({ @@ -170,7 +182,7 @@ export namespace ImageMethods { collectionId: z.number(), }), ), - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { const { collectionId } = params.paging.details return await prisma.image.findMany({ where: { @@ -179,18 +191,18 @@ export namespace ImageMethods { ...cursorPageingSelection(params.paging.page) }) } - }) + }), /** * Reads a special image by name (special atr.). * In the case that the special image does not exist (bad state) a "bad" image will be created. */ - export const readSpecial = ServiceMethod({ - auther: () => ImageAuthers.readSpecial.dynamicFields({}), + readSpecial: defineOperation({ + authorizer: () => imageAuth.readSpecial.dynamicFields({}), paramsSchema: z.object({ special: z.nativeEnum(SpecialImage) }), - method: async ({ prisma, params: { special }, session }) => { + operation: async ({ prisma, params: { special }, session }) => { const image = await prisma.image.findUnique({ where: { special, @@ -198,24 +210,24 @@ export namespace ImageMethods { }) if (!image) { - return await createSourceless.client(prisma).execute( + return await createSourceless( { params: { name: special, special }, session } ) } return image } - }) + }), /** * Update a image by id and data. Also can give the image a new license by data.licenseId. */ - export const update = ServiceMethod({ - auther: () => ImageAuthers.update.dynamicFields({}), + update: defineOperation({ + authorizer: () => imageAuth.update.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - dataSchema: ImageSchemas.update, - method: async ({ prisma, params: { id }, data: { licenseId, ...data } }) => { + dataSchema: imageSchemas.update, + operation: async ({ prisma, params: { id }, data: { licenseId, ...data } }) => { console.log('lic', licenseId) return await prisma.image.update({ where: { @@ -229,14 +241,14 @@ export namespace ImageMethods { } }) } - }) + }), - export const destroy = ServiceMethod({ - auther: () => ImageAuthers.destroy.dynamicFields({}), + destroy: defineOperation({ + authorizer: () => imageAuth.destroy.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - method: async ({ prisma, params: { id } }) => { + operation: async ({ prisma, params: { id } }) => { const image = await prisma.image.findUnique({ where: { id, @@ -253,20 +265,5 @@ export namespace ImageMethods { }, }) } - }) - - /** - * Creates one image from a file. - * @param file - The file to create the image from - * @param allowedExt - The allowed extensions for the file - * @param size - The size to resize the image to - * @returns - */ - export async function createOneInStore(file: File, allowedExt: string[], size: number) { - const ret = await createFile(file, 'images', allowedExt, async (buffer) => await sharp(buffer).resize(size, size, { - fit: sharp.fit.inside, - withoutEnlargement: true - }).toBuffer()) - return ret - } + }), } diff --git a/src/services/images/schemas.ts b/src/services/images/schemas.ts index 036963088..69579621a 100644 --- a/src/services/images/schemas.ts +++ b/src/services/images/schemas.ts @@ -1,58 +1,62 @@ -import { ImageConfig } from '@/services/images/config' +import { allowedExtensions, maxImageCountInOneBatch, maxImageFileSize } from './constants' import { z } from 'zod' import { zfd } from 'zod-form-data' -export namespace ImageSchemas { - const maxFileSizeMb = Math.round(ImageConfig.maxFileSize / 1024 / 1024) - export const fileSchema = z.instanceof(File).refine( - file => file.size < ImageConfig.maxFileSize, `File size must be less than ${maxFileSizeMb}mb` +const maxFileSizeMb = Math.round(maxImageFileSize / 1024 / 1024) + +export const imageFileSchema = z.instanceof(File).refine( + file => file.size < maxImageFileSize, `File size must be less than ${maxFileSizeMb}mb` +).refine( + file => allowedExtensions.includes(file.type.split('/')[1]), + `File type must be one of ${allowedExtensions.join(', ')}` +) + +const baseSchema = z.object({ + file: imageFileSchema, + name: z.string().max(50, 'max length in 50').min(2, 'min length is 2').optional(), + alt: z.string().max(100, 'max length in 50').min(2, 'min length is 2'), + files: zfd.repeatable(z.array(z.instanceof(File)).refine( + files => files.every(file => file.size < maxImageFileSize), + `All file size must be less than ${maxFileSizeMb}mb` + )).refine( + files => files.every(file => allowedExtensions.includes(file.type.split('/')[1])), + `File type must be one of ${allowedExtensions.join(', ')}` ).refine( - file => ImageConfig.allowedExtUpload.includes(file.type.split('/')[1]), - `File type must be one of ${ImageConfig.allowedExtUpload.join(', ')}` - ) - const fields = z.object({ - file: fileSchema, - name: z.string().max(50, 'max length in 50').min(2, 'min length is 2').optional(), - alt: z.string().max(100, 'max length in 50').min(2, 'min length is 2'), - files: zfd.repeatable(z.array(z.instanceof(File)).refine( - files => files.every(file => file.size < ImageConfig.maxFileSize), - `All file size must be less than ${maxFileSizeMb}mb` - )).refine( - files => files.every(file => ImageConfig.allowedExtUpload.includes(file.type.split('/')[1])), - `File type must be one of ${ImageConfig.allowedExtUpload.join(', ')}` - ).refine( - files => files.length <= ImageConfig.maxNumberInOneBatch && files.length > 0, - `Du kan bare laste opp mellom 1 og ${ImageConfig.maxNumberInOneBatch} bilder av gangen` - ), - licenseId: z.union([ - z.string().optional().nullable(), - z.coerce.number().optional().or(z.literal('NULL')), - ]).transform( - value => { - if (typeof value === 'string' && value === 'NULL') return null - if (typeof value === 'string') return parseInt(value, 10) - return value - } - ), - credit: z.string().optional(), - }) + files => files.length <= maxImageCountInOneBatch && files.length > 0, + `Du kan bare laste opp mellom 1 og ${maxImageCountInOneBatch} bilder av gangen` + ), + licenseId: z.union([ + z.string().optional().nullable(), + z.coerce.number().optional().or(z.literal('NULL')), + ]).transform( + value => { + if (typeof value === 'string' && value === 'NULL') return null + if (typeof value === 'string') return parseInt(value, 10) + return value + } + ), + credit: z.string().optional(), +}) - export const create = fields.pick({ +export const imageSchemas = { + create: baseSchema.pick({ name: true, alt: true, file: true, licenseId: true, credit: true, - }) - export const createMany = fields.pick({ + }), + + createMany: baseSchema.pick({ files: true, licenseId: true, credit: true, - }) - export const update = fields.partial().pick({ + }), + + update: baseSchema.partial().pick({ name: true, alt: true, credit: true, licenseId: true, - }) + }), } diff --git a/src/services/images/Types.ts b/src/services/images/types.ts similarity index 100% rename from src/services/images/Types.ts rename to src/services/images/types.ts diff --git a/src/services/licenses/actions.ts b/src/services/licenses/actions.ts new file mode 100644 index 000000000..1411255fe --- /dev/null +++ b/src/services/licenses/actions.ts @@ -0,0 +1,12 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { licenseOperations } from '@/services/licenses/operations' + +export const createLicenseAction = makeAction(licenseOperations.create) + +export const destroyLicenseAction = makeAction(licenseOperations.destroy) + +export const readAllLicensesAction = makeAction(licenseOperations.readAll) + +export const updateLicenseAction = makeAction(licenseOperations.update) diff --git a/src/services/licenses/auth.ts b/src/services/licenses/auth.ts new file mode 100644 index 000000000..9f81be925 --- /dev/null +++ b/src/services/licenses/auth.ts @@ -0,0 +1,8 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const licenseAuth = { + create: RequirePermission.staticFields({ permission: 'LICENSE_ADMIN' }), + read: RequirePermission.staticFields({ permission: 'LICENSE_ADMIN' }), + update: RequirePermission.staticFields({ permission: 'LICENSE_ADMIN' }), + destroy: RequirePermission.staticFields({ permission: 'LICENSE_ADMIN' }), +} diff --git a/src/services/licenses/authers.ts b/src/services/licenses/authers.ts deleted file mode 100644 index 6afc4cdd0..000000000 --- a/src/services/licenses/authers.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace LicenseAuthers { - export const create = RequirePermission.staticFields({ permission: 'LICENSE_ADMIN' }) - export const read = RequirePermission.staticFields({ permission: 'LICENSE_ADMIN' }) - export const update = RequirePermission.staticFields({ permission: 'LICENSE_ADMIN' }) - export const destroy = RequirePermission.staticFields({ permission: 'LICENSE_ADMIN' }) -} diff --git a/src/services/licenses/methods.ts b/src/services/licenses/operations.ts similarity index 54% rename from src/services/licenses/methods.ts rename to src/services/licenses/operations.ts index 2eb48af82..77dbc300c 100644 --- a/src/services/licenses/methods.ts +++ b/src/services/licenses/operations.ts @@ -1,28 +1,28 @@ import '@pn-server-only' -import { LicenseSchemas } from './schemas' -import { LicenseAuthers } from './authers' +import { licenseSchemas } from './schemas' +import { licenseAuth } from './auth' import { ServerError } from '@/services/error' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import { z } from 'zod' -export namespace LicenseMethods { - export const create = ServiceMethod({ - auther: () => LicenseAuthers.create.dynamicFields({}), - dataSchema: LicenseSchemas.create, - method: async ({ prisma, data }) => await prisma.license.create({ +export const licenseOperations = { + create: defineOperation({ + authorizer: () => licenseAuth.create.dynamicFields({}), + dataSchema: licenseSchemas.create, + operation: async ({ prisma, data }) => await prisma.license.create({ data, }), - }) - export const readAll = ServiceMethod({ - auther: () => LicenseAuthers.destroy.dynamicFields({}), - method: async ({ prisma }) => await prisma.license.findMany() - }) - export const destroy = ServiceMethod({ + }), + readAll: defineOperation({ + authorizer: () => licenseAuth.destroy.dynamicFields({}), + operation: async ({ prisma }) => await prisma.license.findMany() + }), + destroy: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - auther: () => LicenseAuthers.destroy.dynamicFields({}), - method: async ({ prisma, params }) => { + authorizer: () => licenseAuth.destroy.dynamicFields({}), + operation: async ({ prisma, params }) => { const { name: licenseName } = await prisma.license.findUniqueOrThrow({ where: { id: params.id }, select: { name: true } @@ -43,14 +43,14 @@ export namespace LicenseMethods { await prisma.license.delete({ where: { id: params.id } }) } - }) - export const update = ServiceMethod({ + }), + update: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - dataSchema: LicenseSchemas.update, - auther: () => LicenseAuthers.update.dynamicFields({}), - method: async ({ prisma, params, data }) => { + dataSchema: licenseSchemas.update, + authorizer: () => licenseAuth.update.dynamicFields({}), + operation: async ({ prisma, params, data }) => { await prisma.license.update({ where: { id: params.id @@ -58,5 +58,5 @@ export namespace LicenseMethods { data }) } - }) + }), } diff --git a/src/services/licenses/schemas.ts b/src/services/licenses/schemas.ts index 917aa9ac8..bfbb1d32f 100644 --- a/src/services/licenses/schemas.ts +++ b/src/services/licenses/schemas.ts @@ -1,22 +1,23 @@ import { z } from 'zod' -export namespace LicenseSchemas { - const fields = z.object({ - name: z.string().min( - 5, 'Navn må være minst 5 tegn langt' - ).max( - 50, 'Navn kan maks være 50 tegn langt' - ), - link: z.string().min( - 1, 'Link må være minst 1 tegn langt' - ) - }) - export const create = fields.pick({ +const baseSchema = z.object({ + name: z.string().min( + 5, 'Navn må være minst 5 tegn langt' + ).max( + 50, 'Navn kan maks være 50 tegn langt' + ), + link: z.string().min( + 1, 'Link må være minst 1 tegn langt' + ) +}) + +export const licenseSchemas = { + create: baseSchema.pick({ name: true, link: true, - }) - export const update = fields.partial().pick({ + }), + update: baseSchema.partial().pick({ name: true, link: true, - }) + }), } diff --git a/src/services/lockers/actions.ts b/src/services/lockers/actions.ts new file mode 100644 index 000000000..08f24c201 --- /dev/null +++ b/src/services/lockers/actions.ts @@ -0,0 +1,16 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { lockerLocationOperations } from '@/services/lockers/locations/operations' +import { lockerOperations } from '@/services/lockers/operations' +import { lockerReservationOperations } from '@/services/lockers/reservations/operations' + +export const createLockerLocationAction = makeAction(lockerLocationOperations.create) +export const readAllLockerLocationsAction = makeAction(lockerLocationOperations.readAll) + +export const createLockerAction = makeAction(lockerOperations.create) +export const readLockerAction = makeAction(lockerOperations.read) +export const readLockerPageAction = makeAction(lockerOperations.readPage) + +export const updateLockerReservationAction = makeAction(lockerReservationOperations.update) +export const createLockerReservationAction = makeAction(lockerReservationOperations.create) diff --git a/src/services/lockers/auth.ts b/src/services/lockers/auth.ts new file mode 100644 index 000000000..e37160731 --- /dev/null +++ b/src/services/lockers/auth.ts @@ -0,0 +1,7 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const lockerAuth = { + create: RequirePermission.staticFields({ permission: 'LOCKER_ADMIN' }), + read: RequirePermission.staticFields({ permission: 'LOCKER_USE' }), + readPage: RequirePermission.staticFields({ permission: 'LOCKER_USE' }), +} diff --git a/src/services/lockers/authers.ts b/src/services/lockers/authers.ts deleted file mode 100644 index 07f61af08..000000000 --- a/src/services/lockers/authers.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace LockerAuthers { - export const create = RequirePermission.staticFields({ permission: 'LOCKER_ADMIN' }) - export const read = RequirePermission.staticFields({ permission: 'LOCKER_USE' }) - export const readPage = RequirePermission.staticFields({ permission: 'LOCKER_USE' }) -} diff --git a/src/services/lockers/locations/auth.ts b/src/services/lockers/locations/auth.ts new file mode 100644 index 000000000..360773e0d --- /dev/null +++ b/src/services/lockers/locations/auth.ts @@ -0,0 +1,7 @@ +import { RequireNothing } from '@/auth/auther/RequireNothing' +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const lockerLocationAuth = { + create: RequirePermission.staticFields({ permission: 'LOCKER_ADMIN' }), + readAll: RequireNothing.staticFields({}), +} diff --git a/src/services/lockers/locations/authers.ts b/src/services/lockers/locations/authers.ts deleted file mode 100644 index 78309ac1d..000000000 --- a/src/services/lockers/locations/authers.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { RequireNothing } from '@/auth/auther/RequireNothing' -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace LockerLocationAuthers { - export const create = RequirePermission.staticFields({ permission: 'LOCKER_ADMIN' }) - export const readAll = RequireNothing.staticFields({}) -} diff --git a/src/services/lockers/locations/config.ts b/src/services/lockers/locations/constants.ts similarity index 100% rename from src/services/lockers/locations/config.ts rename to src/services/lockers/locations/constants.ts diff --git a/src/services/lockers/locations/methods.ts b/src/services/lockers/locations/methods.ts deleted file mode 100644 index 8932fbac8..000000000 --- a/src/services/lockers/locations/methods.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { LockerLocationAuthers } from './authers' -import { LockersSchemas } from '@/services/lockers/schemas' -import { ServiceMethod } from '@/services/ServiceMethod' - -export namespace LockerLocationMethods { - /** - * Creates a new locker location. - * - * @param data - Building and floor of the locker location. - * - * @returns The newly created locker location object. - */ - export const create = ServiceMethod({ - auther: () => LockerLocationAuthers.create.dynamicFields({}), - dataSchema: LockersSchemas.createLocation, - method: async ({ prisma, data }) => prisma.lockerLocation.create({ - data: { - building: data.building, - floor: data.floor - } - }) - }) - - /** - * Reads all locker locations. - * - * @returns All locker location objects. - */ - export const readAll = ServiceMethod({ - auther: () => LockerLocationAuthers.readAll.dynamicFields({}), - method: async ({ prisma }) => prisma.lockerLocation.findMany() - }) -} diff --git a/src/services/lockers/locations/operations.ts b/src/services/lockers/locations/operations.ts new file mode 100644 index 000000000..ddd637b3e --- /dev/null +++ b/src/services/lockers/locations/operations.ts @@ -0,0 +1,33 @@ +import { lockerLocationAuth } from './auth' +import { lockersSchemas } from '@/services/lockers/schemas' +import { defineOperation } from '@/services/serviceOperation' + +export const lockerLocationOperations = { + /** + * Creates a new locker location. + * + * @param data - Building and floor of the locker location. + * + * @returns The newly created locker location object. + */ + create: defineOperation({ + authorizer: () => lockerLocationAuth.create.dynamicFields({}), + dataSchema: lockersSchemas.createLocation, + operation: async ({ prisma, data }) => prisma.lockerLocation.create({ + data: { + building: data.building, + floor: data.floor + } + }) + }), + + /** + * Reads all locker locations. + * + * @returns All locker location objects. + */ + readAll: defineOperation({ + authorizer: () => lockerLocationAuth.readAll.dynamicFields({}), + operation: async ({ prisma }) => prisma.lockerLocation.findMany() + }), +} diff --git a/src/services/lockers/methods.ts b/src/services/lockers/operations.ts similarity index 76% rename from src/services/lockers/methods.ts rename to src/services/lockers/operations.ts index 01bf5dd37..8fe7adc13 100644 --- a/src/services/lockers/methods.ts +++ b/src/services/lockers/operations.ts @@ -1,14 +1,14 @@ import '@pn-server-only' -import { lockerReservationIncluder } from './reservations/config' -import { LockerAuthers } from './authers' -import { LockersSchemas } from './schemas' -import { ServiceMethod } from '@/services/ServiceMethod' +import { lockerReservationIncluder } from './reservations/constants' +import { lockersSchemas } from './schemas' +import { lockerAuth } from './auth' +import { defineOperation } from '@/services/serviceOperation' import { ServerError } from '@/services/error' import { readPageInputSchemaObject } from '@/lib/paging/schema' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' import { z } from 'zod' import type { Prisma } from '@prisma/client' -import type { LockerWithReservation } from '@/services/lockers/Types' +import type { LockerWithReservation } from '@/services/lockers/types' export async function updateLockerReservationIfExpired(prisma: Prisma.TransactionClient, locker: LockerWithReservation) { if (!locker.LockerReservation.length) return @@ -32,7 +32,7 @@ export async function updateLockerReservationIfExpired(prisma: Prisma.Transactio locker.LockerReservation = [] } } -export namespace LockerMethods { +export const lockerOperations = { /** * Creates a new locker. * @@ -40,17 +40,16 @@ export namespace LockerMethods { * * @returns The newly created locker object. */ - export const create = ServiceMethod({ - auther: () => LockerAuthers.create.dynamicFields({}), - dataSchema: LockersSchemas.create, - method: async ({ prisma, data }) => { + create: defineOperation({ + authorizer: () => lockerAuth.create.dynamicFields({}), + dataSchema: lockersSchemas.create, + operation: async ({ prisma, data }) => { console.log(data) return await prisma.locker.create({ data, }) } - - }) + }), /** * Reads a locker. Expired locker reservations are updated when reading. @@ -59,12 +58,12 @@ export namespace LockerMethods { * * @returns The locker object. */ - export const read = ServiceMethod({ - auther: () => LockerAuthers.read.dynamicFields({}), + read: defineOperation({ + authorizer: () => lockerAuth.read.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - method: async ({ prisma, params: { id } }) => { + operation: async ({ prisma, params: { id } }) => { const locker = await prisma.locker.findUniqueOrThrow({ where: { id, @@ -76,7 +75,7 @@ export namespace LockerMethods { return locker } - }) + }), /** * Reads a page of lockers. Expired locker reservations are updated when reading. @@ -85,8 +84,8 @@ export namespace LockerMethods { * * @returns A list of locker objects. */ - export const readPage = ServiceMethod({ - auther: () => LockerAuthers.readPage.dynamicFields({}), + readPage: defineOperation({ + authorizer: () => lockerAuth.readPage.dynamicFields({}), paramsSchema: readPageInputSchemaObject( z.number(), z.object({ @@ -94,7 +93,7 @@ export namespace LockerMethods { }), z.any(), ), - method: async ({ prisma, params }) => { + operation: async ({ prisma, params }) => { const lockers = await prisma.locker.findMany({ ...cursorPageingSelection(params.paging.page), orderBy: { @@ -107,5 +106,5 @@ export namespace LockerMethods { return lockers } - }) + }), } diff --git a/src/services/lockers/reservations/auth.ts b/src/services/lockers/reservations/auth.ts new file mode 100644 index 000000000..b1617e547 --- /dev/null +++ b/src/services/lockers/reservations/auth.ts @@ -0,0 +1,7 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const lockerReservationAuth = { + create: RequirePermission.staticFields({ permission: 'LOCKER_USE' }), + read: RequirePermission.staticFields({ permission: 'LOCKER_USE' }), + update: RequirePermission.staticFields({ permission: 'LOCKER_USE' }), +} diff --git a/src/services/lockers/reservations/authers.ts b/src/services/lockers/reservations/authers.ts deleted file mode 100644 index 77068334a..000000000 --- a/src/services/lockers/reservations/authers.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace LockerReservationAuthers { - export const create = RequirePermission.staticFields({ permission: 'LOCKER_USE' }) - export const read = RequirePermission.staticFields({ permission: 'LOCKER_USE' }) - export const update = RequirePermission.staticFields({ permission: 'LOCKER_USE' }) -} diff --git a/src/services/lockers/reservations/config.ts b/src/services/lockers/reservations/constants.ts similarity index 100% rename from src/services/lockers/reservations/config.ts rename to src/services/lockers/reservations/constants.ts diff --git a/src/services/lockers/reservations/methods.ts b/src/services/lockers/reservations/operations.ts similarity index 75% rename from src/services/lockers/reservations/methods.ts rename to src/services/lockers/reservations/operations.ts index 7623a254a..b6cc28723 100644 --- a/src/services/lockers/reservations/methods.ts +++ b/src/services/lockers/reservations/operations.ts @@ -1,13 +1,13 @@ import '@pn-server-only' -import { LockerReservationAuthers } from './authers' -import { LockerReservationSchemas } from './schemas' -import { ServiceMethod } from '@/services/ServiceMethod' +import { lockerReservationAuth } from './auth' +import { lockerReservationSchemas } from './schemas' +import { defineOperation } from '@/services/serviceOperation' import { Smorekopp } from '@/services/error' -import { GroupMethods } from '@/services/groups/methods' +import { groupOperations } from '@/services/groups/operations' import { z } from 'zod' -export namespace LockerReservationMethods { +export const lockerReservationOperations = { /** * Creates a new locker reservation for a given user and locker. * @@ -16,18 +16,17 @@ export namespace LockerReservationMethods { * * @returns The newly created reservation object. */ - export const create = ServiceMethod({ - auther: () => LockerReservationAuthers.create.dynamicFields({}), + create: defineOperation({ + authorizer: () => lockerReservationAuth.create.dynamicFields({}), paramsSchema: z.object({ lockerId: z.number(), }), - dataSchema: LockerReservationSchemas.create, - method: async ({ prisma, session, data, params }) => { + dataSchema: lockerReservationSchemas.create, + operation: async ({ prisma, session, data, params }) => { // TODO: Use authers for authing in stead of this // Verify that user is in group if (data.groupId) { - const groupUsers = await GroupMethods.readUsersOfGroups.newClient().execute({ - session: null, + const groupUsers = await groupOperations.readUsersOfGroups({ bypassAuth: true, params: { groups: [{ groupId: data.groupId, admin: false }] @@ -54,7 +53,7 @@ export namespace LockerReservationMethods { } }) }, - }) + }), /** * Updates an existing locker reservation. @@ -64,17 +63,17 @@ export namespace LockerReservationMethods { * * @returns The updated reservation object. */ - export const read = ServiceMethod({ - auther: () => LockerReservationAuthers.read.dynamicFields({}), + read: defineOperation({ + authorizer: () => lockerReservationAuth.read.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - method: ({ prisma, params: { id } }) => prisma.lockerReservation.findUniqueOrThrow({ + operation: ({ prisma, params: { id } }) => prisma.lockerReservation.findUniqueOrThrow({ where: { id, }, }) - }) + }), /** * Updates an existing locker reservation. @@ -84,13 +83,13 @@ export namespace LockerReservationMethods { * * @returns The updated reservation object. */ - export const update = ServiceMethod({ - auther: () => LockerReservationAuthers.update.dynamicFields({}), + update: defineOperation({ + authorizer: () => lockerReservationAuth.update.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), - dataSchema: LockerReservationSchemas.update, - method: async ({ prisma, session, data, params: { id } }) => { + dataSchema: lockerReservationSchemas.update, + operation: async ({ prisma, session, data, params: { id } }) => { // TODO: Use authers for authing in stead of this // Verify that the user updating is the creator of the reservation const reservation = await prisma.lockerReservation.findUniqueOrThrow({ @@ -108,8 +107,7 @@ export namespace LockerReservationMethods { // Verify that user is in group if (data.groupId) { - const groupUsers = await GroupMethods.readUsersOfGroups.client(prisma).execute({ - session, + const groupUsers = await groupOperations.readUsersOfGroups({ bypassAuth: true, params: { groups: [{ groupId: data.groupId, admin: false }] @@ -133,6 +131,6 @@ export namespace LockerReservationMethods { } }) }, - }) + }), } diff --git a/src/services/lockers/reservations/schemas.ts b/src/services/lockers/reservations/schemas.ts index 391621ed7..68daee3ae 100644 --- a/src/services/lockers/reservations/schemas.ts +++ b/src/services/lockers/reservations/schemas.ts @@ -1,21 +1,16 @@ import { z } from 'zod' -export namespace LockerReservationSchemas { - const fields = z.object({ - groupId: z.string(), - indefinateDate: z.string().optional(), - endDate: z.string().optional() - }) +const baseSchema = z.object({ + groupId: z.string(), + indefinateDate: z.string().optional(), + endDate: z.string().optional() +}).transform(data => ({ + groupId: data.groupId === 'null' ? null : parseInt(data.groupId, 10), + indefinateDate: data.indefinateDate ? data.indefinateDate === 'on' : false, + endDate: data.endDate ? (new Date(data.endDate)) : null +})) - export const create = fields.pick({ - groupId: true, - indefinateDate: true, - endDate: true, - }).transform(data => ({ - groupId: data.groupId === 'null' ? null : parseInt(data.groupId, 10), - indefinateDate: data.indefinateDate ? data.indefinateDate === 'on' : false, - endDate: data.endDate ? (new Date(data.endDate)) : null - })) - - export const update = create +export const lockerReservationSchemas = { + create: baseSchema, + update: baseSchema, } diff --git a/src/services/lockers/schemas.ts b/src/services/lockers/schemas.ts index d85ed054b..5de96929d 100644 --- a/src/services/lockers/schemas.ts +++ b/src/services/lockers/schemas.ts @@ -1,19 +1,20 @@ import { z } from 'zod' -export namespace LockersSchemas { - const fields = z.object({ - building: z.string(), - floor: z.coerce.number(), - id: z.coerce.number() - }) - export const create = fields.pick({ +const baseSchema = z.object({ + building: z.string(), + floor: z.coerce.number(), + id: z.coerce.number() +}) + +export const lockersSchemas = { + create: baseSchema.pick({ building: true, floor: true, id: true - }) + }), - export const createLocation = fields.pick({ + createLocation: baseSchema.pick({ building: true, floor: true, - }) + }), } diff --git a/src/services/lockers/Types.ts b/src/services/lockers/types.ts similarity index 96% rename from src/services/lockers/Types.ts rename to src/services/lockers/types.ts index b89f1e8a6..0069548ca 100644 --- a/src/services/lockers/Types.ts +++ b/src/services/lockers/types.ts @@ -1,4 +1,4 @@ -import type { lockerReservationIncluder } from './reservations/config' +import type { lockerReservationIncluder } from './reservations/constants' import type { Prisma } from '@prisma/client' export type LockerWithReservation = Prisma.LockerGetPayload<{ diff --git a/src/services/mail/ConfigVars.ts b/src/services/mail/ConfigVars.ts index 5439c5c9b..d0ede451f 100644 --- a/src/services/mail/ConfigVars.ts +++ b/src/services/mail/ConfigVars.ts @@ -1,11 +1,13 @@ +import { isBuildPhase } from '@/lib/isBuildPhase' import { ServerError } from '@/services/error' -if (!process.env.DOMAIN || !process.env.MAIL_DOMAIN) { - throw new ServerError('INVALID CONFIGURATION', 'The environment variables DOMAIN and MAIL_DOMAIN must be set') -} - -export const validMailAdressDomains = [ - process.env.DOMAIN, - process.env.MAIL_DOMAIN -] as const +export const validMailAdressDomains = isBuildPhase() ? ['omega.ntnu.no'] : (() => { + if (!process.env.DOMAIN || !process.env.MAIL_DOMAIN) { + throw new ServerError('INVALID CONFIGURATION', 'The environment variables DOMAIN and MAIL_DOMAIN must be set') + } + return [ + process.env.DOMAIN, + process.env.MAIL_DOMAIN + ] as const +})() diff --git a/src/services/mail/actions.ts b/src/services/mail/actions.ts new file mode 100644 index 000000000..a3a7a7432 --- /dev/null +++ b/src/services/mail/actions.ts @@ -0,0 +1,187 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { readMailAliases } from '@/services/mail/alias/read' +import { + createAliasMailingListRelation, + createMailingListExternalRelation, + createMailingListGroupRelation, + createMailingListUserRelation } from '@/services/mail/create' +import { + destroyAliasMailingListRelation, + destroyMailingListExternalRelation, + destroyMailingListGroupRelation, + destroyMailingListUserRelation } from '@/services/mail/destroy' +import { readMailingLists } from '@/services/mail/list/read' +import { readMailAddressExternal } from '@/services/mail/mailAddressExternal/read' +import { readMailTraversal } from '@/services/mail/read' +import { + createAliasMailingListValidation, + createMailingListExternalValidation, + createMailingListGroupValidation, + createMailingListUserValidation } from '@/services/mail/validation' +import type { MailListTypes } from '@/services/mail/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateAliasMailingListType, + CreateMailingListExternalType, + CreateMailingListGroupType, + CreateMailingListUserType } from '@/services/mail/validation' +import type { UserFiltered } from '@/services/users/types' +import type { MailAliasMailingList, + MailingListGroup, + MailingListMailAddressExternal, + MailingListUser, MailAddressExternal, MailAlias, MailingList } from '@prisma/client' + +export async function createAliasMailingListRelationAction(formdata: FormData): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_ALIAS_CREATE']] + }) + if (!authorized) return createActionError(status) + + const parse = createAliasMailingListValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => createAliasMailingListRelation(parse.data)) +} + +export async function createMailingListExternalRelationAction(formdata: FormData): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_EXTERNAL_ADDRESS_CREATE']] + }) + if (!authorized) return createActionError(status) + + const parse = createMailingListExternalValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => createMailingListExternalRelation(parse.data)) +} + +export async function createMailingListUserRelationAction(formdata: FormData): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_USER_CREATE']] + }) + if (!authorized) return createActionError(status) + + const parse = createMailingListUserValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => createMailingListUserRelation(parse.data)) +} + +export async function createMailingListGroupRelationAction(formdata: FormData): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_GROUP_CREATE']] + }) + if (!authorized) return createActionError(status) + + const parse = createMailingListGroupValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => createMailingListGroupRelation(parse.data)) +} + +export async function destroyAliasMailingListRelationAction(formdata: FormData | CreateAliasMailingListType['Type']): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_ALIAS_DESTROY']] + }) + if (!authorized) return createActionError(status) + + const parse = createAliasMailingListValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => destroyAliasMailingListRelation(parse.data)) +} + +export async function destroyMailingListExternalRelationAction(formdata: FormData | CreateMailingListExternalType['Type']): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_EXTERNAL_ADDRESS_DESTROY']] + }) + if (!authorized) return createActionError(status) + + const parse = createMailingListExternalValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => destroyMailingListExternalRelation(parse.data)) +} + +export async function destroyMailingListUserRelationAction(formdata: FormData | CreateMailingListUserType['Type']): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_USER_DESTROY']], + }) + if (!authorized) return createActionError(status) + + const parse = createMailingListUserValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => destroyMailingListUserRelation(parse.data)) +} + +export async function destroyMailingListGroupRelationAction(formdata: FormData | CreateMailingListGroupType['Type']): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_GROUP_DESTROY']] + }) + if (!authorized) return createActionError(status) + + const parse = createMailingListGroupValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => destroyMailingListGroupRelation(parse.data)) +} + +export async function readMailFlowAction(filter: MailListTypes, id: number) { + const { authorized, status } = await getUser({ + requiredPermissions: [ + ['MAILINGLIST_READ'], + ['MAILALIAS_READ'], + ['MAILADDRESS_EXTERNAL_READ'], + ['GROUP_READ'], + ], + }) + + if (!authorized) return createActionError(status) + + return safeServerCall(() => readMailTraversal({ + filter, + id, + })) +} + +export async function readMailOptions(): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [ + ['MAILINGLIST_READ'], + ['MAILALIAS_READ'], + ['MAILADDRESS_EXTERNAL_READ'], + ], + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(async () => { + const results = await Promise.all([ + readMailAliases(), + readMailingLists(), + readMailAddressExternal(), + ]) + + return { + alias: results[0], + mailingList: results[1], + mailaddressExternal: results[2], + users: [] + } + }) +} diff --git a/src/services/mail/alias/actions.ts b/src/services/mail/alias/actions.ts new file mode 100644 index 000000000..a06cc7ff5 --- /dev/null +++ b/src/services/mail/alias/actions.ts @@ -0,0 +1,61 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createMailAlias } from '@/services/mail/alias/create' +import { destroyMailAlias } from '@/services/mail/alias/destroy' +import { readMailAliases } from '@/services/mail/alias/read' +import { updateMailAlias } from '@/services/mail/alias/update' +import { + createMailAliasValidation, + destoryMailAliasValidation, + updateMailAliasValidation, +} from '@/services/mail/alias/validation' +import type { ActionReturn } from '@/services/actionTypes' +import type { MailAlias } from '@prisma/client' + +export async function createMailAliasAction(rawdata: FormData): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILALIAS_CREATE']] + }) + if (!authorized) return createActionError(status) + + const parse = createMailAliasValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => createMailAlias(parse.data)) +} + +export async function destroyMailAliasAction(id: number): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILALIAS_DESTORY']], + }) + if (!authorized) return createActionError(status) + + const parse = destoryMailAliasValidation.typeValidate({ id }) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => destroyMailAlias(parse.data.id)) +} + +export async function readMailAliasesAction(): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILALIAS_READ']] + }) + if (!authorized) return createActionError(status) + + return safeServerCall(() => readMailAliases()) +} + +export async function updateMailAliasAction(rawdata: FormData): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILALIAS_UPDATE']], + }) + if (!authorized) return createActionError(status) + + const parsed = updateMailAliasValidation.typeValidate(rawdata) + if (!parsed.success) return createZodActionError(parsed) + + return await safeServerCall(() => updateMailAlias(parsed.data)) +} diff --git a/src/services/mail/alias/create.ts b/src/services/mail/alias/create.ts index a18b752f4..b957b7fc1 100644 --- a/src/services/mail/alias/create.ts +++ b/src/services/mail/alias/create.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { createMailAliasValidation, type CreateMailAliasTypes } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailAlias } from '@prisma/client' export async function createMailAlias(rawdata: CreateMailAliasTypes['Detailed']): diff --git a/src/services/mail/alias/destroy.ts b/src/services/mail/alias/destroy.ts index c54088692..8e5f6b2b2 100644 --- a/src/services/mail/alias/destroy.ts +++ b/src/services/mail/alias/destroy.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { destoryMailAliasValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { ServerError } from '@/services/error' import type { MailAlias } from '@prisma/client' diff --git a/src/services/mail/alias/read.ts b/src/services/mail/alias/read.ts index 2b3d89f5a..8ce0b35b1 100644 --- a/src/services/mail/alias/read.ts +++ b/src/services/mail/alias/read.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailAlias } from '@prisma/client' diff --git a/src/services/mail/alias/update.ts b/src/services/mail/alias/update.ts index eb7df0a5f..7da3566c9 100644 --- a/src/services/mail/alias/update.ts +++ b/src/services/mail/alias/update.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { updateMailAliasValidation, type UpdateMailAliasTypes } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailAlias } from '@prisma/client' export async function updateMailAlias(rawdata: UpdateMailAliasTypes['Detailed']): Promise { diff --git a/src/services/mail/create.ts b/src/services/mail/create.ts index 08fdcb224..f768e9549 100644 --- a/src/services/mail/create.ts +++ b/src/services/mail/create.ts @@ -6,7 +6,7 @@ import { createMailingListUserValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailAliasMailingList, MailingListGroup, diff --git a/src/services/mail/destroy.ts b/src/services/mail/destroy.ts index 02ae53465..3e64bfb7b 100644 --- a/src/services/mail/destroy.ts +++ b/src/services/mail/destroy.ts @@ -6,7 +6,7 @@ import { createMailingListUserValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailAliasMailingList, MailingListGroup, diff --git a/src/services/mail/list/actions.ts b/src/services/mail/list/actions.ts new file mode 100644 index 000000000..3a142809a --- /dev/null +++ b/src/services/mail/list/actions.ts @@ -0,0 +1,52 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createMailingList } from '@/services/mail/list/create' +import { destroyMailingList } from '@/services/mail/list/destroy' +import { updateMailingList } from '@/services/mail/list/update' +import { + createMailingListValidation, + readMailingListValidation, + updateMailingListValidation +} from '@/services/mail/list/validation' +import type { ActionReturn } from '@/services/actionTypes' +import type { MailingList } from '@prisma/client' + +export async function createMailingListAction(rawdata: FormData): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_CREATE']] + }) + if (!authorized) return createActionError(status) + + const parse = createMailingListValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => createMailingList(parse.data)) +} + +export async function destroyMailingListAction(id: number): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_DESTROY']], + }) + if (!authorized) return createActionError(status) + + const parse = readMailingListValidation.typeValidate({ id }) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => destroyMailingList(parse.data.id)) +} + +export async function updateMailingListAction(data: FormData): +Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILINGLIST_UPDATE']], + }) + if (!authorized) return createActionError(status) + + const parse = updateMailingListValidation.typeValidate(data) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => updateMailingList(parse.data)) +} diff --git a/src/services/mail/list/create.ts b/src/services/mail/list/create.ts index dd913aa03..d13b8e8e7 100644 --- a/src/services/mail/list/create.ts +++ b/src/services/mail/list/create.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { createMailingListValidation, type CreateMailingListTypes } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailingList } from '@prisma/client' export async function createMailingList(rawdata: CreateMailingListTypes['Detailed']): diff --git a/src/services/mail/list/destroy.ts b/src/services/mail/list/destroy.ts index 2f2a60a86..fd3ae6685 100644 --- a/src/services/mail/list/destroy.ts +++ b/src/services/mail/list/destroy.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { readMailingListValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailingList } from '@prisma/client' diff --git a/src/services/mail/list/read.ts b/src/services/mail/list/read.ts index 3174cc890..0f3033dd6 100644 --- a/src/services/mail/list/read.ts +++ b/src/services/mail/list/read.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailingList } from '@prisma/client' export async function readMailingLists(): Promise { diff --git a/src/services/mail/list/update.ts b/src/services/mail/list/update.ts index 475292767..0316a39ff 100644 --- a/src/services/mail/list/update.ts +++ b/src/services/mail/list/update.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { updateMailingListValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { UpdateMailingListTypes } from './validation' import type { MailingList } from '@prisma/client' diff --git a/src/services/mail/mailAddressExternal/actions.ts b/src/services/mail/mailAddressExternal/actions.ts new file mode 100644 index 000000000..6a56c49dd --- /dev/null +++ b/src/services/mail/mailAddressExternal/actions.ts @@ -0,0 +1,52 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createMailAddressExternal } from '@/services/mail/mailAddressExternal/create' +import { destroyMailAddressExternal } from '@/services/mail/mailAddressExternal/destroy' +import { updateMailAddressExternal } from '@/services/mail/mailAddressExternal/update' +import { + createMailAddressExternalValidation, + readMailAddressExternalValidation, + updateMailAddressExternalValidation, +} from '@/services/mail/mailAddressExternal/validation' +import type { ActionReturn } from '@/services/actionTypes' +import type { MailAddressExternal } from '@prisma/client' + +export async function createMailAddressExternalAction(rawdata: FormData): + Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILADDRESS_EXTERNAL_CREATE']] + }) + if (!authorized) return createActionError(status) + + const parse = createMailAddressExternalValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => createMailAddressExternal(parse.data)) +} + +export async function destroyMailAddressExternalAction(id: number): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILADDRESS_EXTERNAL_DESTROY']], + }) + if (!authorized) return createActionError(status) + + const parse = readMailAddressExternalValidation.typeValidate({ id }) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => destroyMailAddressExternal(parse.data.id)) +} + +export async function updateMailAddressExternalAction(data: FormData): +Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['MAILADDRESS_EXTERNAL_UPDATE']], + }) + if (!authorized) return createActionError(status) + + const parse = updateMailAddressExternalValidation.typeValidate(data) + if (!parse.success) return createZodActionError(parse) + + return safeServerCall(() => updateMailAddressExternal(parse.data)) +} diff --git a/src/services/mail/mailAddressExternal/create.ts b/src/services/mail/mailAddressExternal/create.ts index 1f334f823..3540c4c1f 100644 --- a/src/services/mail/mailAddressExternal/create.ts +++ b/src/services/mail/mailAddressExternal/create.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { createMailAddressExternalValidation, type CreateMailAddressExternalTypes } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailAddressExternal } from '@prisma/client' export async function createMailAddressExternal(rawdata: CreateMailAddressExternalTypes['Detailed']): diff --git a/src/services/mail/mailAddressExternal/destroy.ts b/src/services/mail/mailAddressExternal/destroy.ts index bad655b42..99df8f13f 100644 --- a/src/services/mail/mailAddressExternal/destroy.ts +++ b/src/services/mail/mailAddressExternal/destroy.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { readMailAddressExternalValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailAddressExternal } from '@prisma/client' diff --git a/src/services/mail/mailAddressExternal/read.ts b/src/services/mail/mailAddressExternal/read.ts index 3f3e046b4..4f1cacaa0 100644 --- a/src/services/mail/mailAddressExternal/read.ts +++ b/src/services/mail/mailAddressExternal/read.ts @@ -1,6 +1,6 @@ import '@pn-server-only' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailAddressExternal } from '@prisma/client' diff --git a/src/services/mail/mailAddressExternal/update.ts b/src/services/mail/mailAddressExternal/update.ts index 919b7f808..c6f3152a6 100644 --- a/src/services/mail/mailAddressExternal/update.ts +++ b/src/services/mail/mailAddressExternal/update.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { updateMailAddressExternalValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { MailAddressExternal } from '@prisma/client' import type { UpdatemailAddressExternalTypes } from './validation' diff --git a/src/services/mail/read.ts b/src/services/mail/read.ts index 07a949aa5..4ab5c0494 100644 --- a/src/services/mail/read.ts +++ b/src/services/mail/read.ts @@ -1,9 +1,9 @@ import '@pn-server-only' +import { userFilterSelection } from '@/services/users/constants' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' -import prisma from '@/prisma' -import { UserConfig } from '@/services/users/config' -import type { MailFlowObject, MailListTypes, ViaArrayType, ViaType } from './Types' +import { prisma } from '@/prisma/client' +import type { MailFlowObject, MailListTypes, ViaArrayType, ViaType } from './types' /** @@ -89,7 +89,7 @@ async function readAliasTraversal(id: number): Promise { // TODO: only find valid memberships include: { user: { - select: UserConfig.filterSelection, + select: userFilterSelection, } } } @@ -100,7 +100,7 @@ async function readAliasTraversal(id: number): Promise { users: { include: { user: { - select: UserConfig.filterSelection, + select: userFilterSelection, }, }, }, @@ -220,7 +220,7 @@ async function readMailingListTraversal(id: number): Promise { },*/ include: { user: { - select: UserConfig.filterSelection, + select: userFilterSelection, } } } @@ -231,7 +231,7 @@ async function readMailingListTraversal(id: number): Promise { users: { include: { user: { - select: UserConfig.filterSelection, + select: userFilterSelection, }, }, }, @@ -355,7 +355,7 @@ async function readGroupTraversal(id: number): Promise { memberships: { include: { user: { - select: UserConfig.filterSelection, + select: userFilterSelection, } } }, diff --git a/src/services/mail/Types.ts b/src/services/mail/types.ts similarity index 91% rename from src/services/mail/Types.ts rename to src/services/mail/types.ts index b0ecc84c2..33e3df65d 100644 --- a/src/services/mail/Types.ts +++ b/src/services/mail/types.ts @@ -1,6 +1,6 @@ import type { Group, MailAddressExternal, MailAlias, MailingList } from '@prisma/client' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' export const MailListTypeArray = ['alias', 'mailingList', 'group', 'user', 'mailaddressExternal'] as const diff --git a/src/services/news/actions.ts b/src/services/news/actions.ts new file mode 100644 index 000000000..d45789137 --- /dev/null +++ b/src/services/news/actions.ts @@ -0,0 +1,89 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { createNews } from '@/services/news/create' +import { destroyNews } from '@/services/news/destroy' +import { readNews, readNewsCurrent, readOldNewsPage } from '@/services/news/read' +import { updateNews } from '@/services/news/update' +import { createNewsArticleValidation, updateNewsArticleValidation } from '@/services/news/validation' +import { notificationOperations } from '@/services/notifications/operations' +import type { CreateNewsArticleTypes, UpdateNewsArticleTypes } from '@/services/news/validation' +import type { ExpandedNewsArticle, NewsCursor, SimpleNewsArticle } from '@/services/news/types' +import type { ReadPageInput } from '@/lib/paging/types' +import type { ActionReturn } from '@/services/actionTypes' + +export async function createNewsAction( + rawdata: FormData | CreateNewsArticleTypes['Type'] +): Promise> { + //TODO: check for can create news permission + const parse = createNewsArticleValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createNews(data)) +} + +export async function destroyNewsAction(id: number): Promise>> { + //TODO: check auth + return await safeServerCall(() => destroyNews(id)) +} + +export async function readOldNewsPageAction( + readPageImput: ReadPageInput +): Promise> { + //TODO: only read news with right visibility + return await safeServerCall(() => readOldNewsPage(readPageImput)) +} + +export async function readNewsCurrentAction(): Promise> { + //TODO: only read news with right visibility + return await safeServerCall(() => readNewsCurrent()) +} + +export async function readNewsAction(idOrName: number | { + articleName: string + order: number +}): Promise> { + //TODO: only read news if right visibility + return await safeServerCall(() => readNews(idOrName)) +} + +export async function updateNewsAction( + id: number, + rawdata: FormData | UpdateNewsArticleTypes['Type'] +): Promise>> { + //TODO: auth + const parse = updateNewsArticleValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + return await safeServerCall(() => updateNews(id, data)) +} + +export async function publishNewsAction( + // disable eslint rule temporarily until todo is resolved + // eslint-disable-next-line @typescript-eslint/no-unused-vars + id: number, + // disable eslint rule temporarily until todo is resolved + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldPublish: boolean +): Promise>> { + notificationOperations.createSpecial({ + params: { + special: 'NEW_NEWS_ARTICLE', + }, + data: { + title: 'Ny nyhetsartikkel', // TODO: Add info about the article + message: 'En ny nyhetsartikkel er publisert', + }, + bypassAuth: true, + }) + + return createActionError('UNKNOWN ERROR', 'Not implemented') +} + +// disable eslint rule temporarily until todo is resolved +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export async function updateVisibilityAction(id: number, visible: unknown): Promise> { + //TODO: add visible field to news + return createActionError('UNKNOWN ERROR', 'Not implemented') +} diff --git a/src/services/news/create.ts b/src/services/news/create.ts index a9d430ef1..7516fbe21 100644 --- a/src/services/news/create.ts +++ b/src/services/news/create.ts @@ -2,11 +2,11 @@ import '@pn-server-only' import { defaultNewsArticleOldCutoff, newsArticleRealtionsIncluder } from './ConfigVars' import { createNewsArticleValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' import { createArticle } from '@/services/cms/articles/create' import type { CreateNewsArticleTypes } from './validation' -import type { ExpandedNewsArticle } from './Types' +import type { ExpandedNewsArticle } from './types' /** * A function that creates a news article, it also creates a corresponding article in the CMS to diff --git a/src/services/news/destroy.ts b/src/services/news/destroy.ts index 0270f2898..828d22890 100644 --- a/src/services/news/destroy.ts +++ b/src/services/news/destroy.ts @@ -1,8 +1,8 @@ import '@pn-server-only' import { prismaCall } from '@/services/prismaCall' import { destroyArticle } from '@/services/cms/articles/destroy' -import prisma from '@/prisma' -import type { SimpleNewsArticle } from '@/services/news/Types' +import { prisma } from '@/prisma/client' +import type { SimpleNewsArticle } from '@/services/news/types' /** * Yhis function destroys a newsArticle. It is also responsible for sleaning up the article, diff --git a/src/services/news/read.ts b/src/services/news/read.ts index b9d123e9c..842046aca 100644 --- a/src/services/news/read.ts +++ b/src/services/news/read.ts @@ -3,9 +3,9 @@ import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import { newsArticleRealtionsIncluder, simpleNewsArticleRealtionsIncluder } from '@/services/news/ConfigVars' -import prisma from '@/prisma' -import type { ExpandedNewsArticle, NewsCursor, SimpleNewsArticle } from '@/services/news/Types' -import type { ReadPageInput } from '@/lib/paging/Types' +import { prisma } from '@/prisma/client' +import type { ExpandedNewsArticle, NewsCursor, SimpleNewsArticle } from '@/services/news/types' +import type { ReadPageInput } from '@/lib/paging/types' export async function readOldNewsPage( { page }: ReadPageInput diff --git a/src/services/news/Types.ts b/src/services/news/types.ts similarity index 83% rename from src/services/news/Types.ts rename to src/services/news/types.ts index 63b539f8c..6541c26fc 100644 --- a/src/services/news/Types.ts +++ b/src/services/news/types.ts @@ -1,4 +1,4 @@ -import type { ExpandedArticle } from '@/cms/articles/Types' +import type { ExpandedArticle } from '@/cms/articles/types' import type { NewsArticle, Image } from '@prisma/client' export type ExpandedNewsArticle = NewsArticle & { diff --git a/src/services/news/update.ts b/src/services/news/update.ts index f2c2bad27..e1664c160 100644 --- a/src/services/news/update.ts +++ b/src/services/news/update.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { updateNewsArticleValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { UpdateNewsArticleTypes } from './validation' -import type { SimpleNewsArticle } from '@/services/news/Types' +import type { SimpleNewsArticle } from '@/services/news/types' export async function updateNews( id: number, diff --git a/src/services/notifications/Types.ts b/src/services/notifications/Types.ts deleted file mode 100644 index 562343e7b..000000000 --- a/src/services/notifications/Types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { NotificationConfig } from './config' -import type { NotificationChannelConfig } from './channel/config' -import type { NotificationMethod, Prisma } from '@prisma/client' - -export type NotificationMethodTypes = typeof NotificationConfig.methodTypes[number] - -export type NotificationMethods = typeof NotificationConfig.methods[number] - -export type NotificationMethodGeneral = Omit - -export type ExpandedNotificationChannel = Prisma.NotificationChannelGetPayload<{ - include: typeof NotificationChannelConfig.includer -}> diff --git a/src/services/notifications/actions.ts b/src/services/notifications/actions.ts new file mode 100644 index 000000000..f1362690d --- /dev/null +++ b/src/services/notifications/actions.ts @@ -0,0 +1,15 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { notificationChannelOperations } from '@/services/notifications/channel/operations' +import { notificationOperations } from '@/services/notifications/operations' +import { notificationSubscriptionOperations } from '@/services/notifications/subscription/operations' + +export const createNotificationChannelAction = makeAction(notificationChannelOperations.create) +export const updateNotificationChannelAction = makeAction(notificationChannelOperations.update) +export const readNotificationChannelsAction = makeAction(notificationChannelOperations.readMany) + +export const createNotificationAction = makeAction(notificationOperations.create) + +export const readNotificationSubscriptionsAction = makeAction(notificationSubscriptionOperations.read) +export const updateNotificationSubscriptionsAction = makeAction(notificationSubscriptionOperations.update) diff --git a/src/services/notifications/auth.ts b/src/services/notifications/auth.ts new file mode 100644 index 000000000..641137b1e --- /dev/null +++ b/src/services/notifications/auth.ts @@ -0,0 +1,7 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' +import '@pn-server-only' + +export const notificationAuth = { + create: RequirePermission.staticFields({ permission: 'NOTIFICATION_CREATE' }), + sendMail: RequirePermission.staticFields({ permission: 'MAIL_SEND' }), +} diff --git a/src/services/notifications/authers.ts b/src/services/notifications/authers.ts deleted file mode 100644 index 7f16cf222..000000000 --- a/src/services/notifications/authers.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' -import '@pn-server-only' - -export namespace NotificationAuthers { - export const create = RequirePermission.staticFields({ permission: 'NOTIFICATION_CREATE' }) - export const sendMail = RequirePermission.staticFields({ permission: 'MAIL_SEND' }) -} diff --git a/src/services/notifications/channel/auth.ts b/src/services/notifications/channel/auth.ts new file mode 100644 index 000000000..821826a3f --- /dev/null +++ b/src/services/notifications/channel/auth.ts @@ -0,0 +1,9 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' +import '@pn-server-only' + +export const notificationChannelAuth = { + create: RequirePermission.staticFields({ permission: 'NOTIFICATION_CHANNEL_CREATE' }), + read: RequirePermission.staticFields({ permission: 'NOTIFICATION_CHANNEL_READ' }), + update: RequirePermission.staticFields({ permission: 'NOTIFICATION_CHANNEL_UPDATE' }), + destroy: RequirePermission.staticFields({ permission: 'NOTIFICATION_CHANNEL_UPDATE' }), +} diff --git a/src/services/notifications/channel/authers.ts b/src/services/notifications/channel/authers.ts deleted file mode 100644 index 425492732..000000000 --- a/src/services/notifications/channel/authers.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' -import '@pn-server-only' - -export namespace NotificationChannelAuthers { - export const create = RequirePermission.staticFields({ permission: 'NOTIFICATION_CHANNEL_CREATE' }) - export const read = RequirePermission.staticFields({ permission: 'NOTIFICATION_CHANNEL_READ' }) - export const update = RequirePermission.staticFields({ permission: 'NOTIFICATION_CHANNEL_UPDATE' }) - export const destroy = RequirePermission.staticFields({ permission: 'NOTIFICATION_CHANNEL_UPDATE' }) -} diff --git a/src/services/notifications/channel/config.ts b/src/services/notifications/channel/config.ts deleted file mode 100644 index a1ae4c7f3..000000000 --- a/src/services/notifications/channel/config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NotificationConfig } from '@/services/notifications/config' - - -export namespace NotificationChannelConfig { - - export const includer = { - availableMethods: { - select: NotificationConfig.allMethodsOn, - }, - defaultMethods: { - select: NotificationConfig.allMethodsOn, - }, - } as const -} - -export const INFINITE_LOOP_PREVENTION_MAX_ITERATIONS = 1000 - diff --git a/src/services/notifications/channel/constants.ts b/src/services/notifications/channel/constants.ts new file mode 100644 index 000000000..0e5a52098 --- /dev/null +++ b/src/services/notifications/channel/constants.ts @@ -0,0 +1,12 @@ +import { allNotificationMethodsOn } from '@/services/notifications/constants' + +export const availableNotificationMethodIncluder = { + availableMethods: { + select: allNotificationMethodsOn, + }, + defaultMethods: { + select: allNotificationMethodsOn, + }, +} as const + +export const INFINITE_LOOP_PREVENTION_MAX_ITERATIONS = 1000 diff --git a/src/services/notifications/channel/methods.ts b/src/services/notifications/channel/operations.ts similarity index 66% rename from src/services/notifications/channel/methods.ts rename to src/services/notifications/channel/operations.ts index 04fef2d48..8fb04b5c8 100644 --- a/src/services/notifications/channel/methods.ts +++ b/src/services/notifications/channel/operations.ts @@ -1,28 +1,31 @@ import '@pn-server-only' -import { NotificationChannelAuthers } from './authers' -import { NotificationChannelSchemas } from './schemas' -import { NotificationChannelConfig } from './config' +import { notificationChannelAuth } from './auth' +import { notificationChannelSchemas, validateMethods, validateNewParent } from './schemas' +import { availableNotificationMethodIncluder } from './constants' +import { + allNotificationMethodsOff, + allNotificationMethodsOn, + notificationMethodsArray, +} from '@/services/notifications/constants' +import { notificationMethodSchema } from '@/services/notifications/schemas' import { booleanOperationOnMethods } from '@/services/notifications/notificationMethodOperations' -import { ServiceMethod } from '@/services/ServiceMethod' -import { DEFAULT_NOTIFICATION_ALIAS } from '@/services/notifications/email/config' -import { NotificationConfig } from '@/services/notifications/config' +import { defineOperation } from '@/services/serviceOperation' +import { DEFAULT_NOTIFICATION_ALIAS } from '@/services/notifications/email/constants' import { ServerError } from '@/services/error' -import { NotificationSchemas } from '@/services/notifications/schemas' import { z } from 'zod' -import type { ExpandedNotificationChannel, NotificationMethodGeneral } from '@/services/notifications/Types' +import type { ExpandedNotificationChannel, NotificationMethodGeneral } from '@/services/notifications/types' -export namespace NotificationChannelMethods { - - export const create = ServiceMethod({ - auther: () => NotificationChannelAuthers.create.dynamicFields({}), - dataSchema: NotificationChannelSchemas.create, +export const notificationChannelOperations = { + create: defineOperation({ + authorizer: () => notificationChannelAuth.create.dynamicFields({}), + dataSchema: notificationChannelSchemas.create, opensTransaction: true, paramsSchema: z.object({ - availableMethods: NotificationSchemas.notificationMethodFields, - defaultMethods: NotificationSchemas.notificationMethodFields, + availableMethods: notificationMethodSchema, + defaultMethods: notificationMethodSchema, }), - method: async ({ prisma, data, params }): Promise => { - if (!NotificationChannelSchemas.validateMethods(params.availableMethods, params.defaultMethods)) { + operation: async ({ prisma, data, params }): Promise => { + if (!validateMethods(params.availableMethods, params.defaultMethods)) { throw new ServerError('BAD PARAMETERS', 'Default methods cannot exceed available methods.') } @@ -47,10 +50,10 @@ export namespace NotificationChannelMethods { } } }, - include: NotificationChannelConfig.includer, + include: availableNotificationMethodIncluder, }) - if (NotificationChannelSchemas.validateMethods(NotificationConfig.allMethodsOff, params.defaultMethods)) { + if (validateMethods(allNotificationMethodsOff, params.defaultMethods)) { return channel } @@ -61,7 +64,7 @@ export namespace NotificationChannelMethods { }, include: { methods: { - select: NotificationConfig.allMethodsOn, + select: allNotificationMethodsOn, } } }) @@ -72,8 +75,8 @@ export namespace NotificationChannelMethods { subscriptionMethods: booleanOperationOnMethods(subscription.methods, channel.defaultMethods, 'AND') })) .filter(sub => - !NotificationChannelSchemas.validateMethods( - NotificationConfig.allMethodsOff, + !validateMethods( + allNotificationMethodsOff, sub.subscriptionMethods ) ) @@ -99,40 +102,40 @@ export namespace NotificationChannelMethods { return channel } - }) + }), - export const readMany = ServiceMethod({ - auther: () => NotificationChannelAuthers.read.dynamicFields({}), - method: async ({ prisma }) => await prisma.notificationChannel.findMany({ - include: NotificationChannelConfig.includer, + readMany: defineOperation({ + authorizer: () => notificationChannelAuth.read.dynamicFields({}), + operation: async ({ prisma }) => await prisma.notificationChannel.findMany({ + include: availableNotificationMethodIncluder, }) - }) + }), - export const readDefault = ServiceMethod({ - auther: () => NotificationChannelAuthers.read.dynamicFields({}), - method: async ({ prisma }) => await prisma.notificationChannel.findMany({ + readDefault: defineOperation({ + authorizer: () => notificationChannelAuth.read.dynamicFields({}), + operation: async ({ prisma }) => await prisma.notificationChannel.findMany({ where: { defaultMethods: { - OR: NotificationConfig.methods.map(method => ({ + OR: notificationMethodsArray.map(method => ({ [method]: true })) } }, - include: NotificationChannelConfig.includer, + include: availableNotificationMethodIncluder, }) - }) + }), - export const update = ServiceMethod({ - auther: () => NotificationChannelAuthers.update.dynamicFields({}), - dataSchema: NotificationChannelSchemas.update, + update: defineOperation({ + authorizer: () => notificationChannelAuth.update.dynamicFields({}), + dataSchema: notificationChannelSchemas.update, paramsSchema: z.object({ id: z.number(), - availableMethods: NotificationSchemas.notificationMethodFields, - defaultMethods: NotificationSchemas.notificationMethodFields, + availableMethods: notificationMethodSchema, + defaultMethods: notificationMethodSchema, }), opensTransaction: true, - method: async ({ prisma, data, params, session }) => { - if (!NotificationChannelSchemas.validateMethods(params.availableMethods, params.defaultMethods)) { + operation: async ({ prisma, data, params }) => { + if (!validateMethods(params.availableMethods, params.defaultMethods)) { throw new ServerError('BAD PARAMETERS', 'Default methods cannot exceed available methods.') } @@ -153,12 +156,11 @@ export namespace NotificationChannelMethods { // Not allowed to change the parent of ROOT if (channel.special !== 'ROOT') { - const allChannels = await readMany.client(prisma).execute({ - session, + const allChannels = await notificationChannelOperations.readMany({ bypassAuth: true, }) - if (!NotificationChannelSchemas.validateNewParent(params.id, data.parentId, allChannels)) { + if (!validateNewParent(params.id, data.parentId, allChannels)) { throw new ServerError('BAD PARAMETERS', 'Cannot set parentId in a loop') } @@ -166,8 +168,8 @@ export namespace NotificationChannelMethods { } function methodsAreEqual(lhs: NotificationMethodGeneral, rhs: NotificationMethodGeneral) { - for (let i = 0; i < NotificationConfig.methods.length; i++) { - if (lhs[NotificationConfig.methods[i]] !== rhs[NotificationConfig.methods[i]]) { + for (let i = 0; i < notificationMethodsArray.length; i++) { + if (lhs[notificationMethodsArray[i]] !== rhs[notificationMethodsArray[i]]) { return false } } @@ -209,27 +211,27 @@ export namespace NotificationChannelMethods { } } }, - include: NotificationChannelConfig.includer, + include: availableNotificationMethodIncluder, }) }) } - }) + }), // It doesn't seem that this function is used yet. -Theodor - export const destroy = ServiceMethod({ - auther: () => NotificationChannelAuthers.destroy.dynamicFields({}), + destroy: defineOperation({ + authorizer: () => notificationChannelAuth.destroy.dynamicFields({}), paramsSchema: z.object({ id: z.number(), }), opensTransaction: true, - method: async ({ prisma, params }) => prisma.$transaction(async (tx) => { + operation: async ({ prisma, params }) => prisma.$transaction(async (tx) => { // NOTE: this should maybe be just a archive not a delete const results = await tx.notificationChannel.delete({ where: { id: params.id, }, - include: NotificationChannelConfig.includer, + include: availableNotificationMethodIncluder, }) await tx.notificationMethod.deleteMany({ @@ -243,6 +245,5 @@ export namespace NotificationChannelMethods { return results }) - }) - + }), } diff --git a/src/services/notifications/channel/schemas.ts b/src/services/notifications/channel/schemas.ts index 81d2a8101..ee178aa1d 100644 --- a/src/services/notifications/channel/schemas.ts +++ b/src/services/notifications/channel/schemas.ts @@ -1,107 +1,105 @@ -import { INFINITE_LOOP_PREVENTION_MAX_ITERATIONS } from './config' -import { NotificationConfig } from '@/services/notifications/config' +import { INFINITE_LOOP_PREVENTION_MAX_ITERATIONS } from './constants' +import { notificationMethodsArray } from '@/services/notifications/constants' import { SpecialNotificationChannel } from '@prisma/client' import { z } from 'zod' import type { ExpandedNotificationChannel, NotificationMethodGeneral, NotificationMethodTypes -} from '@/services/notifications/Types' - - -export namespace NotificationChannelSchemas { - - export function parseMethods(data: FormData, prefix?: NotificationMethodTypes) { - return Object.fromEntries( - NotificationConfig.methods.filter(method => NotificationConfig.methods.includes(method)).map(method => { - const compare = prefix ? `${prefix}_${method}` : method - const value = data.get(compare) - if (!value) { - return [method, false] - } - - return [method, value === 'on'] - }) - ) as NotificationMethodGeneral - } - - /** - * Validates the available notification methods against the default methods. - * @param availableMethods - The available notification methods. - * @param defaultMethods - The default notification methods. - * @returns A boolean indicating whether the available methods are valid. - */ - export function validateMethods( - availableMethods: NotificationMethodGeneral, - defaultMethods: NotificationMethodGeneral - ): boolean { - for (let i = 0; i < NotificationConfig.methods.length; i++) { - const availableMethod = availableMethods[NotificationConfig.methods[i]] - const defaultMethod = defaultMethods[NotificationConfig.methods[i]] - - if (defaultMethod && !availableMethod) return false - } +} from '@/services/notifications/types' + +export function parseMethods(data: FormData, prefix?: NotificationMethodTypes) { + return Object.fromEntries( + notificationMethodsArray.filter(method => notificationMethodsArray.includes(method)).map(method => { + const compare = prefix ? `${prefix}_${method}` : method + const value = data.get(compare) + if (!value) { + return [method, false] + } - return true - } + return [method, value === 'on'] + }) + ) as NotificationMethodGeneral +} - export function validateNewParent( - channelId: number, - newParentId: number, - channels: ExpandedNotificationChannel[] - ): boolean { - return findValidParents(channelId, channels).map(channel => channel.id).includes(newParentId) +/** + * Validates the available notification methods against the default methods. + * @param availableMethods - The available notification methods. + * @param defaultMethods - The default notification methods. + * @returns A boolean indicating whether the available methods are valid. + */ +export function validateMethods( + availableMethods: NotificationMethodGeneral, + defaultMethods: NotificationMethodGeneral +): boolean { + for (let i = 0; i < notificationMethodsArray.length; i++) { + const availableMethod = availableMethods[notificationMethodsArray[i]] + const defaultMethod = defaultMethods[notificationMethodsArray[i]] + + if (defaultMethod && !availableMethod) return false } - export function findValidParents( - channelId: number, - channels: ExpandedNotificationChannel[] - ): ExpandedNotificationChannel[] { - const validParents = channels.filter(channel => channel.id !== channelId) - // Remove chrildren of the current channel - const channelIDS = new Set(validParents.map(channel => channel.id)) - for (let i = 0; i < INFINITE_LOOP_PREVENTION_MAX_ITERATIONS; i++) { - const lengthBeforeReduction = channelIDS.size + return true +} - for (let j = validParents.length - 1; j >= 0; j--) { - if (!channelIDS.has(validParents[j].parentId)) { - channelIDS.delete(validParents[j].id) - validParents.splice(j, 1) - } - } +export function validateNewParent( + channelId: number, + newParentId: number, + channels: ExpandedNotificationChannel[] +): boolean { + return findValidParents(channelId, channels).map(channel => channel.id).includes(newParentId) +} - // Quit the loop if the last round didn't remove any more parent - if (lengthBeforeReduction === channelIDS.size) { - break +export function findValidParents( + channelId: number, + channels: ExpandedNotificationChannel[] +): ExpandedNotificationChannel[] { + const validParents = channels.filter(channel => channel.id !== channelId) + // Remove chrildren of the current channel + const channelIDS = new Set(validParents.map(channel => channel.id)) + for (let i = 0; i < INFINITE_LOOP_PREVENTION_MAX_ITERATIONS; i++) { + const lengthBeforeReduction = channelIDS.size + + for (let j = validParents.length - 1; j >= 0; j--) { + if (!channelIDS.has(validParents[j].parentId)) { + channelIDS.delete(validParents[j].id) + validParents.splice(j, 1) } + } - if (i >= INFINITE_LOOP_PREVENTION_MAX_ITERATIONS - 1) { - // It's highly unlikely that this wil ever throw - throw new Error('Stopping infinite loop, while finding valid parents.') - } + // Quit the loop if the last round didn't remove any more parent + if (lengthBeforeReduction === channelIDS.size) { + break } - return validParents + if (i >= INFINITE_LOOP_PREVENTION_MAX_ITERATIONS - 1) { + // It's highly unlikely that this wil ever throw + throw new Error('Stopping infinite loop, while finding valid parents.') + } } - const fields = z.object({ - name: z.string().min(2), - description: z.string(), - special: z.nativeEnum(SpecialNotificationChannel), - parentId: z.coerce.number().min(1), - mailAliasId: z.coerce.number().min(1), - }) + return validParents +} + +const baseSchema = z.object({ + name: z.string().min(2), + description: z.string(), + special: z.nativeEnum(SpecialNotificationChannel), + parentId: z.coerce.number().min(1), + mailAliasId: z.coerce.number().min(1), +}) - export const create = fields.pick({ +export const notificationChannelSchemas = { + create: baseSchema.pick({ name: true, description: true, parentId: true, - }) + }), - export const update = fields.pick({ + update: baseSchema.pick({ name: true, description: true, parentId: true, mailAliasId: true, - }) + }), } diff --git a/src/services/notifications/config.ts b/src/services/notifications/config.ts deleted file mode 100644 index 548e8aa39..000000000 --- a/src/services/notifications/config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { NotificationMethodGeneral } from './Types' - - -export namespace NotificationConfig { - export const methodsDisplayMap = { - email: 'E-post', - emailWeekly: 'Ukentlig e-post', - } satisfies Record - - export const allMethodsOn: NotificationMethodGeneral = { - email: true, - emailWeekly: true, - } - - export const allMethodsOff: NotificationMethodGeneral = { - email: false, - emailWeekly: false, - } - - export const methodTypes = ['availableMethods', 'defaultMethods'] as const - export const methods = ['email', 'emailWeekly'] as const satisfies (keyof NotificationMethodGeneral)[] -} - diff --git a/src/services/notifications/constants.ts b/src/services/notifications/constants.ts new file mode 100644 index 000000000..6a2e4b907 --- /dev/null +++ b/src/services/notifications/constants.ts @@ -0,0 +1,19 @@ +import type { NotificationMethodGeneral } from './types' + +export const notificationMethodsDisplayMap = { + email: 'E-post', + emailWeekly: 'Ukentlig e-post', +} satisfies Record + +export const allNotificationMethodsOn: NotificationMethodGeneral = { + email: true, + emailWeekly: true, +} + +export const allNotificationMethodsOff: NotificationMethodGeneral = { + email: false, + emailWeekly: false, +} + +export const notificationMethodTypes = ['availableMethods', 'defaultMethods'] as const +export const notificationMethodsArray = ['email', 'emailWeekly'] as const satisfies (keyof NotificationMethodGeneral)[] diff --git a/src/services/notifications/email/config.ts b/src/services/notifications/email/constants.ts similarity index 100% rename from src/services/notifications/email/config.ts rename to src/services/notifications/email/constants.ts diff --git a/src/services/notifications/email/dispatch.tsx b/src/services/notifications/email/dispatch.tsx index f478aef98..2819fc6ba 100644 --- a/src/services/notifications/email/dispatch.tsx +++ b/src/services/notifications/email/dispatch.tsx @@ -1,14 +1,14 @@ import { sendBulkMail } from './send' -import { DEFAULT_NOTIFICATION_ALIAS } from './config' +import { DEFAULT_NOTIFICATION_ALIAS } from './constants' import { sendEmailValidation } from './validation' import { DefaultEmailTemplate } from './templates/default' -import { NotificationMethods } from '@/services/notifications/methods' +import { repalceSpecialSymbols } from '@/services/notifications/operations' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { render } from '@react-email/render' -import type { ExpandedNotificationChannel } from '@/services/notifications/Types' +import type { ExpandedNotificationChannel } from '@/services/notifications/types' import type { Notification } from '@prisma/client' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' export async function dispatchEmailNotifications( @@ -39,8 +39,8 @@ export async function dispatchEmailNotifications( const parsed = sendEmailValidation.detailedValidate({ from: senderAlias, to: user.email, - subject: NotificationMethods.repalceSpecialSymbols(notificaion.title, user), - text: NotificationMethods.repalceSpecialSymbols(notificaion.message, user), + subject: repalceSpecialSymbols(notificaion.title, user), + text: repalceSpecialSymbols(notificaion.message, user), }) return { @@ -63,5 +63,7 @@ export async function dispatchEmailNotifications( } async function wrapInHTML(user: UserFiltered, text: string): Promise { + // TODO: Would it be possible to do React.createElement here? + // It feels cursed to write TSX in backend code. return render() } diff --git a/src/services/notifications/email/mailHandler.ts b/src/services/notifications/email/mailHandler.ts index e12cdee52..f064c20ce 100644 --- a/src/services/notifications/email/mailHandler.ts +++ b/src/services/notifications/email/mailHandler.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import { TRANSPORT_OPTIONS } from './config' +import { TRANSPORT_OPTIONS } from './constants' import nodemailer from 'nodemailer' import type SMTPPool from 'nodemailer/lib/smtp-pool' import type SMTPTransport from 'nodemailer/lib/smtp-transport' diff --git a/src/services/notifications/email/systemMail/resetPassword.tsx b/src/services/notifications/email/systemMail/resetPassword.tsx index 962511f19..b61192be1 100644 --- a/src/services/notifications/email/systemMail/resetPassword.tsx +++ b/src/services/notifications/email/systemMail/resetPassword.tsx @@ -2,7 +2,7 @@ import '@pn-server-only' import { sendSystemMail } from '@/services/notifications/email/send' import { ResetPasswordTemplate } from '@/services/notifications/email/templates/resetPassword' import { generateJWT } from '@/jwt/jwt' -import { UserMethods } from '@/services/users/methods' +import { userOperations } from '@/services/users/operations' import { ServerError } from '@/services/error' import { z } from 'zod' @@ -10,9 +10,8 @@ export async function sendResetPasswordMail(email: string) { const emailParsed = z.string().email().parse(email) try { - const user = await UserMethods.read.newClient().execute({ + const user = await userOperations.read({ params: { email: emailParsed }, - session: null, bypassAuth: true, }) diff --git a/src/services/notifications/email/systemMail/userInvitivation.tsx b/src/services/notifications/email/systemMail/userInvitivation.tsx index aa2b3c3b3..65cc442da 100644 --- a/src/services/notifications/email/systemMail/userInvitivation.tsx +++ b/src/services/notifications/email/systemMail/userInvitivation.tsx @@ -1,5 +1,5 @@ import { generateJWT } from '@/jwt/jwt' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' import '@pn-server-only' import { userInvitationExpiration } from './ConfigVars' import { sendSystemMail } from '@/services/notifications/email/send' diff --git a/src/services/notifications/email/systemMail/verifyEmail.tsx b/src/services/notifications/email/systemMail/verifyEmail.tsx index 23e1cfd68..15860b4f4 100644 --- a/src/services/notifications/email/systemMail/verifyEmail.tsx +++ b/src/services/notifications/email/systemMail/verifyEmail.tsx @@ -3,12 +3,12 @@ import { emailValidationExpiration } from './ConfigVars' import { VerifyEmailTemplate } from '@/services/notifications/email/templates/verifyEmail' import { sendSystemMail } from '@/services/notifications/email/send' import { generateJWT } from '@/jwt/jwt' -import { UserSchemas } from '@/services/users/schemas' -import type { UserFiltered } from '@/services/users/Types' +import { userSchemas } from '@/services/users/schemas' +import type { UserFiltered } from '@/services/users/types' // TODO: Fix this with new validation export async function sendVerifyEmail(user: UserFiltered, email: string) { - const parse = UserSchemas.verifyEmail.parse({ email }) + const parse = userSchemas.verifyEmail.parse({ email }) const jwt = generateJWT('verifyemail', { email: parse.email, diff --git a/src/services/notifications/email/templates/default.tsx b/src/services/notifications/email/templates/default.tsx index 28f5a8fd9..5364f201e 100644 --- a/src/services/notifications/email/templates/default.tsx +++ b/src/services/notifications/email/templates/default.tsx @@ -1,7 +1,7 @@ import '@pn-server-only' import { Html, Markdown } from '@react-email/components' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' export function DefaultEmailTemplate({ text, diff --git a/src/services/notifications/email/templates/resetPassword.tsx b/src/services/notifications/email/templates/resetPassword.tsx index e66136a6e..fb2c2f476 100644 --- a/src/services/notifications/email/templates/resetPassword.tsx +++ b/src/services/notifications/email/templates/resetPassword.tsx @@ -1,7 +1,7 @@ import '@pn-server-only' import { Html } from '@react-email/components' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' export function ResetPasswordTemplate({ user, diff --git a/src/services/notifications/email/templates/userInvitation.tsx b/src/services/notifications/email/templates/userInvitation.tsx index de92f0f34..7da868f12 100644 --- a/src/services/notifications/email/templates/userInvitation.tsx +++ b/src/services/notifications/email/templates/userInvitation.tsx @@ -1,7 +1,7 @@ import '@pn-server-only' import { Html } from '@react-email/components' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' export function UserInvitationTemplate({ user, diff --git a/src/services/notifications/email/templates/verifyEmail.tsx b/src/services/notifications/email/templates/verifyEmail.tsx index 910d2a6c9..a0e166554 100644 --- a/src/services/notifications/email/templates/verifyEmail.tsx +++ b/src/services/notifications/email/templates/verifyEmail.tsx @@ -1,7 +1,7 @@ import '@pn-server-only' import { Html } from '@react-email/components' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' export function VerifyEmailTemplate({ user, diff --git a/src/services/notifications/notificationMethodOperations.ts b/src/services/notifications/notificationMethodOperations.ts index 1251a26b4..0e0894bb6 100644 --- a/src/services/notifications/notificationMethodOperations.ts +++ b/src/services/notifications/notificationMethodOperations.ts @@ -1,12 +1,12 @@ -import { NotificationConfig } from './config' -import type { NotificationMethodGeneral } from './Types' +import { allNotificationMethodsOff, allNotificationMethodsOn, notificationMethodsArray } from './constants' +import type { NotificationMethodGeneral } from './types' export function newAllMethodsOff() { - return { ...NotificationConfig.allMethodsOff } + return { ...allNotificationMethodsOff } } export function newAllMethodsOn() { - return { ...NotificationConfig.allMethodsOn } + return { ...allNotificationMethodsOn } } export function booleanOperationOnMethods( @@ -16,7 +16,7 @@ export function booleanOperationOnMethods( ): NotificationMethodGeneral { const ret = Object.assign({}, lhs) - for (const key of NotificationConfig.methods) { + for (const key of notificationMethodsArray) { switch (operation) { case 'AND': ret[key] &&= rhs[key] diff --git a/src/services/notifications/methods.ts b/src/services/notifications/operations.ts similarity index 66% rename from src/services/notifications/methods.ts rename to src/services/notifications/operations.ts index b905a8bfe..7992efcb1 100644 --- a/src/services/notifications/methods.ts +++ b/src/services/notifications/operations.ts @@ -1,45 +1,44 @@ import '@pn-server-only' -import { NotificationSchemas } from './schemas' -import { NotificationConfig } from './config' -import { NotificationChannelConfig } from './channel/config' import { dispatchEmailNotifications } from './email/dispatch' -import { NotificationAuthers } from './authers' -import { ServiceMethod } from '@/services/ServiceMethod' +import { notificationAuth } from './auth' +import { notificationSchemas } from './schemas' +import { allNotificationMethodsOn, notificationMethodsArray } from './constants' +import { availableNotificationMethodIncluder } from './channel/constants' +import { userFilterSelection } from '@/services/users/constants' +import { defineOperation } from '@/services/serviceOperation' import { ServerOnly } from '@/auth/auther/ServerOnly' -import { UserConfig } from '@/services/users/config' import { z } from 'zod' import { SpecialNotificationChannel } from '@prisma/client' import type { Notification } from '@prisma/client' -import type { ExpandedNotificationChannel } from './Types' -import type { UserFiltered } from '@/services/users/Types' +import type { ExpandedNotificationChannel, NotificationResult } from './types' +import type { UserFiltered } from '@/services/users/types' -export namespace NotificationMethods { +const dispathMethod = { + email: dispatchEmailNotifications, + emailWeekly: async () => { }, +} satisfies Record< + typeof notificationMethodsArray[number], + ((channel: ExpandedNotificationChannel, notification: Notification, users: UserFiltered[]) => Promise) +> - const dispathMethod = { - email: dispatchEmailNotifications, - emailWeekly: async () => { }, - } satisfies Record< - typeof NotificationConfig.methods[number], - ((channel: ExpandedNotificationChannel, notification: Notification, users: UserFiltered[]) => Promise) - > - - export function repalceSpecialSymbols(text: string, user: UserFiltered) { - return text - .replaceAll('%u', user.username) - .replaceAll('%n', user.firstname) - .replaceAll('%N', `${user.firstname} ${user.lastname}`) - } +export function repalceSpecialSymbols(text: string, user: UserFiltered) { + return text + .replaceAll('%u', user.username) + .replaceAll('%n', user.firstname) + .replaceAll('%N', `${user.firstname} ${user.lastname}`) +} +export const notificationOperations = { /** * Creates a notification with the specified data. * * @param data - The detailed data for dispatching the notification. * @returns A promise that resolves with an object containing the dispatched notification and the number of recipients. */ - export const create = ServiceMethod({ - auther: () => NotificationAuthers.create.dynamicFields({}), - dataSchema: NotificationSchemas.create, - method: async ({ prisma, data }) => { + create: defineOperation({ + authorizer: () => notificationAuth.create.dynamicFields({}), + dataSchema: notificationSchemas.create, + operation: async ({ prisma, data }): Promise => { // This prevent notifications from beeing sent during seeding if (process.env.IGNORE_SERVER_ONLY) { return { @@ -65,14 +64,14 @@ export namespace NotificationMethods { id: data.channelId, }, include: { - ...NotificationChannelConfig.includer, + ...availableNotificationMethodIncluder, subscriptions: { select: { methods: { - select: NotificationConfig.allMethodsOn, + select: allNotificationMethodsOn, }, user: { - select: UserConfig.filterSelection, + select: userFilterSelection, }, }, }, @@ -88,7 +87,7 @@ export namespace NotificationMethods { // TODO: Filter the users by visibility - NotificationConfig.methods.forEach(method => { + notificationMethodsArray.forEach(method => { if (!results.availableMethods[method]) { return } @@ -108,8 +107,7 @@ export namespace NotificationMethods { recipients: results.subscriptions.length } } - }) - + }), /** * Createses a notification to a special notification channel. @@ -119,20 +117,20 @@ export namespace NotificationMethods { * @param message - The message content of the notification. * @returns A promise that resolves with an object containing the dispatched notification and the number of recipients. */ - export const createSpecial = ServiceMethod({ - auther: ServerOnly, + createSpecial: defineOperation({ + authorizer: ServerOnly, paramsSchema: z.object({ special: z.nativeEnum(SpecialNotificationChannel), }), - dataSchema: NotificationSchemas.createSpecial, - method: async ({ prisma, params, data, session }) => { + dataSchema: notificationSchemas.createSpecial, + operation: async ({ prisma, params, data, session }): Promise => { const channel = await prisma.notificationChannel.findUniqueOrThrow({ where: { special: params.special, } }) - return await create.client(prisma).execute({ + return await notificationOperations.create({ session, bypassAuth: true, data: { @@ -143,5 +141,5 @@ export namespace NotificationMethods { } }) } - }) + }), } diff --git a/src/services/notifications/schemas.ts b/src/services/notifications/schemas.ts index 76e431e4f..018b9d6e0 100644 --- a/src/services/notifications/schemas.ts +++ b/src/services/notifications/schemas.ts @@ -1,40 +1,38 @@ import { z } from 'zod' - -export namespace NotificationSchemas { - - export const notificationMethodFields = z.object({ - email: z.boolean(), - emailWeekly: z.boolean(), - }) - - const fields = z.object({ - channelId: z.coerce.number().min(1), - title: z.string().min(2), - message: z.string().min(10), - email: z.string().email(), - userIdList: z.number().array().optional(), - }) - - export const create = fields.pick({ +export const notificationMethodSchema = z.object({ + email: z.boolean(), + emailWeekly: z.boolean(), +}) + +const baseSchema = z.object({ + channelId: z.coerce.number().min(1), + title: z.string().min(2), + message: z.string().min(10), + email: z.string().email(), + userIdList: z.number().array().optional(), +}) + +export const notificationSchemas = { + create: baseSchema.pick({ channelId: true, title: true, message: true, userIdList: true, - }) + }), - export const createSpecial = fields.pick({ + createSpecial: baseSchema.pick({ title: true, message: true, userIdList: true, - }) + }), - export const sendMail = fields.pick({ + sendMail: baseSchema.pick({ email: true, - }) + }), - export const sendEmail = fields.pick({ + sendEmail: baseSchema.pick({ title: true, message: true, - }) + }), } diff --git a/src/services/notifications/subscription/auth.ts b/src/services/notifications/subscription/auth.ts new file mode 100644 index 000000000..5616998a9 --- /dev/null +++ b/src/services/notifications/subscription/auth.ts @@ -0,0 +1,6 @@ +import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' + +export const notificationSubscriptionAuth = { + read: RequireUserIdOrPermission.staticFields({ permission: 'NOTIFICATION_SUBSCRIPTION_READ' }), + update: RequireUserIdOrPermission.staticFields({ permission: 'NOTIFICATION_SUBSCRIPTION_UPDATE' }), +} diff --git a/src/services/notifications/subscription/authers.ts b/src/services/notifications/subscription/authers.ts deleted file mode 100644 index 925a42673..000000000 --- a/src/services/notifications/subscription/authers.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' - -export namespace NotificationSubscriptionAuthers { - export const read = RequireUserIdOrPermission.staticFields({ permission: 'NOTIFICATION_SUBSCRIPTION_READ' }) - export const update = RequireUserIdOrPermission.staticFields({ permission: 'NOTIFICATION_SUBSCRIPTION_UPDATE' }) -} diff --git a/src/services/notifications/subscription/config.ts b/src/services/notifications/subscription/config.ts deleted file mode 100644 index c60fe2e08..000000000 --- a/src/services/notifications/subscription/config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NotificationConfig } from '@/services/notifications/config' -import type { Prisma } from '@prisma/client' - - -export namespace NotificationSubscriptionConfig { - export const includer = { - methods: { - select: NotificationConfig.allMethodsOn, - }, - } satisfies Prisma.NotificationSubscriptionInclude -} diff --git a/src/services/notifications/subscription/constants.ts b/src/services/notifications/subscription/constants.ts new file mode 100644 index 000000000..a0e223ef1 --- /dev/null +++ b/src/services/notifications/subscription/constants.ts @@ -0,0 +1,8 @@ +import { allNotificationMethodsOn } from '@/services/notifications/constants' +import type { Prisma } from '@prisma/client' + +export const notificationMethodIncluder = { + methods: { + select: allNotificationMethodsOn, + }, +} satisfies Prisma.NotificationSubscriptionInclude diff --git a/src/services/notifications/subscription/methods.ts b/src/services/notifications/subscription/methods.ts deleted file mode 100644 index b723c70e8..000000000 --- a/src/services/notifications/subscription/methods.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { NotificationSubscriptionAuthers } from './authers' -import { NotificationSubscriptionConfig } from './config' -import { SubscriptionSchemas } from './schemas' -import { NotificationChannelConfig } from '@/services/notifications/channel/config' -import { NotificationConfig } from '@/services/notifications/config' -import { NotificationChannelSchemas } from '@/services/notifications/channel/schemas' -import { NotificationChannelMethods } from '@/services/notifications/channel/methods' -import { ServiceMethod } from '@/services/ServiceMethod' -import { ServerOnly } from '@/auth/auther/ServerOnly' -import { ServerError } from '@/services/error' -import { z } from 'zod' -import type { PrismaPossibleTransaction } from '@/services/ServiceMethod' -import type { Subscription } from './Types' -import type { NotificationMethodGeneral } from '@/services/notifications/Types' - - -export namespace NotificationSubscriptionMethods { - - export const read = ServiceMethod({ - paramsSchema: z.object({ - userId: z.number(), - }), - auther: ({ params }) => NotificationSubscriptionAuthers.read.dynamicFields(params), - method: async ({ prisma, params }) => await prisma.notificationSubscription.findMany({ - where: { - userId: params.userId, - }, - include: NotificationSubscriptionConfig.includer, - }), - }) - - export const createDefault = ServiceMethod({ - auther: ServerOnly, - paramsSchema: z.object({ - userId: z.number(), - }), - opensTransaction: true, - method: async ({ prisma, params, session }) => { - const channels = await NotificationChannelMethods.readDefault.client(prisma).execute({ - session, - bypassAuth: true, - }) - - await prisma.$transaction(channels.map(channel => - prisma.notificationSubscription.create({ - data: { - user: { - connect: { - id: params.userId, - } - }, - channel: { - connect: { - id: channel.id, - } - }, - methods: { - create: channel.defaultMethods, - } - } - }) - )) - } - }) - - - // eslint-disable-next-line - async function createTransactionPart( - prisma: PrismaPossibleTransaction, - userId: number, - channelId: number, - methods: NotificationMethodGeneral - ): Promise<(() => Promise) | null> { - const whereFilter = { - userId_channelId: { - userId, - channelId, - } - } - - const subscription = await prisma.notificationSubscription.findUnique({ - where: whereFilter - }) - - const subscriptionExists = subscription !== null - - // If all methods are off, we delete the relation - if (NotificationChannelSchemas.validateMethods(NotificationConfig.allMethodsOff, methods)) { - // No change, do nothing - if (!subscriptionExists) { - return null - } - - // Delete the realtion - return async () => { - const sub = await prisma.notificationSubscription.delete({ - where: whereFilter, - include: NotificationSubscriptionConfig.includer, - }) - - await prisma.notificationMethod.delete({ - where: { - id: sub.methodsId - } - }) - - return sub - } - } - - // Verify that the new methods are a subset of the available methods - const notificaionChannel = await prisma.notificationChannel.findUniqueOrThrow({ - where: { - id: channelId, - }, - include: NotificationChannelConfig.includer, - }) - - if (!NotificationChannelSchemas.validateMethods(notificaionChannel.availableMethods, methods)) { - throw new ServerError('BAD PARAMETERS', 'The methods must a subset of the available methods') - } - - // Update the relation - if (subscriptionExists) { - return async () => { - const results = await prisma.notificationMethod.update({ - where: { - id: subscription.methodsId, - }, - data: methods, - select: NotificationConfig.allMethodsOn, - }) - - return { - ...subscription, - methods: results, - } - } - } - - // Create the relation - return () => prisma.notificationSubscription.create({ - data: { - channel: { - connect: { - id: channelId, - }, - }, - user: { - connect: { - id: userId, - }, - }, - methods: { - create: methods, - }, - }, - include: NotificationSubscriptionConfig.includer, - }) - } - - export const update = ServiceMethod({ - auther: ({ params }) => NotificationSubscriptionAuthers.update.dynamicFields(params), - paramsSchema: z.object({ - userId: z.number(), - }), - dataSchema: SubscriptionSchemas.update, - method: async ({ prisma, params, data }): Promise => { - // Prepare updates and validate the data with the data in the database - const transactionParts = (await Promise.all( - data.subscriptions.map(subscription => - createTransactionPart(prisma, params.userId, subscription.channelId, subscription.methods) - ) - )).filter(i => i) as (() => Promise)[] - - // Update the subscriptions - return await Promise.all( - transactionParts.map(part => part()) - ) - } - }) -} diff --git a/src/services/notifications/subscription/operations.ts b/src/services/notifications/subscription/operations.ts new file mode 100644 index 000000000..80eb326fa --- /dev/null +++ b/src/services/notifications/subscription/operations.ts @@ -0,0 +1,180 @@ +import { notificationMethodIncluder } from './constants' +import { notificationSubscriptionAuth } from './auth' +import { subscriptionSchemas } from './schemas' +import { validateMethods } from '@/services/notifications/channel/schemas' +import { allNotificationMethodsOff, allNotificationMethodsOn } from '@/services/notifications/constants' +import { availableNotificationMethodIncluder } from '@/services/notifications/channel/constants' +import { notificationChannelOperations } from '@/services/notifications/channel/operations' +import { defineOperation } from '@/services/serviceOperation' +import { ServerOnly } from '@/auth/auther/ServerOnly' +import { ServerError } from '@/services/error' +import { z } from 'zod' +import type { Prisma } from '@prisma/client' +import type { Subscription } from './types' +import type { NotificationMethodGeneral } from '@/services/notifications/types' + +// eslint-disable-next-line +async function createTransactionPart( + prisma: Prisma.TransactionClient, + userId: number, + channelId: number, + methods: NotificationMethodGeneral +): Promise<(() => Promise) | null> { + const whereFilter = { + userId_channelId: { + userId, + channelId, + } + } + + const subscription = await prisma.notificationSubscription.findUnique({ + where: whereFilter + }) + + const subscriptionExists = subscription !== null + + // If all methods are off, we delete the relation + if (validateMethods(allNotificationMethodsOff, methods)) { + // No change, do nothing + if (!subscriptionExists) { + return null + } + + // Delete the realtion + return async () => { + const sub = await prisma.notificationSubscription.delete({ + where: whereFilter, + include: notificationMethodIncluder, + }) + + await prisma.notificationMethod.delete({ + where: { + id: sub.methodsId + } + }) + + return sub + } + } + + // Verify that the new methods are a subset of the available methods + const notificaionChannel = await prisma.notificationChannel.findUniqueOrThrow({ + where: { + id: channelId, + }, + include: availableNotificationMethodIncluder, + }) + + if (!validateMethods(notificaionChannel.availableMethods, methods)) { + throw new ServerError('BAD PARAMETERS', 'The methods must a subset of the available methods') + } + + // Update the relation + if (subscriptionExists) { + return async () => { + const results = await prisma.notificationMethod.update({ + where: { + id: subscription.methodsId, + }, + data: methods, + select: allNotificationMethodsOn, + }) + + return { + ...subscription, + methods: results, + } + } + } + + // Create the relation + return () => prisma.notificationSubscription.create({ + data: { + channel: { + connect: { + id: channelId, + }, + }, + user: { + connect: { + id: userId, + }, + }, + methods: { + create: methods, + }, + }, + include: notificationMethodIncluder, + }) +} + +export const notificationSubscriptionOperations = { + read: defineOperation({ + paramsSchema: z.object({ + userId: z.number(), + }), + authorizer: ({ params }) => notificationSubscriptionAuth.read.dynamicFields(params), + operation: async ({ prisma, params }) => await prisma.notificationSubscription.findMany({ + where: { + userId: params.userId, + }, + include: notificationMethodIncluder, + }), + }), + + createDefault: defineOperation({ + authorizer: ServerOnly, + paramsSchema: z.object({ + userId: z.number(), + }), + opensTransaction: true, + operation: async ({ prisma, params, session }) => { + const channels = await notificationChannelOperations.readDefault({ + session, + bypassAuth: true, + }) + + await prisma.$transaction(channels.map(channel => + prisma.notificationSubscription.create({ + data: { + user: { + connect: { + id: params.userId, + } + }, + channel: { + connect: { + id: channel.id, + } + }, + methods: { + create: channel.defaultMethods, + } + } + }) + )) + } + }), + + + update: defineOperation({ + authorizer: ({ params }) => notificationSubscriptionAuth.update.dynamicFields(params), + paramsSchema: z.object({ + userId: z.number(), + }), + dataSchema: subscriptionSchemas.update, + operation: async ({ prisma, params, data }): Promise => { + // Prepare updates and validate the data with the data in the database + const transactionParts = (await Promise.all( + data.subscriptions.map(subscription => + createTransactionPart(prisma, params.userId, subscription.channelId, subscription.methods) + ) + )).filter(i => i) as (() => Promise)[] + + // Update the subscriptions + return await Promise.all( + transactionParts.map(part => part()) + ) + } + }), +} diff --git a/src/services/notifications/subscription/schemas.ts b/src/services/notifications/subscription/schemas.ts index ad09958d1..8683e7f9c 100644 --- a/src/services/notifications/subscription/schemas.ts +++ b/src/services/notifications/subscription/schemas.ts @@ -1,17 +1,15 @@ -import { NotificationSchemas } from '@/services/notifications/schemas' +import { notificationMethodSchema } from '@/services/notifications/schemas' import { z } from 'zod' +const baseSchema = z.object({ + subscriptions: z.array(z.object({ + channelId: z.number().min(1), + methods: notificationMethodSchema, + })), +}) -export namespace SubscriptionSchemas { - - const fields = z.object({ - subscriptions: z.array(z.object({ - channelId: z.number().min(1), - methods: NotificationSchemas.notificationMethodFields, - })), - }) - - export const update = fields.pick({ +export const subscriptionSchemas = { + update: baseSchema.pick({ subscriptions: true, - }) + }), } diff --git a/src/services/notifications/subscription/Types.ts b/src/services/notifications/subscription/types.ts similarity index 62% rename from src/services/notifications/subscription/Types.ts rename to src/services/notifications/subscription/types.ts index c4e07c9d8..0c92d6134 100644 --- a/src/services/notifications/subscription/Types.ts +++ b/src/services/notifications/subscription/types.ts @@ -1,9 +1,9 @@ -import type { NotificationSubscriptionConfig } from './config' +import type { notificationMethodIncluder } from './constants' import type { Prisma } from '@prisma/client' export type Subscription = Prisma.NotificationSubscriptionGetPayload<{ - include: typeof NotificationSubscriptionConfig.includer + include: typeof notificationMethodIncluder }> export type MinimizedSubscription = Pick diff --git a/src/services/notifications/types.ts b/src/services/notifications/types.ts new file mode 100644 index 000000000..1259db248 --- /dev/null +++ b/src/services/notifications/types.ts @@ -0,0 +1,18 @@ +import type { availableNotificationMethodIncluder } from './channel/constants' +import type { notificationMethodsArray, notificationMethodTypes } from './constants' +import type { Notification, NotificationMethod, Prisma } from '@prisma/client' + +export type NotificationMethodTypes = typeof notificationMethodTypes[number] + +export type NotificationMethods = typeof notificationMethodsArray[number] + +export type NotificationMethodGeneral = Omit + +export type ExpandedNotificationChannel = Prisma.NotificationChannelGetPayload<{ + include: typeof availableNotificationMethodIncluder +}> + +export type NotificationResult = { + notification: Notification | null, + recipients: number +} diff --git a/src/services/ombul/actions.ts b/src/services/ombul/actions.ts new file mode 100644 index 000000000..2aeabf900 --- /dev/null +++ b/src/services/ombul/actions.ts @@ -0,0 +1,123 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createOmbul } from '@/services/ombul/create' +import { destroyOmbul } from '@/services/ombul/destroy' +import { readLatestOmbul, readOmbul, readOmbuls } from '@/services/ombul/read' +import { updateOmbul, updateOmbulFile } from '@/services/ombul/update' +import { createOmbulValidation, updateOmbulFileValidation, updateOmbulValidation } from '@/services/ombul/validation' +import type { ExpandedOmbul } from '@/services/ombul/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateOmbulTypes, UpdateOmbulFileTypes, UpdateOmbulTypes } from '@/services/ombul/validation' +import type { Ombul } from '@prisma/client' + +/** + * Create a new Ombul. + * @param rawData includes a pdf file with the ombul issue optionaly year and issueNumber + * @param CoverImageId is the id of the Image that will be used as the cover of the ombul + */ +export async function createOmbulAction(rawdata: FormData | CreateOmbulTypes['Type']): Promise> { + //Auth route + const { status, authorized } = await getUser({ + requiredPermissions: [['OMBUL_CREATE']] + }) + if (!authorized) return createActionError(status) + + const parse = createOmbulValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createOmbul(data)) +} + +export async function destroyOmbulAction(id: number): Promise> { + const { status, authorized } = await getUser({ + requiredPermissions: [['OMBUL_DESTROY']] + }) + + if (!authorized) return createActionError(status) + + return await safeServerCall(() => destroyOmbul(id)) +} + +export async function readLatestOmbulAction(): Promise> { + //Auth route + const { status, authorized } = await getUser({ + requiredPermissions: [['OMBUL_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readLatestOmbul()) +} + +export async function readOmbulAction(idOrNameAndYear: number | { + name: string, + year: number, +}): Promise> { + //Auth route + const { status, authorized } = await getUser({ + requiredPermissions: [['OMBUL_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readOmbul(idOrNameAndYear)) +} + +export async function readOmbulsAction(): Promise> { + //Auth route + const { status, authorized } = await getUser({ + requiredPermissions: [['OMBUL_READ']] + }) + if (!authorized) { + return createActionError(status) + } + return await safeServerCall(() => readOmbuls()) +} + +/** + * A action to update an ombul + * @param id - The id of the ombul to update + * @param rawdata - The new data for the ombul including: name, year, issueNumber, description, + * @returns The updated ombul + */ +export async function updateOmbulAction( + id: number, + rawdata: FormData | UpdateOmbulTypes['Type'] +): Promise> { + // Auth route + const { status, authorized } = await getUser({ + requiredPermissions: [['OMBUL_UPDATE']] + }) + if (!authorized) return createActionError(status) + + //Parse the data + const parse = updateOmbulValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => updateOmbul(id, data)) +} + +/** + * A action that updates the ombul file (i.e. the pdf file) of an ombul + * @param id - The id of the ombul to update + * @param rawData - The new data for the new ombul file with field name 'file' + * @returns The updated ombul + */ +export async function updateOmbulFileAction( + id: number, + rawData: FormData | UpdateOmbulFileTypes['Type'] +): Promise> { + // auth route + const { status, authorized } = await getUser({ + requiredPermissions: [['OMBUL_UPDATE']] + }) + if (!authorized) return createActionError(status) + + const parse = updateOmbulFileValidation.typeValidate(rawData) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => updateOmbulFile(id, data)) +} diff --git a/src/services/ombul/create.ts b/src/services/ombul/create.ts index f9e782919..032334307 100644 --- a/src/services/ombul/create.ts +++ b/src/services/ombul/create.ts @@ -3,10 +3,10 @@ import { createOmbulValidation } from './validation' import { prismaCall } from '@/services/prismaCall' import { readSpecialImageCollection } from '@/services/images/collections/read' import { createCmsImage } from '@/services/cms/images/create' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { createFile } from '@/services/store/createFile' -import { ImageMethods } from '@/services/images/methods' -import { NotificationMethods } from '@/services/notifications/methods' +import { imageOperations } from '@/services/images/operations' +import { notificationOperations } from '@/services/notifications/operations' import type { CreateOmbulTypes } from './validation' import type { Ombul } from '@prisma/client' @@ -43,7 +43,7 @@ export async function createOmbul( // create coverimage const ombulCoverCollection = await readSpecialImageCollection('OMBULCOVERS') - const coverImage = await ImageMethods.create.client(prisma).execute({ + const coverImage = await imageOperations.create({ params: { collectionId: ombulCoverCollection.id, }, @@ -52,7 +52,6 @@ export async function createOmbul( alt: `cover of ${config.name}`, file: cover, }, - session: null }) const cmsCoverImage = await createCmsImage({ name: fsLocation }, coverImage) @@ -71,7 +70,7 @@ export async function createOmbul( } })) - NotificationMethods.createSpecial.newClient().execute({ + notificationOperations.createSpecial({ params: { special: 'NEW_OMBUL', }, @@ -79,7 +78,6 @@ export async function createOmbul( title: 'Ny ombul', message: `Ny ombul er ute! ${ombul.name}`, }, - session: null, bypassAuth: true, }) diff --git a/src/services/ombul/destroy.ts b/src/services/ombul/destroy.ts index 604bacd8f..9ea90aaa8 100644 --- a/src/services/ombul/destroy.ts +++ b/src/services/ombul/destroy.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { destroyFile } from '@/services/store/destroyFile' -import type { ExpandedOmbul } from '@/services/ombul/Types' +import type { ExpandedOmbul } from '@/services/ombul/types' /** * A function to destroy an ombul, also deletes the file from the store, and the cmsImage on cascade diff --git a/src/services/ombul/read.ts b/src/services/ombul/read.ts index 474c8bb27..d4edeb582 100644 --- a/src/services/ombul/read.ts +++ b/src/services/ombul/read.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { Ombul } from '@prisma/client' -import type { ExpandedOmbul } from './Types' +import type { ExpandedOmbul } from './types' export async function readLatestOmbul(): Promise { const ombul = await prisma.ombul.findMany({ diff --git a/src/services/ombul/Types.ts b/src/services/ombul/types.ts similarity index 100% rename from src/services/ombul/Types.ts rename to src/services/ombul/types.ts diff --git a/src/services/ombul/update.ts b/src/services/ombul/update.ts index 29cb1623f..2d2cab8b6 100644 --- a/src/services/ombul/update.ts +++ b/src/services/ombul/update.ts @@ -2,11 +2,11 @@ import '@pn-server-only' import { updateOmbulFileValidation, updateOmbulValidation } from './validation' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { createFile } from '@/services/store/createFile' import { destroyFile } from '@/services/store/destroyFile' import type { UpdateOmbulFileTypes, UpdateOmbulTypes } from './validation' -import type { ExpandedOmbul } from './Types' +import type { ExpandedOmbul } from './types' /** * A function Update an ombul diff --git a/src/services/ombul/validation.ts b/src/services/ombul/validation.ts index f543cd9f2..588f24781 100644 --- a/src/services/ombul/validation.ts +++ b/src/services/ombul/validation.ts @@ -1,13 +1,13 @@ +import { imageFileSchema } from '@/services/images/schemas' import { ValidationBase } from '@/services/Validation' import { maxOmbulFileSize } from '@/services/ombul/ConfigVars' -import { ImageSchemas } from '@/services/images/schemas' import { z } from 'zod' import type { ValidationTypes } from '@/services/Validation' export const baseOmbulValidation = new ValidationBase({ type: { ombulFile: z.instanceof(File), - ombulCoverImage: ImageSchemas.fileSchema, + ombulCoverImage: imageFileSchema, year: z.string().optional(), issueNumber: z.string().optional(), name: z.string(), @@ -15,7 +15,7 @@ export const baseOmbulValidation = new ValidationBase({ }, details: { ombulFile: z.instanceof(File).refine(file => file.size < maxOmbulFileSize, 'Fil må være mindre enn 10mb'), - ombulCoverImage: ImageSchemas.fileSchema, + ombulCoverImage: imageFileSchema, year: z.number().refine(val => (val === undefined) || (val >= 1919 && val <= (new Date()).getFullYear()), 'Må være mellom 1919 og nåværende år' diff --git a/src/services/omegaOrder/actions.ts b/src/services/omegaOrder/actions.ts new file mode 100644 index 000000000..6bf55f39c --- /dev/null +++ b/src/services/omegaOrder/actions.ts @@ -0,0 +1,26 @@ +'use server' + +import { createActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createOmegaOrder } from '@/services/omegaOrder/create' +import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' +import type { ActionReturn } from '@/services/actionTypes' +import type { OmegaOrder } from '@prisma/client' + +export async function createOmegaOrderAction(): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['OMEGA_ORDER_CREATE']] + }) + if (!authorized) return createActionError(status) + + return safeServerCall(createOmegaOrder) +} + +export async function readCurrentOmegaOrderAction(): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['OMEGA_ORDER_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readCurrentOmegaOrder()) +} diff --git a/src/services/omegaOrder/create.ts b/src/services/omegaOrder/create.ts index d2c2ddaed..14ad8f697 100644 --- a/src/services/omegaOrder/create.ts +++ b/src/services/omegaOrder/create.ts @@ -2,7 +2,7 @@ import '@pn-server-only' import { readCurrentOmegaOrder } from './read' import { AutomaticallyIncreaseOrder } from './ConfigVars' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { GroupType } from '@prisma/client' import type { PrismaPromise } from '@prisma/client' diff --git a/src/services/omegaOrder/read.ts b/src/services/omegaOrder/read.ts index 8ea8dfae4..38c509554 100644 --- a/src/services/omegaOrder/read.ts +++ b/src/services/omegaOrder/read.ts @@ -1,7 +1,7 @@ 'use server' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { OmegaOrder } from '@prisma/client' /** diff --git a/src/services/omegaid/actions.ts b/src/services/omegaid/actions.ts new file mode 100644 index 000000000..e104af06f --- /dev/null +++ b/src/services/omegaid/actions.ts @@ -0,0 +1,32 @@ +'use server' + +import { createActionError } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { ServerError } from '@/services/error' +import { generateOmegaId } from '@/services/omegaid/generate' +import type { ActionReturn } from '@/services/actionTypes' + +export async function generateOmegaIdAction(): Promise> { + const { user, authorized, status } = await getUser({ + userRequired: true, + }) + + if (!authorized) return createActionError(status) + + const token = generateOmegaId(user) + + return { + success: true, + data: token, + } +} + +export async function readOmegaJWTPublicKey(): Promise { + const key = process.env.JWT_PUBLIC_KEY + + if (!key) { + throw new ServerError('INVALID CONFIGURATION', 'The JWT_PUBLIC_KEY must be set') + } + + return key +} diff --git a/src/services/omegaid/compress.ts b/src/services/omegaid/compress.ts index 4e1df7f40..4cda9fa29 100644 --- a/src/services/omegaid/compress.ts +++ b/src/services/omegaid/compress.ts @@ -1,6 +1,6 @@ -import { JWT_ISSUER } from '@/lib/jwt/ConfigVars' -import type { ActionReturn, ActionReturnError } from '@/actions/Types' -import type { OmegaIdJWT } from '@/services/omegaid/Types' +import { JWT_ISSUER } from '@/lib/jwt/constants' +import type { ActionReturn, ActionError } from '@/services/actionTypes' +import type { OmegaIdJWT } from '@/services/omegaid/types' /** * This file handles compression and decompression of omegaID @@ -81,7 +81,7 @@ export function decompressOmegaId(rawdata: string): ActionReturn { const headerJSONString = JSON.stringify(header) const headerB64String = encodeBase64Url(headerJSONString) - const errorReturn: ActionReturnError = { + const errorReturn: ActionError = { success: false, errorCode: 'JWT INVALID', httpCode: 400, diff --git a/src/services/omegaid/generate.ts b/src/services/omegaid/generate.ts index 546efd20f..275ea246a 100644 --- a/src/services/omegaid/generate.ts +++ b/src/services/omegaid/generate.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { OmegaIdExpiryTime } from './ConfigVars' import { generateJWT } from '@/jwt/jwt' -import type { OmegaId } from './Types' +import type { OmegaId } from './types' export function generateOmegaId(user: OmegaId): string { diff --git a/src/services/omegaid/Types.ts b/src/services/omegaid/types.ts similarity index 70% rename from src/services/omegaid/Types.ts rename to src/services/omegaid/types.ts index 3d82cbe70..f9cabef89 100644 --- a/src/services/omegaid/Types.ts +++ b/src/services/omegaid/types.ts @@ -1,4 +1,4 @@ -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' export type OmegaId = Pick diff --git a/src/services/omegaquotes/actions.ts b/src/services/omegaquotes/actions.ts new file mode 100644 index 000000000..e4e486bee --- /dev/null +++ b/src/services/omegaquotes/actions.ts @@ -0,0 +1,42 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createQuote } from '@/services/omegaquotes/create' +import { readQuotesPage } from '@/services/omegaquotes/read' +import { createOmegaquotesValidation } from '@/services/omegaquotes/validation' +import type { OmegaquoteCursor, OmegaquoteFiltered } from '@/services/omegaquotes/types' +import type { ReadPageInput } from '@/lib/paging/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateOmegaguotesTypes } from '@/services/omegaquotes/validation' +import type { OmegaQuote } from '@prisma/client' + +export async function createQuoteAction( + rawdata: FormData | CreateOmegaguotesTypes['Type'] +): Promise> { + const { user, status, authorized } = await getUser({ + requiredPermissions: [['OMEGAQUOTES_WRITE']], + userRequired: true, + }) + if (!authorized) return createActionError(status) + + const parse = createOmegaquotesValidation.typeValidate(rawdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + const results = await safeServerCall(() => createQuote(user.id, data)) + + return results +} + +export async function readQuotesPageAction( + readPageInput: ReadPageInput +): Promise> { + //TODO: REFACTOR when new permission system is working + const { status, authorized } = await getUser({ + requiredPermissions: [['OMEGAQUOTES_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readQuotesPage(readPageInput)) +} diff --git a/src/services/omegaquotes/create.ts b/src/services/omegaquotes/create.ts index d26da895d..ae9714d69 100644 --- a/src/services/omegaquotes/create.ts +++ b/src/services/omegaquotes/create.ts @@ -1,8 +1,8 @@ import '@pn-server-only' import { createOmegaquotesValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import { NotificationMethods } from '@/services/notifications/methods' +import { prisma } from '@/prisma/client' +import { notificationOperations } from '@/services/notifications/operations' import type { CreateOmegaguotesTypes } from './validation' import type { OmegaQuote } from '@prisma/client' @@ -28,7 +28,7 @@ export async function createQuote( } })) - NotificationMethods.createSpecial.newClient().execute({ + notificationOperations.createSpecial({ params: { special: 'NEW_OMEGAQUOTE', }, @@ -36,7 +36,6 @@ export async function createQuote( title: 'Ny Omegaquote♪', message: `${results.quote}\n - ${results.author}`, }, - session: null, bypassAuth: true, }) diff --git a/src/services/omegaquotes/read.ts b/src/services/omegaquotes/read.ts index 4b5b729f6..b0d477632 100644 --- a/src/services/omegaquotes/read.ts +++ b/src/services/omegaquotes/read.ts @@ -2,9 +2,9 @@ import '@pn-server-only' import { omegaQuoteFilterSelection } from './CofigVars' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ReadPageInput } from '@/lib/paging/Types' -import type { OmegaquoteCursor, OmegaquoteFiltered } from '@/services/omegaquotes/Types' +import { prisma } from '@/prisma/client' +import type { ReadPageInput } from '@/lib/paging/types' +import type { OmegaquoteCursor, OmegaquoteFiltered } from '@/services/omegaquotes/types' export async function readQuotesPage( { page }: ReadPageInput diff --git a/src/services/omegaquotes/Types.ts b/src/services/omegaquotes/types.ts similarity index 100% rename from src/services/omegaquotes/Types.ts rename to src/services/omegaquotes/types.ts diff --git a/src/services/permissions/actions.ts b/src/services/permissions/actions.ts new file mode 100644 index 000000000..5ede64e3d --- /dev/null +++ b/src/services/permissions/actions.ts @@ -0,0 +1,10 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { permissionOperations } from '@/services/permissions/operations' + +export const readPermissionOfGroupAction = makeAction(permissionOperations.readPermissionsOfGroup) +export const readPermissionMatrixAction = makeAction(permissionOperations.readPermissionMatrix) +export const readDefaultPermissionsAction = makeAction(permissionOperations.readDefaultPermissions) +export const updateDefaultPermissionsAction = makeAction(permissionOperations.updateDefaultPermissions) +export const updateGroupPermissionAction = makeAction(permissionOperations.updateGroupPermission) diff --git a/src/services/permissions/auth.ts b/src/services/permissions/auth.ts new file mode 100644 index 000000000..2714a93ed --- /dev/null +++ b/src/services/permissions/auth.ts @@ -0,0 +1,12 @@ +import { RequireNothing } from '@/auth/auther/RequireNothing' +import { RequirePermission } from '@/auth/auther/RequirePermission' + + +export const permissionsAuth = { + readGroupPermissions: RequirePermission.staticFields({ permission: 'PERMISSION_GROUP_READ' }), + readPermissionMatrix: RequirePermission.staticFields({ permission: 'PERMISSION_GROUP_READ' }), + updateGroupPermission: RequirePermission.staticFields({ permission: 'PERMISSION_GROUP_ADMIN' }), + + readDefaultPermissions: RequireNothing.staticFields({}), + updateDefaultPermissions: RequirePermission.staticFields({ permission: 'PERMISSION_DEFAULT_ADMIN' }), +} diff --git a/src/services/permissions/auther.ts b/src/services/permissions/auther.ts deleted file mode 100644 index d91f9fefe..000000000 --- a/src/services/permissions/auther.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { RequireNothing } from '@/auth/auther/RequireNothing' -import { RequirePermission } from '@/auth/auther/RequirePermission' - - -export namespace PermissionAuthers { - export const readGroupPermissions = RequirePermission.staticFields({ permission: 'PERMISSION_GROUP_READ' }) - export const readPermissionMatrix = RequirePermission.staticFields({ permission: 'PERMISSION_GROUP_READ' }) - export const updateGroupPermission = RequirePermission.staticFields({ permission: 'PERMISSION_GROUP_ADMIN' }) - - export const readDefaultPermissions = RequireNothing.staticFields({}) - export const updateDefaultPermissions = RequirePermission.staticFields({ permission: 'PERMISSION_DEFAULT_ADMIN' }) -} diff --git a/src/services/permissions/config.ts b/src/services/permissions/constants.ts similarity index 99% rename from src/services/permissions/config.ts rename to src/services/permissions/constants.ts index 02d8ac710..0c8de89ee 100644 --- a/src/services/permissions/config.ts +++ b/src/services/permissions/constants.ts @@ -1,5 +1,5 @@ import type { Permission } from '@prisma/client' -import type { PermissionInfo } from './Types' +import type { PermissionInfo } from './types' export const permissionCategories = [ 'omega quotes', @@ -20,7 +20,7 @@ export const permissionCategories = [ 'applications', ] as const satisfies string[] -export const PermissionConfig = { +export const permissionConfig = { OMEGAQUOTES_READ: { name: 'Les OmegaQuotes', description: 'kan lese OmegaQuotes', diff --git a/src/services/permissions/methods.ts b/src/services/permissions/operations.ts similarity index 73% rename from src/services/permissions/methods.ts rename to src/services/permissions/operations.ts index d02f2ce2d..b2f65921f 100644 --- a/src/services/permissions/methods.ts +++ b/src/services/permissions/operations.ts @@ -1,33 +1,29 @@ import '@pn-server-only' -import { PermissionAuthers } from './auther' -import { ServiceMethod } from '@/services/ServiceMethod' +import { permissionsAuth } from './auth' +import { defineOperation } from '@/services/serviceOperation' import { ServerOnlyAuther } from '@/auth/auther/RequireServer' import { invalidateAllUserSessionData, invalidateManyUserSessionData } from '@/services/auth/invalidateSession' -import { groupsWithRelationsIncluder } from '@/services/groups/config' -import { checkGroupValidity, inferGroupName } from '@/services/groups/methods' +import { groupsWithRelationsIncluder } from '@/services/groups/constants' +import { checkGroupValidity, inferGroupName } from '@/services/groups/operations' import { Permission } from '@prisma/client' import { z } from 'zod' -export namespace PermissionMethods { - - export const readDefaultPermissions = ServiceMethod({ - auther: () => PermissionAuthers.readDefaultPermissions.dynamicFields({}), - method: async ({ prisma }) => +export const permissionOperations = { + readDefaultPermissions: defineOperation({ + authorizer: () => permissionsAuth.readDefaultPermissions.dynamicFields({}), + operation: async ({ prisma }) => (await prisma.defaultPermission.findMany()).map(perm => perm.permission) - }) + }), - export const readPermissionsOfUser = ServiceMethod({ - auther: ServerOnlyAuther, + readPermissionsOfUser: defineOperation({ + authorizer: ServerOnlyAuther, paramsSchema: z.object({ userId: z.number(), }), - method: async ({ prisma, session, params }) => { + operation: async ({ prisma, params }) => { const [defaultPermissions, groupPermissions] = await Promise.all([ - readDefaultPermissions.client(prisma).execute({ - bypassAuth: true, - session, - }), + permissionOperations.readDefaultPermissions({}), prisma.membership.findMany({ where: { userId: params.userId, @@ -47,27 +43,27 @@ export namespace PermissionMethods { const groupPermsFlatten = groupPermissions.map(group => group.group.permissions.map(permission => permission.permission) ).flat() - const ret = defaultPermissions.concat(groupPermsFlatten) + const ret: Permission[] = defaultPermissions.concat(groupPermsFlatten) return ret.filter((permission, i) => ret.indexOf(permission) === i) } - }) + }), - export const readPermissionsOfGroup = ServiceMethod({ - auther: () => PermissionAuthers.readGroupPermissions.dynamicFields({}), + readPermissionsOfGroup: defineOperation({ + authorizer: () => permissionsAuth.readGroupPermissions.dynamicFields({}), paramsSchema: z.object({ groupId: z.number() }), - method: async ({ prisma, params }) => (await prisma.groupPermission.findMany({ + operation: async ({ prisma, params }) => (await prisma.groupPermission.findMany({ where: { groupId: params.groupId } })).map(permission => permission.permission) - }) + }), - export const readPermissionMatrix = ServiceMethod({ - auther: () => PermissionAuthers.readPermissionMatrix.dynamicFields({}), - method: async ({ prisma }) => { + readPermissionMatrix: defineOperation({ + authorizer: () => permissionsAuth.readPermissionMatrix.dynamicFields({}), + operation: async ({ prisma }) => { const groupsPermission = await prisma.group.findMany({ include: { ...groupsWithRelationsIncluder, @@ -81,14 +77,14 @@ export namespace PermissionMethods { permissions: group.permissions.map(permission => permission.permission) })) } - }) + }), - export const updateDefaultPermissions = ServiceMethod({ - auther: () => PermissionAuthers.updateDefaultPermissions.dynamicFields({}), + updateDefaultPermissions: defineOperation({ + authorizer: () => permissionsAuth.updateDefaultPermissions.dynamicFields({}), dataSchema: z.object({ permissions: z.nativeEnum(Permission).array(), }), - method: async ({ prisma, data }) => { + operation: async ({ prisma, data }) => { await prisma.defaultPermission.deleteMany({ where: { permission: { @@ -109,10 +105,10 @@ export namespace PermissionMethods { return data.permissions } - }) + }), - export const updateGroupPermission = ServiceMethod({ - auther: () => PermissionAuthers.updateGroupPermission.dynamicFields({}), + updateGroupPermission: defineOperation({ + authorizer: () => permissionsAuth.updateGroupPermission.dynamicFields({}), paramsSchema: z.object({ groupId: z.number(), permission: z.nativeEnum(Permission), @@ -120,7 +116,7 @@ export namespace PermissionMethods { dataSchema: z.object({ value: z.boolean() }), - method: async ({ prisma, params, data }) => { + operation: async ({ prisma, params, data }) => { if (data.value) { await prisma.groupPermission.create({ data: { @@ -160,5 +156,5 @@ export namespace PermissionMethods { return data.value } - }) + }), } diff --git a/src/services/permissions/Types.ts b/src/services/permissions/types.ts similarity index 76% rename from src/services/permissions/Types.ts rename to src/services/permissions/types.ts index 2cd716bde..3e60402d9 100644 --- a/src/services/permissions/Types.ts +++ b/src/services/permissions/types.ts @@ -1,4 +1,4 @@ -import type { permissionCategories } from './config' +import type { permissionCategories } from './constants' export type PermissionCategory = typeof permissionCategories[number] export type PermissionInfo = { diff --git a/src/services/screens/actions.ts b/src/services/screens/actions.ts new file mode 100644 index 000000000..d345de45e --- /dev/null +++ b/src/services/screens/actions.ts @@ -0,0 +1,70 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { RequirePermission } from '@/auth/auther/RequirePermission' +import { Session } from '@/auth/session/Session' +import { createScreen } from '@/services/screens/create' +import { destroyScreen } from '@/services/screens/destroy' +import { readScreen, readScreens } from '@/services/screens/read' +import { movePageInScreen, updateScreen } from '@/services/screens/update' +import { createScreenValidation, updateScreenValidation } from '@/services/screens/validation' +import type { ScreenPageMoveDirection } from '@/services/screens/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateScreenTypes, UpdateScreenTypes } from '@/services/screens/validation' +import type { Screen } from '@prisma/client' + +export const adminScreenAuther = RequirePermission.staticFields({ permission: 'SCREEN_ADMIN' }) +export const readScreenAuther = RequirePermission.staticFields({ permission: 'SCREEN_READ' }) + +export async function createScreenAction(formdata: CreateScreenTypes['Type']): Promise> { + const authRes = adminScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) + if (!authRes.authorized) return createActionError(authRes.status) + + const parse = createScreenValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createScreen(data)) +} + +export async function destroyScreenAction(id: number): Promise> { + const authRes = adminScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) + if (!authRes.authorized) return createActionError(authRes.status) + + return await safeServerCall(() => destroyScreen(id)) +} + +export async function readScreenAction(id: number): Promise> { + const authRes = readScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) + if (!authRes.authorized) return createActionError(authRes.status) + + return await safeServerCall(() => readScreen(id)) +} + +export async function readScreensAction(): Promise> { + const authRes = readScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) + if (!authRes.authorized) return createActionError(authRes.status) + + return await safeServerCall(() => readScreens()) +} + +export async function updateScreenAction(id: number, formdata: UpdateScreenTypes['Type']): Promise> { + const authRes = adminScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) + if (!authRes.authorized) return createActionError(authRes.status) + + const parse = updateScreenValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => updateScreen(id, data)) +} + +export async function movePageInScreenAction( + id: {screen: number, page: number}, + direction: ScreenPageMoveDirection +): Promise> { + const authRes = adminScreenAuther.dynamicFields({}).auth(await Session.fromNextAuth()) + if (!authRes.authorized) return createActionError(authRes.status) + + return await safeServerCall(() => movePageInScreen(id, direction)) +} diff --git a/src/services/screens/create.ts b/src/services/screens/create.ts index 0f8c08c6e..0689abfd6 100644 --- a/src/services/screens/create.ts +++ b/src/services/screens/create.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { createScreenValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { CreateScreenTypes } from './validation' import type { Screen } from '@prisma/client' diff --git a/src/services/screens/destroy.ts b/src/services/screens/destroy.ts index 8da83205b..fe8a336e7 100644 --- a/src/services/screens/destroy.ts +++ b/src/services/screens/destroy.ts @@ -1,5 +1,5 @@ import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' export async function destroyScreen(id: number): Promise { await prismaCall(() => prisma.screen.delete({ diff --git a/src/services/screens/pages/actions.ts b/src/services/screens/pages/actions.ts new file mode 100644 index 000000000..fadec1980 --- /dev/null +++ b/src/services/screens/pages/actions.ts @@ -0,0 +1,68 @@ +'use server' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' +import { createPage } from '@/services/screens/pages/create' +import { destroyPage } from '@/services/screens/pages/destroy' +import { readPage, readPages } from '@/services/screens/pages/read' +import { updatePage } from '@/services/screens/pages/update' +import { updatePageValidation } from '@/services/screens/pages/validation' +import { createScreenValidation } from '@/services/screens/validation' +import type { UpdatePageTypes } from '@/services/screens/pages/validation' +import type { ExpandedScreenPage } from '@/services/screens/pages/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { CreateScreenTypes } from '@/services/screens/validation' +import type { ScreenPage } from '@prisma/client' + +export async function createPageAction(formdata: CreateScreenTypes['Type']): Promise> { + const { status, authorized } = await getUser({ + requiredPermissions: [['SCREEN_ADMIN']] + }) + if (!authorized) return createActionError(status) + + const parse = createScreenValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => createPage(data)) +} + +export async function destroyPageAction(id: number): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCREEN_ADMIN']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => destroyPage(id)) +} + +export async function readPageAction(id: number): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCREEN_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readPage(id)) +} + +export async function readPagesAction(): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCREEN_READ']] + }) + if (!authorized) return createActionError(status) + + return await safeServerCall(() => readPages()) +} + +export async function updatePageAction(id: number, formdata: UpdatePageTypes['Type']): Promise> { + const { authorized, status } = await getUser({ + requiredPermissions: [['SCREEN_ADMIN']] + }) + if (!authorized) return createActionError(status) + + const parse = updatePageValidation.typeValidate(formdata) + if (!parse.success) return createZodActionError(parse) + const data = parse.data + + return await safeServerCall(() => updatePage(id, data)) +} diff --git a/src/services/screens/pages/create.ts b/src/services/screens/pages/create.ts index bc0874047..53f31a6a6 100644 --- a/src/services/screens/pages/create.ts +++ b/src/services/screens/pages/create.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { type CreatePageTypes, createPageValidation } from './validation' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { createCmsImage } from '@/services/cms/images/create' import { createCmsParagraph } from '@/services/cms/paragraphs/create' import { v4 } from 'uuid' diff --git a/src/services/screens/pages/destroy.ts b/src/services/screens/pages/destroy.ts index 6ba419784..43df11054 100644 --- a/src/services/screens/pages/destroy.ts +++ b/src/services/screens/pages/destroy.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' export async function destroyPage(id: number): Promise { diff --git a/src/services/screens/pages/read.ts b/src/services/screens/pages/read.ts index cbe8140c5..d0001cd10 100644 --- a/src/services/screens/pages/read.ts +++ b/src/services/screens/pages/read.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { screenPageIncluder } from './ConfigVars' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { ScreenPage } from '@prisma/client' -import type { ExpandedScreenPage } from './Types' +import type { ExpandedScreenPage } from './types' export async function readPage(id: number): Promise { return await prismaCall(() => prisma.screenPage.findUniqueOrThrow({ diff --git a/src/services/screens/pages/Types.ts b/src/services/screens/pages/types.ts similarity index 72% rename from src/services/screens/pages/Types.ts rename to src/services/screens/pages/types.ts index 2104395f0..6e1c9c882 100644 --- a/src/services/screens/pages/Types.ts +++ b/src/services/screens/pages/types.ts @@ -1,4 +1,4 @@ -import type { ExpandedCmsImage } from '@/services/cms/images/Types' +import type { ExpandedCmsImage } from '@/cms/images/types' import type { CmsParagraph, ScreenPage } from '@prisma/client' diff --git a/src/services/screens/pages/update.ts b/src/services/screens/pages/update.ts index 6ea09f115..8cee2b348 100644 --- a/src/services/screens/pages/update.ts +++ b/src/services/screens/pages/update.ts @@ -1,5 +1,5 @@ import '@pn-server-only' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import { prismaCall } from '@/services/prismaCall' import { updatePageValidation } from '@/services/screens/pages/validation' import type { UpdatePageTypes } from '@/services/screens/pages/validation' diff --git a/src/services/screens/read.ts b/src/services/screens/read.ts index eec8d8c02..3d557f69c 100644 --- a/src/services/screens/read.ts +++ b/src/services/screens/read.ts @@ -1,7 +1,7 @@ import { screenPageIncluder } from './pages/ConfigVars' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ExpandedScreen } from './Types' +import { prisma } from '@/prisma/client' +import type { ExpandedScreen } from './types' import type { Screen } from '@prisma/client' export async function readScreen(id: number): Promise { diff --git a/src/services/screens/Types.ts b/src/services/screens/types.ts similarity index 75% rename from src/services/screens/Types.ts rename to src/services/screens/types.ts index 877965875..be7596398 100644 --- a/src/services/screens/Types.ts +++ b/src/services/screens/types.ts @@ -1,4 +1,4 @@ -import type { ExpandedScreenPage } from './pages/Types' +import type { ExpandedScreenPage } from './pages/types' import type { Screen } from '@prisma/client' export type ExpandedScreen = Screen & { diff --git a/src/services/screens/update.ts b/src/services/screens/update.ts index f3fdc5fd4..79317db57 100644 --- a/src/services/screens/update.ts +++ b/src/services/screens/update.ts @@ -2,8 +2,8 @@ import { updateScreenValidation } from './validation' import { readScreen } from './read' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ScreenPageMoveDirection } from './Types' +import { prisma } from '@/prisma/client' +import type { ScreenPageMoveDirection } from './types' import type { UpdateScreenTypes } from './validation' import type { Screen } from '@prisma/client' diff --git a/src/actions/sendmail/send.ts b/src/services/sendmail/actions.ts similarity index 74% rename from src/actions/sendmail/send.ts rename to src/services/sendmail/actions.ts index 60a6e6282..27bc12211 100644 --- a/src/actions/sendmail/send.ts +++ b/src/services/sendmail/actions.ts @@ -1,10 +1,10 @@ 'use server' -import { safeServerCall } from '@/actions/safeServerCall' + +import { createActionError, createZodActionError, safeServerCall } from '@/services/actionError' +import { getUser } from '@/auth/session/getUser' import { sendMail as transportSendMail } from '@/services/notifications/email/send' -import { getUser } from '@/auth/getUser' -import { createActionError, createZodActionError } from '@/actions/error' import { sendEmailValidation } from '@/services/notifications/email/validation' -import type { ActionReturn } from '@/actions/Types' +import type { ActionReturn } from '@/services/actionTypes' export default async function sendMail(rawdata: FormData): Promise> { const { authorized, status } = await getUser({ diff --git a/src/actions/action.ts b/src/services/serverAction.ts similarity index 58% rename from src/actions/action.ts rename to src/services/serverAction.ts index 14a613db1..bb65b6100 100644 --- a/src/actions/action.ts +++ b/src/services/serverAction.ts @@ -1,84 +1,84 @@ import '@pn-server-only' -import { safeServerCall } from './safeServerCall' -import { Session } from '@/auth/Session' -import type { ActionReturn } from './Types' -import type { ServiceMethodType } from '@/services/ServiceMethod' +import { safeServerCall } from './actionError' +import { Session } from '@/auth/session/Session' +import type { ActionReturn } from './actionTypes' +import type { ServiceOperation } from '@/services/serviceOperation' import type { z } from 'zod' // This function is overloaded to allow for different combinations of parameters and data. -export function action( - serviceMethod: ServiceMethodType +export function makeAction( + serviceOperation: ServiceOperation ): () => Promise> -export function action< +export function makeAction< Return, ImplementationParamsSchema extends z.ZodTypeAny >( - serviceMethod: ServiceMethodType + serviceOperation: ServiceOperation ): ( implementationParams: { implementationParams: z.input }, ) => Promise> -export function action< +export function makeAction< Return, ParamsSchema extends z.ZodTypeAny >( - serviceMethod: ServiceMethodType + serviceOperation: ServiceOperation ): ( params: { params: z.input }, ) => Promise> -export function action< +export function makeAction< Return, DataSchema extends z.ZodTypeAny >( - serviceMethod: ServiceMethodType + serviceOperation: ServiceOperation ): ( data: { data: z.input } | FormData ) => Promise> -export function action< +export function makeAction< Return, ParamsSchema extends z.ZodTypeAny, ImplementationParamsSchema extends z.ZodTypeAny >( - serviceMethod: ServiceMethodType + serviceOperation: ServiceOperation ): ( implementationParams: { implementationParams: z.input }, params: { params: z.input }, ) => Promise> -export function action< +export function makeAction< Return, ParamsSchema extends z.ZodTypeAny, DataSchema extends z.ZodTypeAny >( - serviceMethod: ServiceMethodType + serviceOperation: ServiceOperation ): ( params: { params: z.input }, data: { data: z.input } | FormData ) => Promise> -export function action< +export function makeAction< Return, ImplementationParamsSchema extends z.ZodTypeAny, DataSchema extends z.ZodTypeAny >( - serviceMethod: ServiceMethodType + serviceOperation: ServiceOperation ): ( implementationParams: { implementationParams: z.input }, params: { params: unknown }, data: { data: z.input } | FormData ) => Promise> -export function action< +export function makeAction< Return, ImplementationParamsSchema extends z.ZodTypeAny, ParamsSchema extends z.ZodTypeAny, DataSchema extends z.ZodTypeAny >( - serviceMethod: ServiceMethodType + serviceOperation: ServiceOperation ): ( implementationParams: { implementationParams: z.input }, params: { params: z.input }, @@ -87,29 +87,29 @@ export function action< /** - * Turn a service method into suitable function for an action. + * Turn a service operation into suitable function for an action. * - * @param serviceMethod - The service method to create an action for. - * @returns - A function that takes in data (which may be FormData) and/or/nor parameters and calls the service method. + * @param serviceOperation - The service operation to create an action for. + * @returns - A function that takes in data (which may be FormData) and/or/nor parameters and calls the service operation. */ -export function action< +export function makeAction< Return, ParamsSchema extends z.ZodTypeAny | undefined = undefined, DataSchema extends z.ZodTypeAny | undefined = undefined, ImplementationParamsSchema extends z.ZodTypeAny | undefined = undefined >( - serviceMethod: ServiceMethodType + serviceOperation: ServiceOperation ) { // Letting the arguments to the actual function be unknown is safer as anything can be passed to it form the client. - // The action and service method will validate the parameter and data before it is used. + // The action and service operation will validate the parameter and data before it is used. // // For convinience this function is given a return type that is more specific. The return type is a function that - // has arguments witch match the underlying service method. This makes programming easier as intellisesne can + // has arguments witch match the underlying service operation. This makes programming easier as intellisesne can // help and errors are caught at compile time. const actionUnsafe = async ( implementationParams: { implementationParams: unknown }, params: { params: unknown }, - data: { data: unknown } | FormData + data: { data: unknown } | FormData, ) => { const session = await Session.fromNextAuth() @@ -127,26 +127,20 @@ export function action< } return safeServerCall( - () => { - console.log('Executing service method:', serviceMethod) - console.log('With params:', params) - console.log('With data:', processedData) - console.log('With implementation params:', implementationParams) - return serviceMethod.newClient().executeUnsafe({ - session, - params: params?.params, - data: processedData, - implementationParams: implementationParams?.implementationParams - }) - } + () => serviceOperation<'UNSAFE'>({ + session, + params: params?.params, + data: processedData, + implementationParams: implementationParams?.implementationParams + }) ) } - if (serviceMethod.implementationParamsSchema) { + if (serviceOperation.implementationParamsSchema) { return actionUnsafe } - if (serviceMethod.paramsSchema) { + if (serviceOperation.paramsSchema) { return actionUnsafe.bind(null, { implementationParams: undefined }) } diff --git a/src/services/serviceOperation.ts b/src/services/serviceOperation.ts new file mode 100644 index 000000000..a078f8aa0 --- /dev/null +++ b/src/services/serviceOperation.ts @@ -0,0 +1,408 @@ +import '@pn-server-only' +import { ParseError, Smorekopp } from './error' +import { prismaErrorWrapper } from './prismaCall' +import { prisma as globalPrisma } from '@/prisma/client' +import { Session } from '@/auth/session/Session' +import { zfd } from 'zod-form-data' +import { AsyncLocalStorage } from 'async_hooks' +import type { AutherResult } from '@/auth/auther/Auther' +import type { z } from 'zod' +import type { SessionMaybeUser } from '@/auth/session/Session' +import type { Prisma, PrismaClient } from '@prisma/client' + +export type InferedOrInput = + Schema extends undefined + ? object + : InferedOfInput extends 'INFERED' ? z.infer> : z.input> + +export type ParamsObject = + ParamsSchema extends undefined + ? object + : { params: InferedOrInput } + +export type ImplementationParamsObject< + ImplementationParamsSchema extends z.ZodTypeAny | undefined, + InferedOfInput extends 'INFERED' | 'INPUT' +> = + ImplementationParamsSchema extends undefined + ? object + : { implementationParams: InferedOrInput } + +export type DataObject = + DataSchema extends undefined + ? object + : { data: InferedOrInput } + +/** + * This is the type for the argument that are passed to the execute operation of a service operation. + */ +export type ServiceOperationExecuteArgs< + Unsafe extends 'UNSAFE' | 'SAFE', + ParamsSchema extends z.ZodTypeAny | undefined, + DataSchema extends z.ZodTypeAny | undefined, + ImplementationParamsSchema extends z.ZodTypeAny | undefined +> = ( + Unsafe extends 'UNSAFE' ? { + params?: unknown, + implementationParams?: unknown, + data?: unknown, + } : ( + & ParamsObject + & ImplementationParamsObject + & DataObject + ) +) + +export type ServiceOperationOperation< + OpensTransaction extends boolean, + ParamsSchema extends z.ZodTypeAny | undefined, + DataSchema extends z.ZodTypeAny | undefined, + Return, +> = ( + args: + & ParamsObject + & DataObject + & ServiceOperationContext +) => Promise | Return + +export type SubServiceOperationConfig< + ParamsSchema extends z.ZodTypeAny | undefined, + DataSchema extends z.ZodTypeAny | undefined, + ParamsSchemaImplementationFields extends object | undefined, + DataSchemaImplementationFields extends object | undefined, + OperationImplementationParams extends object | undefined, + Return, + OpensTransaction extends boolean = false, +> = { + paramsSchema?: ((implementationFields: ParamsSchemaImplementationFields) => ParamsSchema) | undefined, + dataSchema?: ((implementationFields: DataSchemaImplementationFields) => DataSchema) | undefined, + opensTransaction?: OpensTransaction, + operation: (implementationParams: OperationImplementationParams) => + ServiceOperationOperation +} + +export type AutherGetter< + OpensTransaction extends boolean, + ParamsSchema extends z.ZodTypeAny | undefined, + DataSchema extends z.ZodTypeAny | undefined, + ImplementationParamsSchema extends z.ZodTypeAny | undefined +> = ( + args: + & ParamsObject + & ImplementationParamsObject + & DataObject + & ServiceOperationContext +) => AutherResult | Promise + +export type ServiceOperationImplementationConfig< + OpensTransaction extends boolean, + ImplementationParamsSchema extends z.ZodTypeAny | undefined, + ParamsSchema extends z.ZodTypeAny | undefined, + DataSchema extends z.ZodTypeAny | undefined, + ParamsSchemaImplementationFields extends object | undefined, + DataSchemaImplementationFields extends object | undefined, + OperationImplementationFields extends object | undefined, +> = { + implementationParamsSchema: ImplementationParamsSchema, + authorizer: AutherGetter, + paramsSchemaImplementationFields: ParamsSchemaImplementationFields + dataSchemaImplementationFields: DataSchemaImplementationFields + operationImplementationFields: OperationImplementationFields +} + + +/** + * This is the type for the prisma client that is passed to the service operation. + * It can't simply be PrismaClient because it can be usefull to use a service operation + * inside a transaction. In that case, the prisma client is a Prisma.TransactionClient. + * The caveat is that a Prisma.TransactionClient can't be used to open a new transaction + * so if the service operation opens a transaction, the prisma client can only be a PrismaClient. + */ +export type PrismaPossibleTransaction< + OpensTransaction extends boolean +> = OpensTransaction extends true ? PrismaClient : Prisma.TransactionClient + +/** + * In addition to custom data arguments, every service operation receives a context object. + */ +type ServiceOperationContext = { + prisma: PrismaPossibleTransaction, + session: SessionMaybeUser, + bypassAuth: boolean, +} + +/** + * Async local storage is used to store the context of the current service operation call. + * All service operations called within the context of another service operation will + * inherit the context of the parent service operation, unless explicitly overridden. + * + * Read more about async local storage here: https://nodejs.org/api/async_context.html + */ +const asyncLocalStorage = new AsyncLocalStorage() + +/** + * Runs a callback with a specific service operation context. + * + * @param contextOverride Partial context to override the current context with. + * @param callback The callback to run with the context. + * @returns The return value of the callback. + */ +function withContext( + contextOverride: Partial, + callback: (context: ServiceOperationContext) => T, +): T { + const localContext = asyncLocalStorage.getStore() + + const context: ServiceOperationContext = { + prisma: contextOverride.prisma ?? localContext?.prisma ?? globalPrisma, + session: contextOverride.session ?? localContext?.session ?? Session.empty(), + bypassAuth: contextOverride.bypassAuth ?? localContext?.bypassAuth ?? false, + } + + return asyncLocalStorage.run(context, () => callback(context)) +} + + +/** + * This is the return type of the ServiceOperation function. It contains a client function that can be used + * to pass a specific prisma client to the service operation, and a newClient function that can be used to + * pass the global prisma client to the service operation. + * + * TypeScript is smart enough to infer the behaviour of the return functons without the need to excplitly + * type the return type of the ServiceOperation function, but it is done so for the sake of clarity. + */ +export type ServiceOperation< + OpensTransaction extends boolean, + Return, + ParamsSchema extends z.ZodTypeAny | undefined = undefined, + DataSchema extends z.ZodTypeAny | undefined = undefined, + ImplementationParamsSchema extends z.ZodTypeAny | undefined = undefined +> = { + /** + * Pass a specific prisma client to the service operation. Usefull when using the service operation inside a transaction. + * @note + * @param client + */ + ( + args: + & ServiceOperationExecuteArgs + & Partial> + ): Promise, + paramsSchema?: ParamsSchema, + dataSchema?: DataSchema, + implementationParamsSchema?: ImplementationParamsSchema, +} + +export function defineSubOperation< + Return, + OpensTransaction extends boolean = false, + ParamsSchema extends z.ZodTypeAny | undefined = undefined, + DataSchema extends z.ZodTypeAny | undefined = undefined, + ParamsSchemaImplementationFields extends object | undefined = undefined, + DataSchemaImplementationFields extends object | undefined = undefined, + OperationImplementationParams extends object | undefined = undefined, +>( + serviceOperationConfig: SubServiceOperationConfig< + ParamsSchema, + DataSchema, + ParamsSchemaImplementationFields, + DataSchemaImplementationFields, + OperationImplementationParams, + Return, + OpensTransaction + > +) { + const implement = ( + implementationArgs: ServiceOperationImplementationConfig< + OpensTransaction, + ImplementationParamsSchema, + ParamsSchema, + DataSchema, + ParamsSchemaImplementationFields, + DataSchemaImplementationFields, + OperationImplementationParams + > + ): ServiceOperation => { + const expectedArgsArePresent = ( + args: ServiceOperationExecuteArgs<'UNSAFE', ParamsSchema, DataSchema, ImplementationParamsSchema> + ): args is ServiceOperationExecuteArgs<'SAFE', ParamsSchema, DataSchema, ImplementationParamsSchema> => { + const paramsMatch = Boolean(args.params) === Boolean(serviceOperationConfig.paramsSchema) + const dataMatches = Boolean(args.data) === Boolean(serviceOperationConfig.dataSchema) + const implementationParamsMatch = + Boolean(args.implementationParams) === Boolean(implementationArgs.operationImplementationFields) + return paramsMatch && dataMatches && implementationParamsMatch + } + + // Guard to check if the prisma client can be used for this service operation. + const isAppropriateClient = ( + prisma: PrismaClient | Prisma.TransactionClient + ): prisma is PrismaPossibleTransaction => + !serviceOperationConfig.opensTransaction || '$transaction' in prisma + + const executeOperation = async ({ + implementationParams, params, data, ...context + }: + &ServiceOperationExecuteArgs<'UNSAFE', ParamsSchema, DataSchema, ImplementationParamsSchema> + & Partial> + ): Promise => { + const args = { + implementationParams, + params, + data, + } + + if (args.params) { + if (!serviceOperationConfig.paramsSchema) { + throw new Smorekopp( + 'BAD PARAMETERS', 'Service operation recieved params, but has no params schema.' + ) + } + const paramsSchema = serviceOperationConfig.paramsSchema( + implementationArgs.paramsSchemaImplementationFields! + ) + if (!paramsSchema) { + throw new Smorekopp( + 'BAD PARAMETERS', 'Service operation recieved params, but has no params schema.' + ) + } + const paramsParse = paramsSchema.safeParse(args.params) + + if (!paramsParse.success) { + console.log(paramsParse) // TODO: This needs to be returned to give good error message. + throw new Smorekopp('BAD PARAMETERS', 'Invalid params passed to service operation.') + } + + args.params = paramsParse.data + } + if (args.data) { + if (!serviceOperationConfig.dataSchema) { + throw new Smorekopp( + 'BAD PARAMETERS', 'Service operation recieved data, but has no data schema. 1' + ) + } + const dataSchema = serviceOperationConfig.dataSchema( + implementationArgs.dataSchemaImplementationFields + ) + if (!dataSchema) { + throw new Smorekopp( + 'BAD PARAMETERS', 'Service operation recieved data, but has no data schema. 2' + ) + } + const dataParse = zfd.formData(dataSchema).safeParse(args.data) + if (!dataParse.success) { + console.log(dataParse) + throw new ParseError(dataParse) + } + args.data = dataParse.data + } + + if (args.implementationParams) { + if (!implementationArgs.implementationParamsSchema) { + throw new Smorekopp( + 'BAD PARAMETERS', + 'Service operation recieved implementation params, but has no implementation params schema.' + ) + } + const implementationParamsSchema = implementationArgs.implementationParamsSchema + const implementationParamsParse = implementationParamsSchema.safeParse( + args.implementationParams + ) + if (!implementationParamsParse.success) { + // TODO: This needs to be returned to give good error message. + console.log(implementationParamsParse) + throw new Smorekopp('BAD PARAMETERS', 'Invalid implementation params passed to service operation.') + } + args.implementationParams = implementationParamsParse.data + } + + if (!expectedArgsArePresent(args)) { + throw new Smorekopp( + 'SERVER ERROR', + 'Service operation recieved invalid arguments.' + ) + } + + // Then, get the context (which includes the prisma client, the session and the bypassAuth flag). + // If a context override is provided, use it. Otherwise, use the context from the async local storage. + // If there is no context in the async local storage, use global defaults. + return withContext(context, async ({ prisma, bypassAuth, session }) => { + if (!isAppropriateClient(prisma)) { + throw new Smorekopp( + 'SERVER ERROR', + 'Service operation that opens a transaction cannot be called from within a transaction.', + ) + } + + // Then, authorize user. + // This has to be done after the validation because the authorizer might use the data to authorize the user. + if (!bypassAuth) { + if (!implementationArgs.authorizer) { + throw new Smorekopp( + 'UNAUTHENTICATED', + 'This service operation is not externally callable.' + ) + } + + const authorizer = await implementationArgs.authorizer({ ...args, prisma, bypassAuth, session }) + const authResult = authorizer.auth(session) + + if (!authResult.authorized) { + throw new Smorekopp(authResult.status, authResult.getErrorMessage) + } + } + + // Finally, call the operation. + return prismaErrorWrapper(() => { + const operation = serviceOperationConfig.operation(args.implementationParams) + + return operation({ ...args, prisma, bypassAuth, session }) + }) + }) + } + + executeOperation.paramsSchema = serviceOperationConfig.paramsSchema + ? serviceOperationConfig.paramsSchema(implementationArgs.paramsSchemaImplementationFields) + : undefined + executeOperation.dataSchema = serviceOperationConfig.dataSchema + ? serviceOperationConfig.dataSchema(implementationArgs.dataSchemaImplementationFields) + : undefined + executeOperation.implementationParamsSchema = implementationArgs.implementationParamsSchema + + return executeOperation + } + return { implement } +} + +export function defineOperation< + OpensTransaction extends boolean, + Return, + ParamsSchema extends z.ZodTypeAny | undefined = undefined, + DataSchema extends z.ZodTypeAny | undefined = undefined, +>({ paramsSchema, dataSchema, opensTransaction, authorizer, operation }: { + paramsSchema?: ParamsSchema, + dataSchema?: DataSchema, + opensTransaction?: OpensTransaction, + authorizer: AutherGetter, + operation: ServiceOperationOperation +}): ServiceOperation { + return defineSubOperation< + Return, + OpensTransaction, + ParamsSchema, + DataSchema, + undefined, + undefined, + undefined + >({ + opensTransaction, + operation: () => operation, + paramsSchema: paramsSchema !== undefined ? () => paramsSchema : undefined, + dataSchema: dataSchema !== undefined ? () => dataSchema : undefined, + }).implement({ + authorizer, + implementationParamsSchema: undefined, + dataSchemaImplementationFields: undefined, + paramsSchemaImplementationFields: undefined, + operationImplementationFields: undefined, + }) +} diff --git a/src/services/shop/actions.ts b/src/services/shop/actions.ts new file mode 100644 index 000000000..e831bbae5 --- /dev/null +++ b/src/services/shop/actions.ts @@ -0,0 +1,19 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { productOperations } from '@/services/shop/product/operations' +import { shopOperations } from '@/services/shop/shop/operations' + +export const readProductsAction = makeAction(productOperations.readMany) +export const readProductAction = makeAction(productOperations.read) +export const createProductAction = makeAction(productOperations.create) +export const updateProductAction = makeAction(productOperations.update) + +export const createProductForShopAction = makeAction(productOperations.createForShop) +export const updateProductForShopAction = makeAction(productOperations.updateForShop) + +export const createShopProductConnectionAction = makeAction(productOperations.createShopConnection) + +export const readShopsAction = makeAction(shopOperations.readMany) +export const readShopAction = makeAction(shopOperations.read) +export const createShopAction = makeAction(shopOperations.create) diff --git a/src/services/shop/product/auth.ts b/src/services/shop/product/auth.ts new file mode 100644 index 000000000..9dde385b5 --- /dev/null +++ b/src/services/shop/product/auth.ts @@ -0,0 +1,8 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const productAuth = { + read: RequirePermission.staticFields({ permission: 'PRODUCT_READ' }), + create: RequirePermission.staticFields({ permission: 'PRODUCT_ADMIN' }), + update: RequirePermission.staticFields({ permission: 'PRODUCT_ADMIN' }), + createShopConnection: RequirePermission.staticFields({ permission: 'SHOP_ADMIN' }), +} diff --git a/src/services/shop/product/authers.ts b/src/services/shop/product/authers.ts deleted file mode 100644 index db48ebf94..000000000 --- a/src/services/shop/product/authers.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace ProductAuthers { - export const read = RequirePermission.staticFields({ permission: 'PRODUCT_READ' }) - export const create = RequirePermission.staticFields({ permission: 'PRODUCT_ADMIN' }) - export const update = RequirePermission.staticFields({ permission: 'PRODUCT_ADMIN' }) - export const createShopConnection = RequirePermission.staticFields({ permission: 'SHOP_ADMIN' }) -} diff --git a/src/services/shop/product/methods.ts b/src/services/shop/product/operations.ts similarity index 67% rename from src/services/shop/product/methods.ts rename to src/services/shop/product/operations.ts index 4da287b6f..889b7a54e 100644 --- a/src/services/shop/product/methods.ts +++ b/src/services/shop/product/operations.ts @@ -1,31 +1,31 @@ -import { ProductAuthers } from './authers' -import { ProductSchemas } from './schemas' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import '@pn-server-only' import { ServerError } from '@/services/error' import { z } from 'zod' -import type { ExtendedProduct } from './Types' +import type { ExtendedProduct } from './types' +import { productAuth } from './auth' +import { productSchemas } from './schemas' -export namespace ProductMethods { - export const create = ServiceMethod({ - auther: () => ProductAuthers.create.dynamicFields({}), - dataSchema: ProductSchemas.create, - method: async ({ prisma, data }) => prisma.product.create({ +export const productOperations = { + create: defineOperation({ + authorizer: () => productAuth.create.dynamicFields({}), + dataSchema: productSchemas.create, + operation: async ({ prisma, data }) => prisma.product.create({ data: { ...data, barcode: convertBarcode(data.barcode), name: data.name.toUpperCase() } }) - }) + }), - export const createForShop = ServiceMethod({ - auther: () => ProductAuthers.create.dynamicFields({}), + createForShop: defineOperation({ + authorizer: () => productAuth.create.dynamicFields({}), paramsSchema: z.object({ shopId: z.number(), }), - dataSchema: ProductSchemas.createForShop, - method: async ({ prisma, params, data }) => prisma.product.create({ + dataSchema: productSchemas.createForShop, + operation: async ({ prisma, params, data }) => prisma.product.create({ data: { name: data.name.toUpperCase(), description: data.description, @@ -42,12 +42,12 @@ export namespace ProductMethods { }, }, }) - }) + }), - export const createShopConnection = ServiceMethod({ - auther: () => ProductAuthers.createShopConnection.dynamicFields({}), - dataSchema: ProductSchemas.createShopConnection, - method: async ({ prisma, data }) => prisma.shopProduct.create({ + createShopConnection: defineOperation({ + authorizer: () => productAuth.createShopConnection.dynamicFields({}), + dataSchema: productSchemas.createShopConnection, + operation: async ({ prisma, data }) => prisma.shopProduct.create({ data: { shop: { connect: { @@ -62,19 +62,19 @@ export namespace ProductMethods { price: data.price, } }) - }) + }), - export const readMany = ServiceMethod({ - auther: () => ProductAuthers.read.dynamicFields({}), - method: async ({ prisma }) => await prisma.product.findMany() - }) + readMany: defineOperation({ + authorizer: () => productAuth.read.dynamicFields({}), + operation: async ({ prisma }) => await prisma.product.findMany() + }), - export const read = ServiceMethod({ - auther: () => ProductAuthers.read.dynamicFields({}), + read: defineOperation({ + authorizer: () => productAuth.read.dynamicFields({}), paramsSchema: z.object({ productId: z.number(), }), - method: async ({ prisma, params }) => await prisma.product.findUniqueOrThrow({ + operation: async ({ prisma, params }) => await prisma.product.findUniqueOrThrow({ where: { id: params.productId }, @@ -86,12 +86,12 @@ export namespace ProductMethods { } } }) - }) + }), - export const readByBarCode = ServiceMethod({ - auther: () => ProductAuthers.read.dynamicFields({}), - dataSchema: ProductSchemas.readByBarCode, - method: async ({ prisma, data }): Promise => { + readByBarCode: defineOperation({ + authorizer: () => productAuth.read.dynamicFields({}), + dataSchema: productSchemas.readByBarCode, + operation: async ({ prisma, data }): Promise => { if (!data.barcode) { throw new ServerError('BAD PARAMETERS', 'Barcode is required.') } @@ -128,12 +128,12 @@ export namespace ProductMethods { return ret } - }) + }), - export const update = ServiceMethod({ - auther: () => ProductAuthers.update.dynamicFields({}), - dataSchema: ProductSchemas.update, - method: async ({ prisma, data }) => prisma.product.update({ + update: defineOperation({ + authorizer: () => productAuth.update.dynamicFields({}), + dataSchema: productSchemas.update, + operation: async ({ prisma, data }) => prisma.product.update({ where: { id: data.productId, }, @@ -143,16 +143,16 @@ export namespace ProductMethods { barcode: convertBarcode(data.barcode), }, }) - }) + }), - export const updateForShop = ServiceMethod({ - auther: () => ProductAuthers.update.dynamicFields({}), - dataSchema: ProductSchemas.updateForShop, + updateForShop: defineOperation({ + authorizer: () => productAuth.update.dynamicFields({}), + dataSchema: productSchemas.updateForShop, paramsSchema: z.object({ shopId: z.number(), productId: z.number(), }), - method: async ({ prisma, params, data }) => prisma.product.update({ + operation: async ({ prisma, params, data }) => prisma.product.update({ where: { id: params.productId, }, @@ -176,7 +176,7 @@ export namespace ProductMethods { }, }, }) - }) + }), } export function convertBarcode(barcode?: string | number) { diff --git a/src/services/shop/product/schemas.ts b/src/services/shop/product/schemas.ts index 63771f1b5..1d4e48275 100644 --- a/src/services/shop/product/schemas.ts +++ b/src/services/shop/product/schemas.ts @@ -1,55 +1,54 @@ -import { zpn } from '@/lib/fields/zpn' +import { Zpn } from '@/lib/fields/zpn' import { convertPrice } from '@/lib/money/convert' import { z } from 'zod' -export namespace ProductSchemas { - const fields = z.object({ - shopId: z.coerce.number().int(), - name: z.string().min(3), - description: z.string(), - price: z.coerce.number().int().min(1).transform((val) => convertPrice(val)), - barcode: z.string().or(z.number()).optional(), - active: zpn.checkboxOrBoolean({ label: 'Active' }), - productId: z.coerce.number().int(), - }) - - export const create = fields.pick({ +const baseSchema = z.object({ + shopId: z.coerce.number().int(), + name: z.string().min(3), + description: z.string(), + price: z.coerce.number().int().min(1).transform((val) => convertPrice(val)), + barcode: z.string().or(z.number()).optional(), + active: Zpn.checkboxOrBoolean({ label: 'Active' }), + productId: z.coerce.number().int(), +}) + +export const productSchemas = { + create: baseSchema.pick({ name: true, description: true, barcode: true, - }) + }), - export const update = fields.pick({ + update: baseSchema.pick({ productId: true, name: true, description: true, barcode: true, - }) + }), - export const readByBarCode = fields.pick({ + readByBarCode: baseSchema.pick({ barcode: true, shopId: true, - }) + }), - export const createForShop = fields.pick({ + createForShop: baseSchema.pick({ name: true, description: true, price: true, barcode: true, - }) + }), - export const updateForShop = fields.pick({ + updateForShop: baseSchema.pick({ name: true, description: true, price: true, barcode: true, active: true, - }) + }), - export const createShopConnection = fields.pick({ + createShopConnection: baseSchema.pick({ shopId: true, productId: true, price: true, - }) + }), } - diff --git a/src/services/shop/product/Types.ts b/src/services/shop/product/types.ts similarity index 100% rename from src/services/shop/product/Types.ts rename to src/services/shop/product/types.ts diff --git a/src/services/shop/purchase/authers.ts b/src/services/shop/purchase/auth.ts similarity index 69% rename from src/services/shop/purchase/authers.ts rename to src/services/shop/purchase/auth.ts index b640cf651..8b24f0563 100644 --- a/src/services/shop/purchase/authers.ts +++ b/src/services/shop/purchase/auth.ts @@ -1,8 +1,8 @@ import { RequirePermissionAndDynamicPermission } from '@/auth/auther/RequirePermissionAndDynamicPermission' -export namespace PurchaseAuthers { - export const createByStudentCard = RequirePermissionAndDynamicPermission.staticFields({ +export const purchaseAuth = { + createByStudentCard: RequirePermissionAndDynamicPermission.staticFields({ permission: 'PURCHASE_CREATE_ONBEHALF', dynamicPermission: 'PURCHASE_CREATE', errorMessage: 'Brukeren har ikke lov til å handle i butikker.' diff --git a/src/services/shop/purchase/methods.ts b/src/services/shop/purchase/operations.ts similarity index 76% rename from src/services/shop/purchase/methods.ts rename to src/services/shop/purchase/operations.ts index 5d08ba983..d8d5c5ba1 100644 --- a/src/services/shop/purchase/methods.ts +++ b/src/services/shop/purchase/operations.ts @@ -1,23 +1,22 @@ import '@pn-server-only' -import { PurchaseAuthers } from './authers' -import { PurchaseSchemas } from './schemas' +import { purchaseAuth } from './auth' +import { purchaseSchemas } from './schemas' import { ServerError } from '@/services/error' -import { ServiceMethod } from '@/services/ServiceMethod' -import { UserMethods } from '@/services/users/methods' -import { UserConfig } from '@/services/users/config' -import { PermissionMethods } from '@/services/permissions/methods' +import { defineOperation } from '@/services/serviceOperation' +import { userOperations } from '@/services/users/operations' +import { permissionOperations } from '@/services/permissions/operations' +import { userFilterSelection } from '@/services/users/constants' import { PurchaseMethod } from '@prisma/client' -export namespace PurchaseMethods { - export const createByStudentCard = ServiceMethod({ - auther: async ({ data }) => { +export const purchaseOperations = { + createByStudentCard: defineOperation({ + authorizer: async ({ data }) => { let user try { - user = await UserMethods.read.newClient().execute({ + user = await userOperations.read({ params: { studentCard: data.studentCard, }, - session: null, bypassAuth: true, }) } catch (e) { @@ -27,20 +26,19 @@ export namespace PurchaseMethods { throw e } - const permissions = await PermissionMethods.readPermissionsOfUser.newClient().execute({ - session: null, + const permissions = await permissionOperations.readPermissionsOfUser({ bypassAuth: true, params: { userId: user.id, }, }) - return PurchaseAuthers.createByStudentCard.dynamicFields({ + return purchaseAuth.createByStudentCard.dynamicFields({ permissions, }) }, - dataSchema: PurchaseSchemas.createFromStudentCard, - method: async ({ prisma, data }) => { + dataSchema: purchaseSchemas.createFromStudentCard, + operation: async ({ prisma, data }) => { if (data.products.length === 0) { throw new ServerError('BAD PARAMETERS', 'The list of products to buy cannot be empty') } @@ -49,7 +47,7 @@ export namespace PurchaseMethods { where: { studentCard: data.studentCard, }, - select: UserConfig.filterSelection + select: userFilterSelection, }) // Find the price of the different products @@ -106,5 +104,4 @@ export namespace PurchaseMethods { } } }) - } diff --git a/src/services/shop/purchase/schemas.ts b/src/services/shop/purchase/schemas.ts index a08a445bb..c3f2b2195 100644 --- a/src/services/shop/purchase/schemas.ts +++ b/src/services/shop/purchase/schemas.ts @@ -1,22 +1,21 @@ -import { UserSchemas } from '@/services/users/schemas' +import { studentCardSchema } from '@/services/users/schemas' import { z } from 'zod' -export namespace PurchaseSchemas { - const productsZodObject = z.array(z.object({ - id: z.number().int(), - quantity: z.number().int().min(1) - })) +const productSchema = z.array(z.object({ + id: z.number().int(), + quantity: z.number().int().min(1) +})) - const fields = z.object({ - shopId: z.coerce.number().int(), - studentCard: UserSchemas.studentCardZodValidation, - products: productsZodObject, - }) +const baseSchema = z.object({ + shopId: z.coerce.number().int(), + studentCard: studentCardSchema, + products: productSchema, +}) - export const createFromStudentCard = fields.pick({ +export const purchaseSchemas = { + createFromStudentCard: baseSchema.pick({ shopId: true, products: true, studentCard: true, }) } - diff --git a/src/services/shop/purchase/Types.ts b/src/services/shop/purchase/types.ts similarity index 100% rename from src/services/shop/purchase/Types.ts rename to src/services/shop/purchase/types.ts diff --git a/src/services/shop/shop/auth.ts b/src/services/shop/shop/auth.ts new file mode 100644 index 000000000..a20eb8750 --- /dev/null +++ b/src/services/shop/shop/auth.ts @@ -0,0 +1,6 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' + +export const shopAuth = { + read: RequirePermission.staticFields({ permission: 'SHOP_READ' }), + create: RequirePermission.staticFields({ permission: 'SHOP_ADMIN' }), +} diff --git a/src/services/shop/shop/authers.ts b/src/services/shop/shop/authers.ts deleted file mode 100644 index 63229066d..000000000 --- a/src/services/shop/shop/authers.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' - -export namespace ShopAuthers { - export const read = RequirePermission.staticFields({ permission: 'SHOP_READ' }) - export const create = RequirePermission.staticFields({ permission: 'SHOP_ADMIN' }) -} diff --git a/src/services/shop/shop/methods.ts b/src/services/shop/shop/operations.ts similarity index 55% rename from src/services/shop/shop/methods.ts rename to src/services/shop/shop/operations.ts index 59d86b79b..44ffdd5d8 100644 --- a/src/services/shop/shop/methods.ts +++ b/src/services/shop/shop/operations.ts @@ -1,30 +1,30 @@ -import { ShopAuthers } from './authers' -import { ShopSchemas } from './schema' -import { ServiceMethod } from '@/services/ServiceMethod' +import { defineOperation } from '@/services/serviceOperation' import '@pn-server-only' import { z } from 'zod' -import type { ExtendedShop } from './Types' +import type { ExtendedShop } from './types' +import { shopSchemas } from './schema' +import { shopAuth } from './auth' -export namespace ShopMethods { - export const create = ServiceMethod({ - dataSchema: ShopSchemas.create, - auther: () => ShopAuthers.create.dynamicFields({}), - method: async ({ prisma, data }) => prisma.shop.create({ +export const shopOperations = { + create: defineOperation({ + dataSchema: shopSchemas.create, + authorizer: () => shopAuth.create.dynamicFields({}), + operation: async ({ prisma, data }) => prisma.shop.create({ data }) - }) + }), - export const readMany = ServiceMethod({ - auther: () => ShopAuthers.read.dynamicFields({}), - method: ({ prisma }) => prisma.shop.findMany(), - }) + readMany: defineOperation({ + authorizer: () => shopAuth.read.dynamicFields({}), + operation: ({ prisma }) => prisma.shop.findMany(), + }), - export const read = ServiceMethod({ - auther: () => ShopAuthers.read.dynamicFields({}), + read: defineOperation({ + authorizer: () => shopAuth.read.dynamicFields({}), paramsSchema: z.object({ shopId: z.number(), }), - method: async ({ prisma, params }): Promise => { + operation: async ({ prisma, params }): Promise => { const results = await prisma.shop.findFirst({ where: { id: params.shopId @@ -52,6 +52,6 @@ export namespace ShopMethods { return ret } - }) + }), } diff --git a/src/services/shop/shop/schema.ts b/src/services/shop/shop/schema.ts index c82135a4d..5214f3b51 100644 --- a/src/services/shop/shop/schema.ts +++ b/src/services/shop/shop/schema.ts @@ -1,13 +1,12 @@ import { z } from 'zod' -export namespace ShopSchemas { +const baseSchema = z.object({ + name: z.string().min(3), + description: z.string(), +}) - const fields = z.object({ - name: z.string().min(3), - description: z.string(), - }) - - export const create = fields.pick({ +export const shopSchemas = { + create: baseSchema.pick({ name: true, description: true, }) diff --git a/src/services/shop/shop/Types.ts b/src/services/shop/shop/types.ts similarity index 65% rename from src/services/shop/shop/Types.ts rename to src/services/shop/shop/types.ts index a0a6972dc..a264687c0 100644 --- a/src/services/shop/shop/Types.ts +++ b/src/services/shop/shop/types.ts @@ -1,5 +1,5 @@ import type { Shop } from '@prisma/client' -import type { ExtendedProduct } from '@/services/shop/product/Types' +import type { ExtendedProduct } from '@/services/shop/product/types' export type ExtendedShop = Shop & { diff --git a/src/services/store/createFile.ts b/src/services/store/createFile.ts index 003d81032..4b96b3192 100644 --- a/src/services/store/createFile.ts +++ b/src/services/store/createFile.ts @@ -3,7 +3,7 @@ import { ServerError } from '@/services/error' import { v4 as uuid } from 'uuid' import { join } from 'path' import { mkdir, writeFile } from 'fs/promises' -import type { StoreLocations } from './StoreLocations' +import type { StoreLocations } from './types' /** * Create a file in the store volume diff --git a/src/services/store/destroyFile.ts b/src/services/store/destroyFile.ts index cf530f878..e258677fd 100644 --- a/src/services/store/destroyFile.ts +++ b/src/services/store/destroyFile.ts @@ -2,7 +2,7 @@ import '@pn-server-only' import { ServerError } from '@/services/error' import { unlink } from 'fs/promises' import { join } from 'path' -import type { StoreLocations } from './StoreLocations' +import type { StoreLocations } from './types' function isErrorWithCode(error: unknown): error is { code: string } { return typeof error === 'object' && error !== null && 'code' in error diff --git a/src/services/store/StoreLocations.ts b/src/services/store/types.ts similarity index 100% rename from src/services/store/StoreLocations.ts rename to src/services/store/types.ts diff --git a/src/services/users/actions.ts b/src/services/users/actions.ts new file mode 100644 index 000000000..17de12e36 --- /dev/null +++ b/src/services/users/actions.ts @@ -0,0 +1,44 @@ +'use server' + +import { makeAction } from '@/services/serverAction' +import { groupOperations } from '@/services/groups/operations' +import { userOperations } from '@/services/users/operations' + +/** + * A action that creates a user by the given data. It will also hash the password + * @param rawdata - The user to create + * @returns - The created user + */ +export const createUserAction = makeAction(userOperations.create) + +/** + * Action to destroy a user by the given id + * @param id - The id of the user to destroy + * @returns + */ +export const destroyUserAction = makeAction(userOperations.destroy) + +/** + * A action to read a page of users with the given details (filtering) + * @param readPageInput - This is a) the page to read and b) the details to filter by like + * name and groups + * @returns + */ +export const readUserPageAction = makeAction(userOperations.readPage) + +/** + * Action meant to read the profile of a user. + * A profile is a user with more information about them attached. + * @param username - The username of the user to read + * @returns - The profile of the user + */ +export const readUserProfileAction = makeAction(userOperations.readProfile) + +export const readUserAction = makeAction(userOperations.read) + +export const readGroupsForPageFilteringAction = makeAction(groupOperations.readGroupsExpanded) + +export const updateUserAction = makeAction(userOperations.update) +export const registerNewEmailAction = makeAction(userOperations.registerNewEmail) +export const registerUser = makeAction(userOperations.register) +export const registerStudentCardInQueueAction = makeAction(userOperations.registerStudentCardInQueue) diff --git a/src/services/users/auth.ts b/src/services/users/auth.ts new file mode 100644 index 000000000..6ca87888c --- /dev/null +++ b/src/services/users/auth.ts @@ -0,0 +1,25 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' +import { RequireUserFieldOrPermission } from '@/auth/auther/RequireUserFieldOrPermission' +import { RequireUserId } from '@/auth/auther/RequireUserId' +import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' +import { RequireUsernameOrPermission } from '@/auth/auther/RequireUsernameOrPermission' + +export const userAuth = { + readProfile: RequireUsernameOrPermission.staticFields({ permission: 'USERS_READ' }), + read: RequireUserFieldOrPermission.staticFields({ permission: 'USERS_READ' }), + readOrNull: RequireUserFieldOrPermission.staticFields({ permission: 'USERS_READ' }), + readPage: RequirePermission.staticFields({ permission: 'USERS_READ' }), + create: RequirePermission.staticFields({ permission: 'USERS_CREATE' }), + connectStudentCard: RequirePermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }), + registerStudentCardInQueue: RequireUserIdOrPermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }), + registerNewEmail: RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }), + updatePassword: RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }), + update: RequirePermission.staticFields({ permission: 'USERS_UPDATE' }), + + // TODO: Implement method for updating profile, + // IDEA: profile = a user can do it themselvs. Just user - only an admin can do it + updateProfile: RequireUsernameOrPermission.staticFields({ permission: 'USERS_UPDATE' }), + + register: RequireUserId.staticFields({}), + destroy: RequirePermission.staticFields({ permission: 'USERS_DESTROY' }), +} diff --git a/src/services/users/authers.ts b/src/services/users/authers.ts deleted file mode 100644 index fba65d30d..000000000 --- a/src/services/users/authers.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { RequirePermission } from '@/auth/auther/RequirePermission' -import { RequireUserFieldOrPermission } from '@/auth/auther/RequireUserFieldOrPermission' -import { RequireUserId } from '@/auth/auther/RequireUserId' -import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission' -import { RequireUsernameOrPermission } from '@/auth/auther/RequireUsernameOrPermission' - -export namespace UserAuthers { - export const readProfile = RequireUsernameOrPermission.staticFields({ permission: 'USERS_READ' }) - export const read = RequireUserFieldOrPermission.staticFields({ permission: 'USERS_READ' }) - export const readOrNull = RequireUserFieldOrPermission.staticFields({ permission: 'USERS_READ' }) - export const readPage = RequirePermission.staticFields({ permission: 'USERS_READ' }) - export const create = RequirePermission.staticFields({ permission: 'USERS_CREATE' }) - export const connectStudentCard = RequirePermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }) - export const registerStudentCardInQueue = - RequireUserIdOrPermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }) - export const registerNewEmail = RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }) - export const updatePassword = RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }) - export const update = RequirePermission.staticFields({ permission: 'USERS_UPDATE' }) - - // TODO: Implement method for updating profile, - // IDEA: profile = a user can do it themselvs. Just user - only an admin can do it - export const updateProfile = RequireUsernameOrPermission.staticFields({ permission: 'USERS_UPDATE' }) - - export const register = RequireUserId.staticFields({}) - export const destroy = RequirePermission.staticFields({ permission: 'USERS_DESTROY' }) -} diff --git a/src/services/users/config.ts b/src/services/users/config.ts deleted file mode 100644 index 2fdcd0d31..000000000 --- a/src/services/users/config.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { createSelection } from '@/services/createSelection' -import type { Prisma, User, SEX } from '@prisma/client' - -export namespace UserConfig { - export const maxNumberOfGroupsInFilter = 7 - export const studentCardRegistrationExpiry = 2 // minutter - - // TODO: This needs to be divived into seperate filters, depending on how much information is needed - export const fieldsToExpose = [ - 'id', - 'username', - 'firstname', - 'lastname', - 'email', - 'emailVerified', - 'mobile', - 'createdAt', - 'updatedAt', - 'acceptedTerms', - 'sex', - 'allergies', - ] as const satisfies (keyof User)[] - - export const filterSelection = createSelection([...fieldsToExpose]) - - export const standardMembershipSelection = [ - { - group: { - groupType: 'CLASS' - } - }, - { - group: { - groupType: 'OMEGA_MEMBERSHIP_GROUP' - } - }, - { - group: { - groupType: 'STUDY_PROGRAMME' - } - }, - ] satisfies Prisma.MembershipWhereInput[] - - - export const sexConfig = { - MALE: { - title: 'Broder', - pronoun: 'Hands', - label: 'Mann', - }, - FEMALE: { - title: 'Syster', - pronoun: 'Hendes', - label: 'Kvinne', - }, - OTHER: { - title: 'Søsken', - pronoun: 'Hends', - label: 'Annet', - } - } as const satisfies { [key in SEX]: { title: string, pronoun: string, label: string } } -} diff --git a/src/services/users/constants.ts b/src/services/users/constants.ts new file mode 100644 index 000000000..fd21e4d5e --- /dev/null +++ b/src/services/users/constants.ts @@ -0,0 +1,60 @@ +import { createSelection } from '@/services/createSelection' +import type { Prisma, User, SEX } from '@prisma/client' + +export const maxNumberOfGroupsInFilter = 7 +export const studentCardRegistrationExpiry = 2 // minutter + +// TODO: This needs to be divived into seperate filters, depending on how much information is needed +export const userFieldsToExpose = [ + 'id', + 'username', + 'firstname', + 'lastname', + 'email', + 'emailVerified', + 'mobile', + 'createdAt', + 'updatedAt', + 'acceptedTerms', + 'sex', + 'allergies', +] as const satisfies (keyof User)[] + +export const userFilterSelection = createSelection([...userFieldsToExpose]) + +export const standardMembershipSelection = [ + { + group: { + groupType: 'CLASS' + } + }, + { + group: { + groupType: 'OMEGA_MEMBERSHIP_GROUP' + } + }, + { + group: { + groupType: 'STUDY_PROGRAMME' + } + }, +] satisfies Prisma.MembershipWhereInput[] + + +export const sexConfig = { + MALE: { + title: 'Broder', + pronoun: 'Hands', + label: 'Mann', + }, + FEMALE: { + title: 'Syster', + pronoun: 'Hendes', + label: 'Kvinne', + }, + OTHER: { + title: 'Søsken', + pronoun: 'Hends', + label: 'Annet', + } +} as const satisfies { [key in SEX]: { title: string, pronoun: string, label: string } } diff --git a/src/services/users/methods.ts b/src/services/users/operations.ts similarity index 80% rename from src/services/users/methods.ts rename to src/services/users/operations.ts index c5a28cf0c..89dc9b37f 100644 --- a/src/services/users/methods.ts +++ b/src/services/users/operations.ts @@ -1,35 +1,40 @@ import '@pn-server-only' -import { UserAuthers } from './authers' -import { UserConfig } from './config' -import { NotificationSubscriptionMethods } from '@/services/notifications/subscription/methods' +import { userSchemas } from './schemas' +import { userAuth } from './auth' +import { + maxNumberOfGroupsInFilter, + standardMembershipSelection, + studentCardRegistrationExpiry, + userFilterSelection +} from './constants' +import { imageOperations } from '@/services/images/operations' +import { notificationSubscriptionOperations } from '@/services/notifications/subscription/operations' import { readMembershipsOfUser } from '@/services/groups/memberships/read' import { NTNUEmailDomain } from '@/services/mail/mailAddressExternal/ConfigVars' import { sendVerifyEmail } from '@/services/notifications/email/systemMail/verifyEmail' import { updateUserOmegaMembershipGroup } from '@/services/groups/omegaMembershipGroups/update' import { sendUserInvitationEmail } from '@/services/notifications/email/systemMail/userInvitivation' import { readOmegaMembershipGroup } from '@/services/groups/omegaMembershipGroups/read' -import { UserSchemas } from '@/services/users/schemas' -import { ServiceMethod } from '@/services/ServiceMethod' -import { ImageMethods } from '@/services/images/methods' +import { defineOperation } from '@/services/serviceOperation' import { readPageInputSchemaObject } from '@/lib/paging/schema' import { ServerError } from '@/services/error' import { getMembershipFilter } from '@/auth/getMembershipFilter' import { cursorPageingSelection } from '@/lib/paging/cursorPageingSelection' -import { hashAndEncryptPassword } from '@/auth/password' +import { hashAndEncryptPassword } from '@/auth/passwordHash' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' -import { PermissionMethods } from '@/services/permissions/methods' +import { permissionOperations } from '@/services/permissions/operations' import { z } from 'zod' -import type { UserPagingReturn } from './Types' +import type { UserPagingReturn } from './types' -export namespace UserMethods { +export const userOperations = { /** * This Method creates an user by invitation, and sends the invitation email. * WARNING: This should not be used to create users registered by Feide. */ - export const create = ServiceMethod({ - dataSchema: UserSchemas.create, - auther: () => UserAuthers.create.dynamicFields({}), - method: async ({ prisma, data }) => { + create: defineOperation({ + dataSchema: userSchemas.create, + authorizer: () => userAuth.create.dynamicFields({}), + operation: async ({ prisma, data }) => { const omegaMembership = await readOmegaMembershipGroup('EXTERNAL') const omegaOrder = await readCurrentOmegaOrder() @@ -54,56 +59,55 @@ export namespace UserMethods { return user } - }) + }), - export const read = ServiceMethod({ + read: defineOperation({ paramsSchema: z.object({ username: z.string().optional(), id: z.coerce.number().optional(), email: z.string().optional(), studentCard: z.string().optional(), }), - auther: ({ params }) => UserAuthers.read.dynamicFields(params), - method: async ({ prisma, params }) => await prisma.user.findUniqueOrThrow({ + authorizer: ({ params }) => userAuth.read.dynamicFields(params), + operation: async ({ prisma, params }) => await prisma.user.findUniqueOrThrow({ where: { id: params.id, ...params }, - select: UserConfig.filterSelection + select: userFilterSelection }) - }) + }), - export const readOrNull = ServiceMethod({ + readOrNull: defineOperation({ paramsSchema: z.object({ username: z.string().optional(), id: z.coerce.number().optional(), email: z.string().optional(), studentCard: z.string().optional(), }), - auther: ({ params }) => UserAuthers.read.dynamicFields(params), - method: async ({ prisma, params }) => await prisma.user.findUnique({ + authorizer: ({ params }) => userAuth.read.dynamicFields(params), + operation: async ({ prisma, params }) => await prisma.user.findUnique({ where: { id: params.id, // This is a bit wierd, but now ts is satisfied. ...params }, - select: UserConfig.filterSelection + select: userFilterSelection }) - }) + }), - export const readProfile = ServiceMethod({ + readProfile: defineOperation({ paramsSchema: z.object({ username: z.string(), }), - auther: ({ params }) => UserAuthers.readProfile.dynamicFields({ username: params.username }), - method: async ({ prisma, params, session }) => { - const defaultProfileImage = await ImageMethods.readSpecial.client(prisma).execute({ + authorizer: ({ params }) => userAuth.readProfile.dynamicFields({ username: params.username }), + operation: async ({ prisma, params }) => { + const defaultProfileImage = await imageOperations.readSpecial({ params: { special: 'DEFAULT_PROFILE_IMAGE' }, - session, }) const user = await prisma.user.findUniqueOrThrow({ where: { username: params.username.toLowerCase() }, select: { - ...UserConfig.filterSelection, + ...userFilterSelection, bio: true, image: true, }, @@ -113,8 +117,7 @@ export namespace UserMethods { })) const memberships = await readMembershipsOfUser(user.id) - const permissions = await PermissionMethods.readPermissionsOfUser.client(prisma).execute({ - session, + const permissions = await permissionOperations.readPermissionsOfUser({ bypassAuth: true, params: { userId: user.id @@ -123,9 +126,9 @@ export namespace UserMethods { return { user, memberships, permissions } } - }) + }), - export const readPage = ServiceMethod({ + readPage: defineOperation({ paramsSchema: readPageInputSchemaObject( z.number(), z.object({ @@ -143,12 +146,12 @@ export namespace UserMethods { }).nullable().optional() }) ), - auther: () => UserAuthers.readPage.dynamicFields({}), - method: async ({ prisma, params }): Promise => { + authorizer: () => userAuth.readPage.dynamicFields({}), + operation: async ({ prisma, params }): Promise => { const { page, details } = params.paging const words = details.partOfName.split(' ') - if (details.groups.length > UserConfig.maxNumberOfGroupsInFilter) { + if (details.groups.length > maxNumberOfGroupsInFilter) { throw new ServerError('BAD PARAMETERS', 'Too many groups in filter') } const groupSelection = details.selectedGroup ? [ @@ -160,7 +163,7 @@ export namespace UserMethods { const users = await prisma.user.findMany({ ...cursorPageingSelection(page), select: { - ...UserConfig.filterSelection, + ...userFilterSelection, memberships: { select: { admin: true, @@ -179,7 +182,7 @@ export namespace UserMethods { { AND: [ { - OR: UserConfig.standardMembershipSelection, + OR: standardMembershipSelection, }, getMembershipFilter('ACTIVE') ] @@ -250,13 +253,13 @@ export namespace UserMethods { } }) } - }) + }), - export const connectStudentCard = ServiceMethod({ - auther: () => UserAuthers.connectStudentCard.dynamicFields({}), - dataSchema: UserSchemas.connectStudentCard, + connectStudentCard: defineOperation({ + authorizer: () => userAuth.connectStudentCard.dynamicFields({}), + dataSchema: userSchemas.connectStudentCard, opensTransaction: true, - method: async ({ prisma, data }) => { + operation: async ({ prisma, data }) => { const currentQueue = await prisma.registerStudentCardQueue.findMany({ where: { expiry: { @@ -290,21 +293,21 @@ export namespace UserMethods { data: { studentCard: data.studentCard, }, - select: UserConfig.filterSelection, + select: userFilterSelection, }) ]) return result[1] } - }) + }), - export const registerStudentCardInQueue = ServiceMethod({ + registerStudentCardInQueue: defineOperation({ paramsSchema: z.object({ userId: z.number(), }), - auther: ({ params }) => UserAuthers.registerStudentCardInQueue.dynamicFields(params), - method: async (args) => { - const expiry = (new Date()).getTime() + UserConfig.studentCardRegistrationExpiry * 60 * 1000 + authorizer: ({ params }) => userAuth.registerStudentCardInQueue.dynamicFields(params), + operation: async (args) => { + const expiry = (new Date()).getTime() + studentCardRegistrationExpiry * 60 * 1000 await args.prisma.registerStudentCardQueue.upsert({ where: { userId: args.params.userId, @@ -322,26 +325,26 @@ export namespace UserMethods { } }) } - }) + }), - export const update = ServiceMethod({ + update: defineOperation({ paramsSchema: z.union([z.object({ id: z.number() }), z.object({ username: z.string() })]), - dataSchema: UserSchemas.update, - auther: () => UserAuthers.update.dynamicFields({}), - method: async ({ prisma: prisma_, params, data }) => prisma_.user.update({ + dataSchema: userSchemas.update, + authorizer: () => userAuth.update.dynamicFields({}), + operation: async ({ prisma: prisma_, params, data }) => prisma_.user.update({ where: params, data }) - }) + }), - export const updatePassword = ServiceMethod({ + updatePassword: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - dataSchema: UserSchemas.updatePassword, - auther: ({ params }) => UserAuthers.updatePassword.dynamicFields({ userId: params.id }), - method: async ({ prisma, data, params }) => { + dataSchema: userSchemas.updatePassword, + authorizer: ({ params }) => userAuth.updatePassword.dynamicFields({ userId: params.id }), + operation: async ({ prisma, data, params }) => { const passwordHash = await hashAndEncryptPassword(data.password) await prisma.credentials.update({ @@ -355,21 +358,21 @@ export namespace UserMethods { return null } - }) + }), - export const registerNewEmail = ServiceMethod({ + registerNewEmail: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - auther: ({ params }) => UserAuthers.registerNewEmail.dynamicFields({ userId: params.id }), - dataSchema: UserSchemas.registerNewEmail, - method: async ({ prisma, params, data }) => { + authorizer: ({ params }) => userAuth.registerNewEmail.dynamicFields({ userId: params.id }), + dataSchema: userSchemas.registerNewEmail, + operation: async ({ prisma, params, data }) => { const storedUser = await prisma.user.findUniqueOrThrow({ where: { id: params.id, }, select: { - ...UserConfig.filterSelection, + ...userFilterSelection, feideAccount: { select: { email: true, @@ -411,7 +414,7 @@ export namespace UserMethods { email: data.email, } } - }) + }), /** * This function completes the last step of user creation: registration. @@ -421,14 +424,14 @@ export namespace UserMethods { * @param rawdata - Registration data. * @returns null */ - export const register = ServiceMethod({ + register: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - dataSchema: UserSchemas.register, - auther: ({ params }) => UserAuthers.register.dynamicFields({ userId: params.id }), + dataSchema: userSchemas.register, + authorizer: ({ params }) => userAuth.register.dynamicFields({ userId: params.id }), opensTransaction: true, - method: async ({ prisma, data, params, session }) => { + operation: async ({ prisma, data, params }) => { const { sex, password, mobile, allergies } = data if (!password) throw new ServerError('BAD PARAMETERS', 'Passord er obligatorisk.') @@ -479,7 +482,7 @@ export namespace UserMethods { mobile, allergies, }, - select: UserConfig.filterSelection + select: userFilterSelection }), prisma.credentials.upsert({ where: { @@ -500,11 +503,10 @@ export namespace UserMethods { ]) try { - await NotificationSubscriptionMethods.createDefault.client(prisma).execute({ + await notificationSubscriptionOperations.createDefault({ params: { userId: params.id, }, - session, bypassAuth: true, }) } catch (error) { @@ -526,9 +528,10 @@ export namespace UserMethods { return results[0] } - }) - export const readUserWithBalance = ServiceMethod({ - auther: ({ params }) => UserAuthers.read.dynamicFields({ + }), + + readUserWithBalance: defineOperation({ + authorizer: ({ params }) => userAuth.read.dynamicFields({ username: params.username || '', }), paramsSchema: z.object({ @@ -537,7 +540,7 @@ export namespace UserMethods { email: z.string().optional(), studentCard: z.string().optional(), }), - method: async ({ prisma: prisma_, params }) => { + operation: async ({ prisma: prisma_, params }) => { const user = await prisma_.user.findFirstOrThrow({ where: params, include: { @@ -550,23 +553,23 @@ export namespace UserMethods { user, } } - }) + }), //TODO: Make soft delete? /** * This function deletes a user from the database. */ - export const destroy = ServiceMethod({ + destroy: defineOperation({ paramsSchema: z.object({ id: z.number(), }), - auther: () => UserAuthers.destroy.dynamicFields({}), - method: async ({ prisma, params }) => { + authorizer: () => userAuth.destroy.dynamicFields({}), + operation: async ({ prisma, params }) => { await prisma.user.delete({ where: { id: params.id, } }) } - }) + }), } diff --git a/src/services/users/schemas.ts b/src/services/users/schemas.ts index 00ab69647..0308e6501 100644 --- a/src/services/users/schemas.ts +++ b/src/services/users/schemas.ts @@ -1,72 +1,72 @@ -import { zpn } from '@/lib/fields/zpn' +import { Zpn } from '@/lib/fields/zpn' import { SEX } from '@prisma/client' import { z } from 'zod' -export namespace UserSchemas { - export const studentCardZodValidation = z.string() +export const studentCardSchema = z.string() - export const fields = z.object({ - username: z.string().max(50).min(2).toLowerCase(), - sex: z.nativeEnum(SEX).optional().nullable(), - email: z.string().max(200).min(2).email(), - emailVerified: z.string().datetime({}).optional().nullable(), - mobile: z.string().regex(/^\+?\d{4,20}$/, { message: 'Skriv kun tall, uten mellomrom.' }), - firstname: z.string().max(50).min(2), - lastname: z.string().max(50).min(2), - allergies: z.string().max(150).optional().nullable(), - studentCard: studentCardZodValidation, - password: z.string().max(50).min(12, { - // eslint-disable-next-line - message: 'Passoret må minst ha 12 tegn, en stor og en liten bokstav, et tall, en rune, to emojier, en musikk note, en magisk sopp og en dråpe smørekopp-blod (avsky).' - }), - confirmPassword: z.string().max(50).min(12), - acceptedTerms: zpn.checkboxOrBoolean({ - label: 'Accepted terms', - }).refine(value => value, 'Du må godta vilkårene for å bruke siden.'), - }) - const refinePassword = { - fcn: (data: { password?: string, confirmPassword?: string }) => data.password === data.confirmPassword, - message: 'Passordene må være like' - } +export const userSchema = z.object({ + username: z.string().max(50).min(2).toLowerCase(), + sex: z.nativeEnum(SEX).optional().nullable(), + email: z.string().max(200).min(2).email(), + emailVerified: z.string().datetime({}).optional().nullable(), + mobile: z.string().regex(/^\+?\d{4,20}$/, { message: 'Skriv kun tall, uten mellomrom.' }), + firstname: z.string().max(50).min(2), + lastname: z.string().max(50).min(2), + allergies: z.string().max(150).optional().nullable(), + studentCard: studentCardSchema, + password: z.string().max(50).min(12, { + // eslint-disable-next-line + message: 'Passoret må minst ha 12 tegn, en stor og en liten bokstav, et tall, en rune, to emojier, en musikk note, en magisk sopp og en dråpe smørekopp-blod (avsky).' + }), + confirmPassword: z.string().max(50).min(12), + acceptedTerms: Zpn.checkboxOrBoolean({ + label: 'Accepted terms', + }).refine(value => value, 'Du må godta vilkårene for å bruke siden.'), +}) +const refinePassword = { + fcn: (data: { password?: string, confirmPassword?: string }) => data.password === data.confirmPassword, + message: 'Passordene må være like' +} - export const create = fields.pick({ +export const userSchemas = { + create: userSchema.pick({ email: true, firstname: true, lastname: true, username: true, emailVerified: true, - }) + }), - export const update = fields.partial().pick({ + update: userSchema.partial().pick({ email: true, firstname: true, lastname: true, username: true, - }) + }), - export const register = fields.pick({ + register: userSchema.pick({ mobile: true, allergies: true, password: true, confirmPassword: true, sex: true, acceptedTerms: true, - }).refine(refinePassword.fcn, refinePassword.message) + }).refine(refinePassword.fcn, refinePassword.message), - export const updatePassword = fields.pick({ + updatePassword: userSchema.pick({ password: true, confirmPassword: true, - }).refine(refinePassword.fcn, refinePassword.message) + }).refine(refinePassword.fcn, refinePassword.message), - export const registerNewEmail = fields.pick({ + registerNewEmail: userSchema.pick({ email: true, - }) + }), - export const connectStudentCard = fields.pick({ + connectStudentCard: userSchema.pick({ studentCard: true, - }) + }), - export const verifyEmail = fields.pick({ + verifyEmail: userSchema.pick({ email: true, - }) + }), } diff --git a/src/services/users/Types.ts b/src/services/users/types.ts similarity index 89% rename from src/services/users/Types.ts rename to src/services/users/types.ts index 24459dfde..da198f5b1 100644 --- a/src/services/users/Types.ts +++ b/src/services/users/types.ts @@ -1,8 +1,8 @@ -import type { MembershipFiltered } from '@/services/groups/memberships/Types' -import type { UserConfig } from './config' +import type { userFieldsToExpose } from './constants' +import type { MembershipFiltered } from '@/services/groups/memberships/types' import type { OmegaMembershipLevel, User, Image, Permission } from '@prisma/client' -export type UserFiltered = Pick +export type UserFiltered = Pick export type StandardMembeships = { class?: number diff --git a/src/services/visibility/ConfigVars.ts b/src/services/visibility/ConfigVars.ts index 0928cbb58..b2bd7eb87 100644 --- a/src/services/visibility/ConfigVars.ts +++ b/src/services/visibility/ConfigVars.ts @@ -14,7 +14,7 @@ export const BypassPermissions = { export type BypassPermissions = typeof BypassPermissions[keyof typeof BypassPermissions] -export const PurposeTextsConfig = { +export const purposeTextsConfig = { IMAGE: 'Bilder', NEWS_ARTICLE: 'Nyheter', ARTICLE_CATEGORY: 'Artikkelkategorier', @@ -26,7 +26,7 @@ export const PurposeTextsConfig = { * Which permissions link to special visibility purposes * If the special visibility were to disappear, it will be regenerated from this. */ -export const SpecialVisibilityConfig = { +export const specialVisibilityConfig = { OMBUL: { regularLevel: 'OMBUL_READ', adminLevel: 'OMBUL_CREATE' diff --git a/src/actions/visibility/read.ts b/src/services/visibility/actions.ts similarity index 81% rename from src/actions/visibility/read.ts rename to src/services/visibility/actions.ts index 75f68363d..4585c2c29 100644 --- a/src/actions/visibility/read.ts +++ b/src/services/visibility/actions.ts @@ -1,29 +1,33 @@ 'use server' -import { safeServerCall } from '@/actions/safeServerCall' -import { createActionError } from '@/actions/error' -import { readVisibilityCollapsed } from '@/services/visibility/read' + +import { createActionError, safeServerCall } from '@/services/actionError' import { checkVisibility } from '@/auth/checkVisibility' -import { getUser } from '@/auth/getUser' -import { PurposeTextsConfig } from '@/services/visibility/ConfigVars' -import { GroupTypesConfig } from '@/services/groups/config' -import { GroupMethods } from '@/services/groups/methods' -import type { ExpandedGroup, GroupsStructured } from '@/services/groups/Types' -import type { GroupMatrix } from '@/services/visibility/Types' -import type { ActionReturn } from '@/actions/Types' +import { getUser } from '@/auth/session/getUser' +import { groupTypesConfig } from '@/services/groups/constants' +import { groupOperations } from '@/services/groups/operations' +import { purposeTextsConfig } from '@/services/visibility/ConfigVars' +import { readVisibilityCollapsed } from '@/services/visibility/read' +import type { ExpandedGroup, GroupsStructured } from '@/services/groups/types' +import type { ActionReturn } from '@/services/actionTypes' +import type { + GroupMatrix, + VisibilityLevelType, + VisibilityRequiermentForAdmin, + VisibilityStructuredForAdmin +} from '@/services/visibility/types' import type { GroupType } from '@prisma/client' -import type { VisibilityRequiermentForAdmin, VisibilityStructuredForAdmin } from './Types' export async function readVisibilityForAdminAction(id: number): Promise> { const [visibilityRes, groupsRes] = await Promise.all([ safeServerCall(() => readVisibilityCollapsed(id)), // TODO: Fix Authing here. The bypass should be false - safeServerCall(() => GroupMethods.readGroupsStructured.newClient().execute({ session: null, bypassAuth: true })) + safeServerCall(() => groupOperations.readGroupsStructured({ bypassAuth: true })) ]) if (!visibilityRes.success || !groupsRes.success) return createActionError('UNKNOWN ERROR', 'noe gikk galt') const visibility = visibilityRes.data const groups = groupsRes.data - const purpose = PurposeTextsConfig[visibility.purpose] + const purpose = purposeTextsConfig[visibility.purpose] if (!checkVisibility(await getUser(), visibility, 'ADMIN')) { return createActionError('UNAUTHORIZED', 'You do not have permission to admin this collection') @@ -65,7 +69,7 @@ function expandOneLevel( standardGroupings.forEach(groupType => { if (!groups[groupType]) return const standardRequriment: VisibilityRequiermentForAdmin = { - name: GroupTypesConfig[groupType].name, + name: groupTypesConfig[groupType].name, groups: groups[groupType].groups.map(group => ({ ...group, selected: false @@ -127,3 +131,11 @@ function groupTypeOfId(id: number, groups: GroupsStructured): GroupType { }) return type || 'MANUAL_GROUP' } + +export async function updateVisibilityAction( + level: VisibilityLevelType, + formdata: FormData +): Promise> { + console.log(formdata) + return { success: true, data: undefined } +} diff --git a/src/services/visibility/create.ts b/src/services/visibility/create.ts index 63365375e..de946d19c 100644 --- a/src/services/visibility/create.ts +++ b/src/services/visibility/create.ts @@ -1,9 +1,9 @@ import '@pn-server-only' import { updateVisibility } from './update' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { Visibility, VisibilityPurpose } from '@prisma/client' -import type { VisibilityLevelMatrices } from './Types' +import type { VisibilityLevelMatrices } from './types' /** * A function to create visibility diff --git a/src/services/visibility/destroy.ts b/src/services/visibility/destroy.ts index 4a63e3df2..bdfa7d279 100644 --- a/src/services/visibility/destroy.ts +++ b/src/services/visibility/destroy.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { ServerError } from '@/services/error' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' /** * Destroy a visibility. This will also destroy the visibility levels associated with the visibility, diff --git a/src/services/visibility/read.ts b/src/services/visibility/read.ts index 34170d7f9..824c1e163 100644 --- a/src/services/visibility/read.ts +++ b/src/services/visibility/read.ts @@ -1,10 +1,10 @@ import '@pn-server-only' -import { SpecialVisibilityConfig } from './ConfigVars' +import { specialVisibilityConfig } from './ConfigVars' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' -import prisma from '@/prisma' +import { prisma } from '@/prisma/client' import type { SpecialVisibilityPurpose, VisibilityRequirmenetGroup } from '@prisma/client' -import type { VisibilityCollapsed } from './Types' +import type { VisibilityCollapsed } from './types' const levelSelector = { select: { @@ -75,12 +75,12 @@ export async function readSpecialVisibility(specialPurpose: SpecialVisibilityPur published: true, regularLevel: { create: { - permission: SpecialVisibilityConfig[specialPurpose].regularLevel + permission: specialVisibilityConfig[specialPurpose].regularLevel } }, adminLevel: { create: { - permission: SpecialVisibilityConfig[specialPurpose].adminLevel + permission: specialVisibilityConfig[specialPurpose].adminLevel } }, }, diff --git a/src/services/visibility/Types.ts b/src/services/visibility/types.ts similarity index 54% rename from src/services/visibility/Types.ts rename to src/services/visibility/types.ts index 04aac260a..39e4bb48e 100644 --- a/src/services/visibility/Types.ts +++ b/src/services/visibility/types.ts @@ -1,4 +1,5 @@ -import type { Matrix } from '@/utils/checkMatrix' +import type { ExpandedGroup, GroupsStructured } from '@/services/groups/types' +import type { Matrix } from '@/lib/checkMatrix' import type { Permission, VisibilityPurpose } from '@prisma/client' export type GroupMatrix = Matrix @@ -28,3 +29,27 @@ export type VisibilityCollapsed = { regular: Permission | null, admin: Permission | null }) + +export type VisibilityRequiermentForAdmin = { + name: string + groups: (ExpandedGroup & { + selected: boolean + })[] +} + +export type VisibilityStructuredForAdmin = { + published: boolean + purpose: string +} & ( + { + type: 'REGULAR' + groups: GroupsStructured + regular: VisibilityRequiermentForAdmin[] + admin: VisibilityRequiermentForAdmin[] + } | { + type: 'SPECIAL' + message: string + regular: string + admin: string + } +) diff --git a/src/services/visibility/update.ts b/src/services/visibility/update.ts index a8af6f1f9..9b8697dd2 100644 --- a/src/services/visibility/update.ts +++ b/src/services/visibility/update.ts @@ -1,7 +1,7 @@ import '@pn-server-only' import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { VisibilityLevelMatrices } from './Types' +import { prisma } from '@/prisma/client' +import type { VisibilityLevelMatrices } from './types' export async function updateVisibility(id: number, data: VisibilityLevelMatrices): Promise { //TODO chack that the admin is a subset of the regular diff --git a/src/typings/global.d.ts b/src/typings/global.d.ts index 75fecbda8..23c3b36f9 100644 --- a/src/typings/global.d.ts +++ b/src/typings/global.d.ts @@ -1,10 +1,6 @@ import type { mailHandlerSingleton } from '@/services/notifications/email/mailHandler' -import type { Prisma, PrismaClient } from '@prisma/client' -import type { DefaultArgs } from '@prisma/client/runtime/library' declare global { - // eslint-disable-next-line no-var - var prisma: PrismaClient // eslint-disable-next-line no-var var mailHandler: ReturnType } diff --git a/src/typings/next-auth.d.ts b/src/typings/next-auth.d.ts index 424f9088a..3fef09a88 100644 --- a/src/typings/next-auth.d.ts +++ b/src/typings/next-auth.d.ts @@ -1,9 +1,9 @@ import 'next-auth' import 'next-auth/adapters' -import type { MembershipFiltered } from '@/services/groups/Types' +import type { MembershipFiltered } from '@/services/groups/types' import type { Permission } from '@prisma/client' -import type { UserFiltered } from '@/services/users/Types' +import type { UserFiltered } from '@/services/users/types' declare module 'next-auth' { // Normally we dissallow typing with empty objects, but in this case we diff --git a/tests/services/apiKeys.test.ts b/tests/services/apiKeys.test.ts index 5b5e212c0..024302ddb 100644 --- a/tests/services/apiKeys.test.ts +++ b/tests/services/apiKeys.test.ts @@ -1,7 +1,7 @@ -import { Session } from '@/auth/Session' +import { Session } from '@/auth/session/Session' import { Smorekopp } from '@/services/error' -import prisma from '@/prisma' -import { ApiKeyMethods } from '@/services/api-keys/methods' +import { prisma } from '@/prisma/client' +import { apiKeyOperations } from '@/services/apiKeys/operations' import { afterEach, describe, expect, test } from '@jest/globals' afterEach(async () => { @@ -16,7 +16,7 @@ describe('api keys', () => { user: null, }) - const createdApiKey = await ApiKeyMethods.create.newClient().execute({ + const createdApiKey = await apiKeyOperations.create({ data: { name: 'Min api nøkkel', }, @@ -28,7 +28,7 @@ describe('api keys', () => { permissions: [], }) - const readApiKeyResult = await ApiKeyMethods.read.newClient().execute({ + const readApiKeyResult = await apiKeyOperations.read({ params: createdApiKey, session, }) @@ -38,7 +38,7 @@ describe('api keys', () => { permissions: [], }) - await ApiKeyMethods.update.newClient().execute({ + await apiKeyOperations.update({ params: createdApiKey, data: { permissions: ['APIKEY_ADMIN'], @@ -59,31 +59,28 @@ describe('api keys', () => { // so there should probably be a system in place to run the same test // with different session objects. test('create, read and update api key with unauthenticated user', async () => { - const createdApiKeyPromise = ApiKeyMethods.create.newClient().execute({ + const createdApiKeyPromise = apiKeyOperations.create({ data: { name: 'Min api nøkkel', }, - session: null, }) expect(createdApiKeyPromise).rejects.toThrow(new Smorekopp('UNAUTHENTICATED')) expect(await prisma.apiKey.count()).toEqual(0) - const readApiKeyPromise = ApiKeyMethods.read.newClient().execute({ + const readApiKeyPromise = apiKeyOperations.read({ params: { id: 1, }, - session: null, }) expect(readApiKeyPromise).rejects.toThrow(new Smorekopp('UNAUTHENTICATED')) - const updateApiKeyPromise = ApiKeyMethods.update.newClient().execute({ + const updateApiKeyPromise = apiKeyOperations.update({ params: { id: 1, }, data: { permissions: ['APIKEY_ADMIN'], }, - session: null, }) expect(updateApiKeyPromise).rejects.toThrow(new Smorekopp('UNAUTHENTICATED')) }) diff --git a/tests/services/jobads.test.ts b/tests/services/jobAds.test.ts similarity index 76% rename from tests/services/jobads.test.ts rename to tests/services/jobAds.test.ts index 53e8ae3c4..929a3c87b 100644 --- a/tests/services/jobads.test.ts +++ b/tests/services/jobAds.test.ts @@ -1,7 +1,7 @@ -import { Session } from '@/auth/Session' +import { Session } from '@/auth/session/Session' import { Smorekopp } from '@/services/error' -import prisma from '@/prisma' -import { JobadMethods } from '@/services/career/jobAds/methods' +import { prisma } from '@/prisma/client' +import { jobAdOperations } from '@/services/career/jobAds/operations' import { afterEach, beforeAll, describe, expect, test } from '@jest/globals' // NOTE: This is file contains a lot of boiler plate which should be refactored to be more reusable. @@ -39,11 +39,10 @@ afterEach(async () => { const jobAds = await prisma.jobAd.findMany() await Promise.all(jobAds.map(jobAd => - JobadMethods.destroy.newClient().execute({ + jobAdOperations.destroy({ params: { id: jobAd.id }, - session: null, bypassAuth: true, }) )) @@ -51,7 +50,7 @@ afterEach(async () => { describe('job ads', () => { test('create with unauthenticated user', async () => { - expect(JobadMethods.create.newClient().execute({ + expect(jobAdOperations.create({ data: CREATE_JOB_AD, session: Session.empty() })).rejects.toThrow(new Smorekopp('UNAUTHENTICATED')) @@ -70,7 +69,7 @@ describe('job ads', () => { user: null, }) - await JobadMethods.create.newClient().execute({ data: CREATE_JOB_AD, session }) + await jobAdOperations.create({ data: CREATE_JOB_AD, session }) const res = await prisma.jobAd.findFirst({}) expect(res).toMatchObject(CREATE_JOB_AD) @@ -78,21 +77,19 @@ describe('job ads', () => { test('(create and then) read with unauthenticated user', async () => { // TODO: To avoid fragile tests, this should be refactored to use a seeded job ad. - const createRes = await JobadMethods.create.newClient().execute({ + const createRes = await jobAdOperations.create({ data: CREATE_JOB_AD, - session: null, bypassAuth: true }) - expect(JobadMethods.read.newClient().execute({ params: { idOrName: createRes.id }, session: Session.empty() })) + expect(jobAdOperations.read({ params: { idOrName: createRes.id }, session: Session.empty() })) .rejects.toThrow(new Smorekopp('UNAUTHENTICATED')) }) test('(create and then) read with authorized user', async () => { // TODO: To avoid fragile tests, this should be refactored to use a seeded job ad. - const createRes = await JobadMethods.create.newClient().execute({ + const createRes = await jobAdOperations.create({ data: CREATE_JOB_AD, - session: null, bypassAuth: true }) @@ -102,19 +99,18 @@ describe('job ads', () => { user: null, }) - const readRes = await JobadMethods.read.newClient().execute({ params: { idOrName: createRes.id }, session }) + const readRes = await jobAdOperations.read({ params: { idOrName: createRes.id }, session }) expect(readRes).toMatchObject(CREATE_JOB_AD) }) test('update with unauthenticated user', async () => { // TODO: To avoid fragile tests, this should be refactored to use a seeded job ad. - const createRes = await JobadMethods.create.newClient().execute({ + const createRes = await jobAdOperations.create({ data: CREATE_JOB_AD, - session: null, bypassAuth: true }) - expect(JobadMethods.update.newClient().execute({ + expect(jobAdOperations.update({ params: { id: createRes.id, }, @@ -128,9 +124,8 @@ describe('job ads', () => { test('update with authorized user', async () => { // TODO: To avoid fragile tests, this should be refactored to use a seeded job ad. - const createRes = await JobadMethods.create.newClient().execute({ + const createRes = await jobAdOperations.create({ data: CREATE_JOB_AD, - session: null, bypassAuth: true }) @@ -140,7 +135,7 @@ describe('job ads', () => { user: null, }) - await JobadMethods.update.newClient().execute({ + await jobAdOperations.update({ params: { id: createRes.id, }, @@ -154,13 +149,12 @@ describe('job ads', () => { test('destroy with unauthenticated user', async () => { // TODO: To avoid fragile tests, this should be refactored to use a seeded job ad. - const createRes = await JobadMethods.create.newClient().execute({ + const createRes = await jobAdOperations.create({ data: CREATE_JOB_AD, - session: null, bypassAuth: true }) - expect(JobadMethods.destroy.newClient().execute({ params: { id: createRes.id }, session: Session.empty() })) + expect(jobAdOperations.destroy({ params: { id: createRes.id }, session: Session.empty() })) .rejects.toThrow(new Smorekopp('UNAUTHENTICATED')) const count = await prisma.jobAd.count() @@ -169,9 +163,8 @@ describe('job ads', () => { test('destroy with authorized user', async () => { // TODO: To avoid fragile tests, this should be refactored to use a seeded job ad. - const createRes = await JobadMethods.create.newClient().execute({ + const createRes = await jobAdOperations.create({ data: CREATE_JOB_AD, - session: null, bypassAuth: true }) @@ -181,7 +174,7 @@ describe('job ads', () => { user: null, }) - await JobadMethods.destroy.newClient().execute({ params: { id: createRes.id }, session }) + await jobAdOperations.destroy({ params: { id: createRes.id }, session }) const count = await prisma.jobAd.count() expect(count).toBe(0) diff --git a/tests/services/serviceOperations.test.ts b/tests/services/serviceOperations.test.ts new file mode 100644 index 000000000..85fb6c5d3 --- /dev/null +++ b/tests/services/serviceOperations.test.ts @@ -0,0 +1,113 @@ +import { RequireNothing } from '@/auth/auther/RequireNothing' +import { RequireServerOnly } from '@/auth/auther/ServerOnly' +import { Session } from '@/auth/session/Session' +import { defineOperation } from '@/services/serviceOperation' +import { prisma as globalPrisma } from '@/prisma/client' +import { describe, expect, test } from '@jest/globals' +import { z } from 'zod' + +describe('service operation', () => { + describe('simple', () => { + const addPositiveOnly = defineOperation({ + authorizer: ({ data: { a, b } }) => { + if (a < 0 || b < 0) { + return RequireServerOnly.staticFields({}).dynamicFields({}) + } + + return RequireNothing.staticFields({}).dynamicFields({}) + }, + dataSchema: z.object({ + a: z.number(), + b: z.number(), + }), + operation: ({ data: { a, b } }) => a + b, + }) + + test('method result', async () => { + const res = await addPositiveOnly({ + data: { + a: 1, + b: 2, + }, + }) + + expect(res).toBe(3) + }) + + test('auth fail', async () => { + await expect(addPositiveOnly({ + data: { + a: -1, + b: 2, + }, + })).rejects.toThrow() + }) + + test('bypass auth', async () => { + const res = await addPositiveOnly({ + data: { + a: -1, + b: 2, + }, + bypassAuth: true, + }) + + expect(res).toBe(1) + }) + }) + + describe('nested', () => { + // Simple service operation that just returns its own context + const inner = defineOperation({ + authorizer: () => RequireNothing.staticFields({}).dynamicFields({}), + operation: async (context) => context, + }) + + // Outer service operation that calls the inner one and returns its context + const outer = defineOperation({ + authorizer: () => RequireNothing.staticFields({}).dynamicFields({}), + operation: async () => await inner({}), + }) + + test('nested context global defaults', async () => { + const res = await outer({}) + + expect(res.bypassAuth).toBe(false) + expect(res.session).toBeInstanceOf(Session) + expect(res.prisma).toBe(globalPrisma) + }) + + test('nested context override bypassAuth', async () => { + const res = await outer({ bypassAuth: true }) + + expect(res.bypassAuth).toBe(true) + expect(res.session).toBeInstanceOf(Session) + expect(res.prisma).toBe(globalPrisma) + }) + + test('nested context override session', async () => { + const customSession = Session.fromJsObject({ + apiKeyId: 1919, + memberships: [], + permissions: [], + user: null, + }) + + const res = await outer({ session: customSession }) + + expect(res.bypassAuth).toBe(false) + expect(res.session).toBe(customSession) + expect(res.prisma).toBe(globalPrisma) + }) + + test('nested context override prisma', async () => { + await globalPrisma.$transaction(async (tx) => { + const res = await outer({ prisma: tx }) + + expect(res.bypassAuth).toBe(false) + expect(res.session).toBeInstanceOf(Session) + expect(res.prisma).toBe(tx) + }) + }) + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 6995df053..94ef950b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,18 +25,16 @@ "@/components/*": ["src/app/_components/*"], "@/UI/*": ["src/app/_components/UI/*"], "@/styles/*": ["src/styles/*"], - "@/images/*": ["src/actions/images/*"], - "@/actions/*": ["src/actions/*"], "@/prisma/*": ["src/prisma/*"], "@/contexts/*": ["src/contexts/*"], "@/hooks/*": ["src/hooks/*"], - "@/cms/*": ["src/app/_components/Cms/*", "src/actions/cms/*", "src/services/cms/*"], + "@/cms/*": ["src/app/_components/Cms/*", "src/services/cms/*"], "@/services/*": ["src/services/*"], "@/utils/*": ["src/utils/*"], "@/jwt/*": ["src/lib/jwt/*"], "@/api/*": ["src/app/api/*"], - "@/education/*": ["src/actions/education/*", "src/services/education/*", "src/app/education/*"], - "@/career/*": ["src/actions/career/*", "src/services/career/*", "src/app/career/*"], + "@/education/*": ["src/services/education/*", "src/app/education/*"], + "@/career/*": ["src/services/career/*", "src/app/career/*"], "@/prisma-dobbel-omega/*": ["node_modules/.prisma-dobbel-omega/*"], "@/seeder/*": ["src/prisma/seeder/*"], "@/*": ["./src/*"]
    - {PermissionConfig[permission as Permission].name} + {permissionConfig[permission as Permission].name}
    Kanal - {NotificationConfig.methodsDisplayMap[method]} + {notificationMethodsDisplayMap[method]}