diff --git a/src/app/_components/VisiblityAdmin/VisibilityAdmin.module.scss b/src/app/_components/VisiblityAdmin/VisibilityAdmin.module.scss
index 46143ec5c..f5daa86a7 100644
--- a/src/app/_components/VisiblityAdmin/VisibilityAdmin.module.scss
+++ b/src/app/_components/VisiblityAdmin/VisibilityAdmin.module.scss
@@ -1,15 +1,5 @@
@use '@/styles/ohma';
.VisibilityAdmin {
- @include ohma.card;
- background-color: ohma.$colors-secondary;
- width: 100%;
- .info {
- margin-bottom: .5em;
- }
- .borderBottom {
- border-bottom: 5px solid ohma.$colors-gray-900;
- margin-bottom: .5em;
- width: 100%;
- }
+
}
\ No newline at end of file
diff --git a/src/app/_components/VisiblityAdmin/VisibilityAdmin.tsx b/src/app/_components/VisiblityAdmin/VisibilityAdmin.tsx
index bd940e11f..2ef4479c7 100644
--- a/src/app/_components/VisiblityAdmin/VisibilityAdmin.tsx
+++ b/src/app/_components/VisiblityAdmin/VisibilityAdmin.tsx
@@ -1,40 +1,17 @@
'use client'
import styles from './VisibilityAdmin.module.scss'
-import VisibilityLevelAdmin from './VisibilityLevelAdmin'
-import useActionCall from '@/hooks/useActionCall'
-import { readVisibilityForAdminAction } from '@/services/visibility/actions'
-import { useCallback } from 'react'
+import type { VisibilityMatrix } from '@/services/visibility/types'
+
type PropTypes = {
- visibilityId: number
+ visibility: VisibilityMatrix
}
-
-export default function VisibilityAdmin({ visibilityId }: PropTypes) {
- const action = useCallback(() => readVisibilityForAdminAction(visibilityId), [visibilityId])
- const { data } = useActionCall(action)
- console.log(data)
- if (!data) return null
+export default function VisibilityAdmin({ visibility }: PropTypes) {
+ console.log(visibility)
return (
-
-
Administrer synelighet
- Synelighet for: {data.purpose}
-
- {
- data.type === 'REGULAR' ? (<>
-
-
-
-
-
-
- >) : (<>
-
{data.message}
-
{data.regular}
-
{data.admin}
- >)
- }
+
Synelighet!
)
}
diff --git a/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.module.scss b/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.module.scss
deleted file mode 100644
index 5a65d71d6..000000000
--- a/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.module.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-@use '@/styles/ohma';
-
-.VisibilityLevelAdmin {
- .requierments {
- display: flex;
- width: 100%;
- justify-content: space-between;
- }
-}
\ No newline at end of file
diff --git a/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.tsx b/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.tsx
deleted file mode 100644
index 6c7cd03ea..000000000
--- a/src/app/_components/VisiblityAdmin/VisibilityLevelAdmin.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import styles from './VisibilityLevelAdmin.module.scss'
-import Form from '@/components/Form/Form'
-import { updateVisibilityAction } from '@/services/visibility/actions'
-import type { VisibilityLevelType, VisibilityRequiermentForAdmin } from '@/services/visibility/types'
-
-type PropTypes = {
- data: VisibilityRequiermentForAdmin[]
- level: VisibilityLevelType
- levelName: string
-}
-export default function VisibilityLevelAdmin({ level, data, levelName }: PropTypes) {
- return (
-
- )
-}
diff --git a/src/app/articles/[category]/EditCategory.tsx b/src/app/articles/[category]/EditCategory.tsx
index bee6c69f4..770dcba2a 100644
--- a/src/app/articles/[category]/EditCategory.tsx
+++ b/src/app/articles/[category]/EditCategory.tsx
@@ -20,7 +20,7 @@ type PropTypes = {
export default function EditCategory({ category }: PropTypes) {
const { refresh, push } = useRouter()
- // Make a visibility check for edit
+ // Make a visibility check for edit - no just call the apropriate auther.
const canEditCategory = true
const handleSuccessDestroy = () => {
diff --git a/src/app/articles/[category]/SideBar.tsx b/src/app/articles/[category]/SideBar.tsx
index 791a3a2fe..a662fbe13 100644
--- a/src/app/articles/[category]/SideBar.tsx
+++ b/src/app/articles/[category]/SideBar.tsx
@@ -74,7 +74,7 @@ export default function SideBar({ category, children }: PropTypes) {
}
function MainListContent({ category }: { category: ExpandedArticleCategory }) {
- // Make a visibility check for edit
+ // Make a visibility check for edit - no just call the apropriate auther.
const canEditCategory = true
const { push, refresh } = useRouter()
diff --git a/src/app/images/collections/[id]/CollectionAdmin.tsx b/src/app/images/collections/[id]/CollectionAdmin.tsx
index 702b53fa1..5aa408c33 100644
--- a/src/app/images/collections/[id]/CollectionAdmin.tsx
+++ b/src/app/images/collections/[id]/CollectionAdmin.tsx
@@ -7,28 +7,27 @@ import TextInput from '@/components/UI/TextInput'
import { ImagePagingContext } from '@/contexts/paging/ImagePaging'
import ImageUploader from '@/components/Image/ImageUploader'
import useEditing from '@/hooks/useEditing'
-import VisibilityAdmin from '@/components/VisiblityAdmin/VisibilityAdmin'
import PopUp from '@/components/PopUp/PopUp'
import Button from '@/components/UI/Button'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCog, faEye, faUpload } from '@fortawesome/free-solid-svg-icons'
import { useRouter } from 'next/navigation'
import { useContext, useState } from 'react'
-import type { VisibilityCollapsed } from '@/services/visibility/types'
+import type { VisibilityMatrix } from '@/services/visibility/types'
import type { ExpandedImageCollection } from '@/services/images/collections/types'
type PropTypes = {
collection: ExpandedImageCollection
- visibility: VisibilityCollapsed
+ visibilityAdmin: VisibilityMatrix
+ visibilityRead: VisibilityMatrix
}
-export default function CollectionAdmin({ collection, visibility }: PropTypes) {
+export default function CollectionAdmin({ collection, visibilityAdmin, visibilityRead }: PropTypes) {
+ console.log(visibilityAdmin, visibilityRead)
const { id: collectionId } = collection
const router = useRouter()
const pagingContext = useContext(ImagePagingContext)
- const canEdit = useEditing({
- requiredVisibility: visibility,
- })
+ const canEdit = useEditing({}) //TODO: pass in auther
const [uploadOption, setUploadOption] = useState<'MANY' | 'ONE'>('MANY')
if (!canEdit) return null
@@ -112,7 +111,7 @@ export default function CollectionAdmin({ collection, visibility }: PropTypes) {
}>
-
+ {/* VisibilityAdmin... */}
diff --git a/src/app/images/collections/[id]/page.tsx b/src/app/images/collections/[id]/page.tsx
index c11c4c491..de75a2d0f 100644
--- a/src/app/images/collections/[id]/page.tsx
+++ b/src/app/images/collections/[id]/page.tsx
@@ -8,6 +8,7 @@ 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'
+import type { VisibilityMatrix } from '@/services/visibility/types'
type PropTypes = {
params: Promise<{
@@ -51,7 +52,11 @@ export default async function Collection({ params }: PropTypes) {
images.map(image => )
} />
-
+
diff --git a/src/auth/auther/RequireVisibility.ts b/src/auth/auther/RequireVisibility.ts
index 86e778925..8d5f06476 100644
--- a/src/auth/auther/RequireVisibility.ts
+++ b/src/auth/auther/RequireVisibility.ts
@@ -1,14 +1,14 @@
import { AutherFactory } from './Auther'
-import { checkVisibility } from '@/auth/checkVisibility'
-import type { VisibilityCollapsed } from '@/services/visibility/types'
+import { checkVisibility } from '@/auth/visibility/checkVisibility'
+import type { VisibilityMatrix } from '@/services/visibility/types'
import type { Permission } from '@prisma/client'
export const RequireVisibility = AutherFactory<
{ bypassPermission: Permission },
- { visibility: VisibilityCollapsed },
+ { visibility: VisibilityMatrix },
'USER_NOT_REQUIERED_FOR_AUTHORIZED'
> (({ session, dynamicFields, staticFields }) => ({
- success: checkVisibility(session, dynamicFields.visibility, 'REGULAR') ||
+ success: checkVisibility(session.memberships, dynamicFields.visibility) ||
session.permissions.includes(staticFields.bypassPermission),
session,
}))
diff --git a/src/auth/checkVisibility.ts b/src/auth/checkVisibility.ts
deleted file mode 100644
index f0529543d..000000000
--- a/src/auth/checkVisibility.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { BypassPermissions } from '@/services/visibility/ConfigVars'
-import type { MembershipFiltered } from '@/services/groups/memberships/types'
-import type { Permission } from '@prisma/client'
-import type { GroupMatrix, VisibilityCollapsed, VisibilityLevelType } from '@/services/visibility/types'
-
-type MembershipAndPermission = {
- memberships: MembershipFiltered[],
- permissions: Permission[]
-}
-
-/**
- * Check if a user meets the visibility requirements of a visibility
- * @param memberships - the memberships of the user. Remember if using getUser and there is no user
- * getUser and useUser will return an empty array
- * @param permissions - the permissions of the user. Remember if using getUser and there is no usee
- * getUser and useUser will return an empty array. Used if type of visibility is SPECIAL
- * @param visibility - the visibility to require
- * @param level - the level of visibility to require
- * @returns - true if the user meets the visibility requirements, false otherwise
- */
-export function checkVisibility({
- memberships,
- permissions
-}: MembershipAndPermission,
-visibility: VisibilityCollapsed,
-level: VisibilityLevelType,
-) {
- const bypassPermission = BypassPermissions[visibility.purpose]
- if (bypassPermission && permissions.includes(bypassPermission)) return true
- if (visibility.type === 'REGULAR' && !visibility.published) return false
- if (visibility.type === 'SPECIAL') {
- if (level === 'REGULAR') {
- //null permission means no permission required
- return visibility.regular ? permissions.includes(visibility.regular) : true
- }
- //null permission means no permission required
- return visibility.admin ? permissions.includes(visibility.admin) : true
- }
- return checkVisibilityATLevel(memberships, visibility[level === 'REGULAR' ? 'regular' : 'admin'])
-}
-
-function checkVisibilityATLevel(memberships: MembershipFiltered[], visibilityLevel: GroupMatrix) {
- if (!visibilityLevel.length) return true
- return visibilityLevel.every(requirement =>
- requirement.some(
- groupId => memberships.some(
- membership => membership.groupId === groupId
- )
- )
- )
-}
diff --git a/src/auth/getVisibilityFilter.ts b/src/auth/getVisibilityFilter.ts
deleted file mode 100644
index 8eec8d04c..000000000
--- a/src/auth/getVisibilityFilter.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import '@pn-server-only'
-import { BypassPermissions } from '@/services/visibility/ConfigVars'
-import type { MembershipFiltered } from '@/services/groups/memberships/types'
-import type { Permission, Prisma, VisibilityPurpose } from '@prisma/client'
-
-function userMayBypassVisibilityBasedOnPermission(
- permissions: Permission[],
- purpose: VisibilityPurpose,
-) {
- const bypassPermissionForPurpose = BypassPermissions[purpose]
- if (!bypassPermissionForPurpose) return false
- return permissions.includes(bypassPermissionForPurpose)
-}
-
-function isVisibilityPurpose(purpose: string): purpose is VisibilityPurpose {
- return Object.keys(BypassPermissions).includes(purpose)
-}
-
-/**
- * Creates a where-filter that can be used in db queries to only return
- * items that mach the users groups (or permission if type is special).
- * This should be used for regularVisibility level.
- * @param groups - The groups the user is a member of
- * @param permissions - The permissions the user has
- * @returns - A where-filter that can be used in db queries. Used n query as
- * ```where: getVisibilityFilter(user.memberships, user.permissions)```
- */
-export function getVisibilityFilter(
- groups: MembershipFiltered[] | undefined,
- permissions: Permission[],
-) {
- const groupIds = groups ? groups.map(group => group.groupId) : []
-
- const bypassers = Object.keys(BypassPermissions).reduce((acc, purpose) => {
- if (!isVisibilityPurpose(purpose)) return acc
- if (!userMayBypassVisibilityBasedOnPermission(permissions, purpose)) return acc
- acc.push({
- visibility: {
- published: true,
- purpose,
- }
- })
- return acc
- }, [] as {
- visibility: {
- published: true,
- purpose: VisibilityPurpose
- }
- }[]
- )
- console.log(permissions)
- return {
- OR: [
- // A user has access if it has the bypass permission for spesific purpose
- ...bypassers,
-
- // A user has access if the visibility is special and the user has the permission or the
- // permission is null
- {
- visibility: {
- published: true,
- specialPurpose: {
- not: null
- },
- regularLevel: {
- permission: {
- in: permissions
- }
- }
- }
- },
- {
- visibility: {
- published: true,
- specialPurpose: {
- not: null
- },
- regularLevel: {
- permission: null
- }
- }
- },
-
- // If the visibility is not special, the user has access if it
- // meets the requirements of the visibility. Or if there are no requirements
- {
- visibility: {
- published: true,
- specialPurpose: null,
- regularLevel: {
- OR: [
- {
- requirements: {
- some: {
- visibilityRequirmenetGroups: {
- some: {
- groupId: {
- in: groupIds
- }
- }
- }
- }
- }
- },
- {
- requirements: {
- none: {}
- }
- }
- ]
- }
- }
- }
- ]
- } satisfies Prisma.ImageCollectionWhereInput
-}
-
-export type VisibilityFilter = ReturnType
diff --git a/src/auth/visibility/checkVisibility.ts b/src/auth/visibility/checkVisibility.ts
new file mode 100644
index 000000000..7cbe43a97
--- /dev/null
+++ b/src/auth/visibility/checkVisibility.ts
@@ -0,0 +1,23 @@
+import type { MembershipFiltered } from '@/services/groups/memberships/types'
+import type { VisibilityMatrix } from '@/services/visibility/types'
+
+
+export function checkVisibility(memberships: MembershipFiltered[], visibility: VisibilityMatrix): boolean {
+ return visibility.requirements.every(
+ requirement => requirement.conditions.some(
+ condition => {
+ if (condition.type === 'ACTIVE') {
+ return memberships.some(
+ membership => membership.groupId === condition.groupId && membership.active
+ )
+ }
+ if (condition.type === 'ORDER') {
+ return memberships.some(
+ membership => membership.groupId === condition.groupId && membership.order === condition.order
+ )
+ }
+ return false
+ }
+ )
+ )
+}
diff --git a/src/auth/visibility/isSubVisibility.ts b/src/auth/visibility/isSubVisibility.ts
new file mode 100644
index 000000000..caafa78b6
--- /dev/null
+++ b/src/auth/visibility/isSubVisibility.ts
@@ -0,0 +1,36 @@
+import type { VisibilityMatrix, VisibilityRequirement } from '@/services/visibility/types'
+
+/**
+ * This function checks if any session (user) satisfying `visibilitySub` is also necessarily
+ * satisfies `visibilitySuper`
+ * session element in visibilitySub => session element in visibilitySuper
+ * If the return is true the implication holds else it does not (necessarily) hold
+ *
+ * For this to be true every requirement in the `visibilitySuper` must be a super-requirement of
+ * some requirement in the `visibilitySub`
+ * @param visibilitySub
+ * @param visibilitySuper
+ */
+export function isSubVisibility(visibilitySub: VisibilityMatrix, visibilitySuper: VisibilityMatrix): boolean {
+ return visibilitySuper.requirements.every(
+ superRequirement => visibilitySub.requirements.some(
+ subRequirement => isSubRequirement(subRequirement, superRequirement)
+ )
+ )
+}
+
+function isSubRequirement(requirementSub: VisibilityRequirement, requirementSuper: VisibilityRequirement): boolean {
+ return requirementSub.conditions.every(
+ conditionSub => requirementSuper.conditions.some(
+ conditionSuper => {
+ if (conditionSub.groupId !== conditionSuper.groupId) return false
+ if (conditionSub.type === 'ACTIVE' && conditionSuper.type === 'ACTIVE') return true
+ if (conditionSub.type === 'ORDER' && conditionSuper.type === 'ORDER') {
+ return conditionSub.order === conditionSuper.order
+ }
+ // Condition types do not match....
+ return false
+ }
+ )
+ )
+}
diff --git a/src/auth/visibility/visibilityFilter.ts b/src/auth/visibility/visibilityFilter.ts
new file mode 100644
index 000000000..28457812c
--- /dev/null
+++ b/src/auth/visibility/visibilityFilter.ts
@@ -0,0 +1,33 @@
+import type { MembershipFiltered } from '@/services/groups/memberships/types'
+import type { Prisma } from '@prisma/client'
+
+export function visibilityFilter(memberships: MembershipFiltered[]) {
+ return {
+ requirements: {
+ every: {
+ conditions: {
+ some: {
+ OR: [
+ {
+ type: 'ACTIVE',
+ groupId: {
+ in: memberships
+ .filter(membership => membership.active)
+ .map(membership => membership.groupId)
+ }
+ },
+ {
+ type: 'ORDER',
+ OR: memberships
+ .map(membership => ({
+ groupId: membership.groupId,
+ order: membership.order
+ }))
+ },
+ ]
+ }
+ }
+ }
+ }
+ } as const satisfies Prisma.VisibilityWhereInput
+}
diff --git a/src/hooks/useEditing.ts b/src/hooks/useEditing.ts
index b18e0478c..8fc1f5553 100644
--- a/src/hooks/useEditing.ts
+++ b/src/hooks/useEditing.ts
@@ -1,13 +1,15 @@
'use client'
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 '@/lib/checkMatrix'
-import type { VisibilityCollapsed, VisibilityLevelType } from '@/services/visibility/types'
+//TODO: Change this to take in session and a auther
+//useUser should return a session.
+// This function does way to many things...
+// just use the auther system...
/**
* A hook that uses useUser to determine if the user is allowed to edit the content.
* If the user is allowed to edit the content, the hook will add the content to the list of editable content
@@ -24,14 +26,8 @@ import type { VisibilityCollapsed, VisibilityLevelType } from '@/services/visibi
*/
export default function useEditing({
requiredPermissions,
- requiredVisibility,
- operation = 'OR',
- level = 'ADMIN'
}: {
requiredPermissions?: Matrix,
- requiredVisibility?: VisibilityCollapsed
- operation?: 'AND' | 'OR',
- level?: VisibilityLevelType
}): boolean {
const editModeCtx = useContext(EditModeContext)
const { authorized: permissionAuthorized, permissions, memberships } = useUser({
@@ -44,14 +40,7 @@ export default function useEditing({
const uniqueKey = useRef(uuid()).current
useEffect(() => {
- const visibilityAuthorized = requiredVisibility ? checkVisibility({
- permissions: permissions ?? [],
- memberships: memberships ?? [],
- }, requiredVisibility, level) : undefined
-
- const authorized_ = (operation === 'OR' ?
- permissionAuthorized || visibilityAuthorized :
- permissionAuthorized && visibilityAuthorized) ?? false
+ const authorized_ = permissionAuthorized === true
if (editModeCtx) {
if (authorized_) editModeCtx.addEditableContent(uniqueKey)
if (!authorized_) editModeCtx.removeEditableContent(uniqueKey)
@@ -60,7 +49,7 @@ export default function useEditing({
return () => {
if (editModeCtx) editModeCtx.removeEditableContent(uniqueKey)
}
- }, [permissionAuthorized, requiredVisibility, permissions, memberships])
+ }, [permissionAuthorized, permissions, memberships])
useEffect(() => {
setEditable(editModeCtx ? Boolean(authorized) && editModeCtx.editMode : false)
diff --git a/src/prisma/schema/cms.prisma b/src/prisma/schema/cms.prisma
index 3e0024b69..cf03ead68 100644
--- a/src/prisma/schema/cms.prisma
+++ b/src/prisma/schema/cms.prisma
@@ -64,8 +64,8 @@ model CmsParagraph {
Course Course[]
School School[]
Event Event[]
- Committee Committee? @relation(name: "committeeParagraph")
- CommitteApplication Committee? @relation(name: "applicationParagraph")
+ Committee Committee? @relation(name: "committeeParagraph")
+ CommitteApplication Committee? @relation(name: "applicationParagraph")
}
enum SpecialCmsLink {
@@ -103,48 +103,47 @@ model ArticleSection {
//The order "position" of the article section in the article
//This field is only used when articleSection is part of an article
- order Int @default(autoincrement())
+ order Int @default(autoincrement())
-
- cmsImage CmsImage?
- cmsParagraph CmsParagraph?
- cmsLink CmsLink?
- InterestGroup InterestGroup?
+ cmsImage CmsImage?
+ cmsParagraph CmsParagraph?
+ cmsLink CmsLink?
+ InterestGroup InterestGroup?
@@unique([articleId, order]) //There can only be one article section with a given order in an article
}
-enum SpecialCmsArticle{
+enum SpecialCmsArticle {
REPORT_PAGE
NEW_STUDENT_PAGE
}
model Article {
- id Int @id @default(autoincrement())
+ id Int @id @default(autoincrement())
name String
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
articleSections ArticleSection[]
newsArticle NewsArticle?
- articleCategory ArticleCategory? @relation(fields: [articleCategoryId, articleCategoryName], references: [id, name], onDelete: Cascade)
+ articleCategory ArticleCategory? @relation(fields: [articleCategoryId, articleCategoryName], references: [id, name], onDelete: Cascade)
articleCategoryId Int?
articleCategoryName String?
- coverImage CmsImage @relation(fields: [coverImageId], references: [id], onDelete: Restrict)
- coverImageId Int @unique
+ coverImage CmsImage @relation(fields: [coverImageId], references: [id], onDelete: Restrict)
+ coverImageId Int @unique
JobAd JobAd?
Committee Committee?
- special SpecialCmsArticle? @unique
+ special SpecialCmsArticle? @unique
@@unique([name, id])
}
model NewsArticle {
- id Int @id @default(autoincrement())
- description String?
- endDateTime DateTime //when the article is no longer considered current
- article Article @relation(fields: [articleId, articleName], references: [id, name], onDelete: Restrict)
- articleId Int @unique
- articleName String
+ id Int @id @default(autoincrement())
+ description String?
+ endDateTime DateTime //when the article is no longer considered current
+ article Article @relation(fields: [articleId, articleName], references: [id, name], onDelete: Restrict)
+ articleId Int @unique
+ articleName String
@@unique([articleId, articleName])
}
diff --git a/src/prisma/schema/group.prisma b/src/prisma/schema/group.prisma
index 53e8d9c8d..e9008d7ac 100644
--- a/src/prisma/schema/group.prisma
+++ b/src/prisma/schema/group.prisma
@@ -6,7 +6,7 @@ model Membership {
admin Boolean
active Boolean
title String @default("Medlem")
- omegaOrder OmegaOrder @relation(fields: [order], references: [order])
+ omegaOrder OmegaOrder @relation(fields: [order], references: [order], onDelete: Restrict)
order Int
@@unique([userId, groupId, order])
@@ -34,7 +34,7 @@ model Group {
id Int @id @default(autoincrement())
groupType GroupType
memberships Membership[]
- omegaOrder OmegaOrder @relation(fields: [order], references: [order])
+ omegaOrder OmegaOrder @relation(fields: [order], references: [order], onDelete: Restrict)
order Int //The order the group is in currently.
class Class?
@@ -46,7 +46,7 @@ model Group {
LockerReservation LockerReservation[]
mailingLists MailingListGroup[]
- VisibilityRequirmenetGroup VisibilityRequirmenetGroup[]
+ VisibilityRequirementGroup VisibilityRequirementGroup[]
permissions GroupPermission[]
}
diff --git a/src/prisma/schema/image.prisma b/src/prisma/schema/image.prisma
index 3a2d0aaca..f4a61333e 100644
--- a/src/prisma/schema/image.prisma
+++ b/src/prisma/schema/image.prisma
@@ -64,8 +64,11 @@ model ImageCollection {
special SpecialCollection? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
- visibility Visibility @relation(fields: [visibilityId], references: [id])
- visibilityId Int @unique
+
+ visibilityAdmin Visibility @relation(name: "ImageCollectionAdminVisibility", fields: [visibilityAdminId], references: [id], onDelete: Restrict)
+ visibilityAdminId Int @unique
+ visibilityRead Visibility @relation(name: "ImageCollectionReadVisibility", fields: [visibilityReadId], references: [id], onDelete: Restrict)
+ visibilityReadId Int @unique
}
enum ImageSize {
diff --git a/src/prisma/schema/omegaorder.prisma b/src/prisma/schema/omegaorder.prisma
index aa30f0d91..b543ff79c 100644
--- a/src/prisma/schema/omegaorder.prisma
+++ b/src/prisma/schema/omegaorder.prisma
@@ -3,6 +3,7 @@ model OmegaOrder {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
- memberships Membership[]
- groups Group[]
+ memberships Membership[]
+ groups Group[]
+ VisibilityRequirementGroup VisibilityRequirementGroup[]
}
diff --git a/src/prisma/schema/permission.prisma b/src/prisma/schema/permission.prisma
index 918d9021a..5ab31fbe3 100644
--- a/src/prisma/schema/permission.prisma
+++ b/src/prisma/schema/permission.prisma
@@ -193,4 +193,3 @@ model GroupPermission {
model DefaultPermission {
permission Permission @unique
}
-
diff --git a/src/prisma/schema/visibility.prisma b/src/prisma/schema/visibility.prisma
index 1b74d7a64..af41a3539 100644
--- a/src/prisma/schema/visibility.prisma
+++ b/src/prisma/schema/visibility.prisma
@@ -1,54 +1,34 @@
-enum VisibilityPurpose {
- IMAGE
- EVENT
- ARTICLE_CATEGORY
- NEWS_ARTICLE
- SPECIAL
-}
-
-enum SpecialVisibilityPurpose {
- OMBUL
- COMMITTEE
- USER
- PUBLIC
-}
-
model Visibility {
- id Int @id @default(autoincrement())
- imageCollection ImageCollection?
- published Boolean @default(false)
- purpose VisibilityPurpose
- specialPurpose SpecialVisibilityPurpose? @unique
+ id Int @id @default(autoincrement())
+ requirements VisibilityRequirement[] // You must fullfill all requirements
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
- regularLevel VisibilityLevel @relation(fields: [regularLevelId], references: [id], name: "regularLevel")
- regularLevelId Int @unique
- adminLevel VisibilityLevel @relation(fields: [adminLevelId], references: [id], name: "adminLevel")
- adminLevelId Int @unique
+ imageCollectionAdmin ImageCollection? @relation(name: "ImageCollectionAdminVisibility")
+ imageCollectionRead ImageCollection? @relation(name: "ImageCollectionReadVisibility")
}
-model VisibilityLevel {
- id Int @id @default(autoincrement())
- permission Permission? //Used if type of the Visibility is SPECIAL. (If null everybody will have access at this level)
- requirements VisibilityRequirement[]
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- regularVisibility Visibility? @relation(name: "regularLevel")
- adminVisibility Visibility? @relation(name: "adminLevel")
+model VisibilityRequirement {
+ id Int @id @default(autoincrement())
+ visibility Visibility @relation(fields: [visibilityId], references: [id], onDelete: Cascade)
+ visibilityId Int
+ conditions VisibilityRequirementGroup[] //must be in one of these to fullfill this requirement
}
-model VisibilityRequirement {
- id Int @id @default(autoincrement())
- visibility VisibilityLevel @relation(fields: [visibilityId], references: [id], onDelete: Cascade)
- visibilityId Int
- visibilityRequirmenetGroups VisibilityRequirmenetGroup[]
+enum VisibilityRequirementGroupType {
+ ACTIVE
+ ORDER
}
-model VisibilityRequirmenetGroup {
- id Int @id @default(autoincrement())
- visibilityRequirmenet VisibilityRequirement @relation(fields: [visibilityRequirmenetId], references: [id], onDelete: Cascade)
- visibilityRequirmenetId Int
- group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
+model VisibilityRequirementGroup {
+ id Int @id @default(autoincrement())
+ visibilityRequirement VisibilityRequirement @relation(fields: [visibilityRequirementId], references: [id], onDelete: Cascade)
+ visibilityRequirementId Int
+ group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
groupId Int
+ type VisibilityRequirementGroupType
+ omegaOrder OmegaOrder @relation(fields: [order], references: [order], onDelete: Restrict)
+ order Int
- @@unique([visibilityRequirmenetId, groupId])
+ @@unique([visibilityRequirementId, groupId, order])
}
diff --git a/src/prisma/seeder/src/SeedSpecialImageCollections.ts b/src/prisma/seeder/src/SeedSpecialImageCollections.ts
index 443fa1f76..a37dc51e4 100644
--- a/src/prisma/seeder/src/SeedSpecialImageCollections.ts
+++ b/src/prisma/seeder/src/SeedSpecialImageCollections.ts
@@ -1,27 +1,12 @@
import { SpecialCollection } from '@prisma/client'
-import type { PrismaClient, SpecialVisibilityPurpose } from '@prisma/client'
-
-export const specialCollectionsVisibility = {
- OMBULCOVERS: {
- specialVisibility: 'OMBUL'
- },
- PROFILEIMAGES: {
- specialVisibility: 'USER'
- },
- STANDARDIMAGES: {
- specialVisibility: 'PUBLIC'
- },
- COMMITTEELOGOS: {
- specialVisibility: 'COMMITTEE'
- }
-} satisfies {[CollectionType in SpecialCollection]: {
- specialVisibility: SpecialVisibilityPurpose
-}}
+import type { PrismaClient } from '@prisma/client'
export default async function SeedSpecialImageCollections(prisma: PrismaClient) {
const keys = Object.keys(SpecialCollection) as SpecialCollection[]
- await Promise.all(keys.map((special) =>
- prisma.imageCollection.upsert({
+ await Promise.all(keys.map(async (special) => {
+ const visibilityAdmin = await prisma.visibility.create({ data: {} })
+ const visibilityRead = await prisma.visibility.create({ data: {} })
+ return await prisma.imageCollection.upsert({
where: {
name: special
},
@@ -31,12 +16,9 @@ export default async function SeedSpecialImageCollections(prisma: PrismaClient)
create: {
name: special,
special,
- visibility: {
- connect: {
- specialPurpose: specialCollectionsVisibility[special].specialVisibility
- }
- }
+ visibilityRead: { connect: { id: visibilityRead.id } },
+ visibilityAdmin: { connect: { id: visibilityAdmin.id } }
}
})
- ))
+ }))
}
diff --git a/src/prisma/seeder/src/development/seedDevImages.ts b/src/prisma/seeder/src/development/seedDevImages.ts
index 798a84b77..85280cadd 100644
--- a/src/prisma/seeder/src/development/seedDevImages.ts
+++ b/src/prisma/seeder/src/development/seedDevImages.ts
@@ -12,13 +12,11 @@ export default async function seedDevImages(prisma: PrismaClient) {
create: {
name: `test_collection_${i}`,
description: 'just a test',
- visibility: {
- create: {
- purpose: 'IMAGE',
- published: true,
- regularLevel: { create: {} },
- adminLevel: { create: {} },
- }
+ visibilityAdmin: {
+ create: {}
+ },
+ visibilityRead: {
+ create: {}
}
}
})
diff --git a/src/prisma/seeder/src/dobbelOmega/migrateImageCollections.ts b/src/prisma/seeder/src/dobbelOmega/migrateImageCollections.ts
index 4eeb756d5..966549318 100644
--- a/src/prisma/seeder/src/dobbelOmega/migrateImageCollections.ts
+++ b/src/prisma/seeder/src/dobbelOmega/migrateImageCollections.ts
@@ -34,13 +34,11 @@ export default async function migrateImageCollections(pnPrisma: PrismaClientPn,
createdAt: imageCollection.updatedAt,
updatedAt: imageCollection.updatedAt,
//TODO: Link to right committee through visibility
- visibility: {
- create: {
- purpose: 'IMAGE',
- published: true,
- regularLevel: { create: {} },
- adminLevel: { create: {} }
- }
+ visibilityRead: {
+ create: {} //Assuming all collections from vevn to be public on migration is probably fine
+ },
+ visibilityAdmin: {
+ create: {} //TODO: not everyone should be able to update this....
}
}
})
diff --git a/src/prisma/seeder/src/dobbelOmega/migrateImages.ts b/src/prisma/seeder/src/dobbelOmega/migrateImages.ts
index 8f70a248a..c97d2d258 100644
--- a/src/prisma/seeder/src/dobbelOmega/migrateImages.ts
+++ b/src/prisma/seeder/src/dobbelOmega/migrateImages.ts
@@ -33,18 +33,12 @@ export default async function migrateImages(
update: {},
create: {
name: 'Søppel fra Veven',
- description: 'Denne samlingen inneholder bilder som ikke tilhører noen samling',
- visibility: {
- create: {
- purpose: 'IMAGE',
- published: true,
- regularLevel: {
- create: {}
- },
- adminLevel: {
- create: {}
- },
- }
+ description: 'Denne samlingen inneholder bilder som ikke tilhørete noen samling i omegaweb-basic',
+ visibilityRead: {
+ create: {},
+ },
+ visibilityAdmin: {
+ create: {} //TODO: Require vevcom or something...
}
},
})
diff --git a/src/prisma/seeder/src/seedSpecialVisibility.ts b/src/prisma/seeder/src/seedSpecialVisibility.ts
deleted file mode 100644
index 486f1ed34..000000000
--- a/src/prisma/seeder/src/seedSpecialVisibility.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import type { Permission, SpecialVisibilityPurpose, PrismaClient } from '@prisma/client'
-
-const SpecialVisibilityConfig = {
- OMBUL: {
- regularLevel: 'OMBUL_READ',
- adminLevel: 'OMBUL_CREATE'
- },
- PUBLIC: {
- regularLevel: null,
- adminLevel: 'FRONTPAGE_ADMIN' // just changed this to make ts happy
- // bypass and purposes will be removed
- },
- COMMITTEE: {
- regularLevel: 'COMMITTEE_READ',
- adminLevel: 'COMMITTEE_CREATE'
- },
- USER: {
- regularLevel: 'USERS_READ',
- adminLevel: 'USERS_CREATE'
- }
-} satisfies {[VisibilityType in SpecialVisibilityPurpose]: {
- regularLevel: Permission | null,
- adminLevel: Permission | null
-}}
-
-
-export default async function SeedSpecialVisibility(prisma: PrismaClient) {
- const keys = Object.keys(SpecialVisibilityConfig) as SpecialVisibilityPurpose[]
- await Promise.all(keys.map((special) =>
- prisma.visibility.upsert({
- where: {
- specialPurpose: special
- },
- update: {
- regularLevel: {
- update: {
- permission: SpecialVisibilityConfig[special].regularLevel
- }
- },
- adminLevel: {
- update: {
- permission: SpecialVisibilityConfig[special].adminLevel
- }
- }
- },
- create: {
- purpose: 'SPECIAL',
- published: true,
- specialPurpose: special,
- regularLevel: {
- create: {
- permission: SpecialVisibilityConfig[special].regularLevel
- }
- },
- adminLevel: {
- create: {
- permission: SpecialVisibilityConfig[special].adminLevel
- }
- }
- }
- })
- ))
-}
diff --git a/src/prisma/seeder/src/seeder.ts b/src/prisma/seeder/src/seeder.ts
index cc9eeb0ae..8d2169104 100644
--- a/src/prisma/seeder/src/seeder.ts
+++ b/src/prisma/seeder/src/seeder.ts
@@ -12,7 +12,6 @@ import dobbelOmega from './dobbelOmega/dobbelOmega'
import seedNotificationChannels from './seedNotificationsChannels'
import seedDevGroups from './development/seedDevGroups'
import seedClasses from './seedClasses'
-import SeedSpecialVisibility from './seedSpecialVisibility'
import seedMail from './seedMail'
import seedStudyProgramme from './seedStudyProgramme'
import seedOmegaMembershipGroups from './seedOmegaMembershipGroups'
@@ -37,7 +36,6 @@ export default async function seed(
if (enableLogging) console.log('seeding standard data....')
await seedOrder(prisma)
- await SeedSpecialVisibility(prisma)
await SeedSpecialImageCollections(prisma)
await seedImages(prisma)
await seedCms(prisma)
diff --git a/src/services/cms/articleSections/implement.ts b/src/services/cms/articleSections/implement.ts
index 95fff1c13..5717c6406 100644
--- a/src/services/cms/articleSections/implement.ts
+++ b/src/services/cms/articleSections/implement.ts
@@ -10,7 +10,7 @@ import type { z } from 'zod'
import type { ArgsAuthGetterAndOwnershipCheck, PrismaPossibleTransaction } from '@/services/serviceOperation'
type ParamsSchema = typeof articleSectionSchemas.params
-type OwnedArticleSections = Prisma.ArticleSectionGetPayload<{
+type OwnedArticleSection = Prisma.ArticleSectionGetPayload<{
include: {
cmsImage: true,
cmsParagraph: true,
@@ -41,7 +41,7 @@ export function implementUpdateArticleSectionOperations<
prisma: PrismaPossibleTransaction,
implementationParams: z.infer
}
- ) => Promise
+ ) => Promise
destroyOnEmpty: boolean
}) {
const ownershipCheckArticleSection = async (
diff --git a/src/services/cms/articles/implement.ts b/src/services/cms/articles/implement.ts
index dbdaf6160..41875a227 100644
--- a/src/services/cms/articles/implement.ts
+++ b/src/services/cms/articles/implement.ts
@@ -8,7 +8,7 @@ import type { articleSchemas } from './schemas'
import type { z } from 'zod'
type ParamsSchema = typeof articleSchemas.params
-type OwnedArticles = Prisma.ArticleGetPayload<{
+type OwnedArticle = Prisma.ArticleGetPayload<{
include: {
coverImage: true,
articleSections: {
@@ -43,7 +43,7 @@ export function implementUpdateArticleOperations<
prisma: PrismaPossibleTransaction,
implementationParams: z.infer
}
- ) => Promise
+ ) => Promise
}) {
const ownershipCheckArticle = async (
args: Omit, 'data'>
diff --git a/src/services/images/collections/ConfigVars.ts b/src/services/images/collections/ConfigVars.ts
deleted file mode 100644
index c76df8682..000000000
--- a/src/services/images/collections/ConfigVars.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { SpecialCollection, SpecialVisibilityPurpose } from '@prisma/client'
-
-/**
- * Each special image collection links to a special visibility known at compile time
- */
-export const specialCollectionsSpecialVisibilityMap = {
- OMBULCOVERS: {
- specialVisibility: 'OMBUL'
- },
- PROFILEIMAGES: {
- specialVisibility: 'USER'
- },
- STANDARDIMAGES: {
- specialVisibility: 'PUBLIC'
- },
- COMMITTEELOGOS: {
- specialVisibility: 'COMMITTEE'
- }
-} satisfies {[T in SpecialCollection]: {
- specialVisibility: SpecialVisibilityPurpose
-}}
diff --git a/src/services/images/collections/actions.ts b/src/services/images/collections/actions.ts
index bb12b2255..57b47fde0 100644
--- a/src/services/images/collections/actions.ts
+++ b/src/services/images/collections/actions.ts
@@ -1,9 +1,7 @@
'use server'
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 {
@@ -12,10 +10,8 @@ import {
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 { SpecialCollection } from '@prisma/client'
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'
@@ -50,16 +46,9 @@ export async function destroyImageCollectionAction(collectionId: number): Promis
*/
export async function readImageCollectionAction(
idOrName: number | string
-): Promise> {
- const collection = await safeServerCall(() => includeVisibility(
- () => readImageCollection(idOrName),
- data => data.visibilityId
- ))
+): Promise> {
+ const collection = await safeServerCall(() => readImageCollection(idOrName))
if (!collection.success) return collection
- if (!checkVisibility(await getUser(), collection.data.visibility, 'REGULAR')) {
- return createActionError('UNAUTHORIZED', 'You do not have permission to view this collection')
- }
-
return collection
}
@@ -71,10 +60,7 @@ export async function readImageCollectionAction(
export async function readImageCollectionsPageAction(
readPageInput: ReadPageInput
): Promise> {
- const { memberships, permissions } = await getUser()
- const visibilityFilter = getVisibilityFilter(memberships, permissions)
- console.log(visibilityFilter)
- return await safeServerCall(() => readImageCollectionsPage(readPageInput, visibilityFilter))
+ return await safeServerCall(() => readImageCollectionsPage(readPageInput))
}
/**
diff --git a/src/services/images/collections/create.ts b/src/services/images/collections/create.ts
index 66eaa424a..83e91d717 100644
--- a/src/services/images/collections/create.ts
+++ b/src/services/images/collections/create.ts
@@ -2,7 +2,7 @@ import '@pn-server-only'
import { createImageCollectionValidation } from './validation'
import { prisma } from '@/prisma/client'
import { prismaCall } from '@/services/prismaCall'
-import { createVisibility } from '@/services/visibility/create'
+import { visibilityOperations } from '@/services/visibility/operations'
import type { CreateImageCollectionTypes } from './validation'
import type { ImageCollection } from '@prisma/client'
@@ -10,13 +10,20 @@ export async function createImageCollection(
rawdata: CreateImageCollectionTypes['Detailed']
): Promise {
const data = createImageCollectionValidation.detailedValidate(rawdata)
- const visivility = await createVisibility('IMAGE')
+ const visibilityRead = await visibilityOperations.create({ bypassAuth: true })
+ const visibilityAdmin = await visibilityOperations.create({ bypassAuth: true })
+
return await prismaCall(() => prisma.imageCollection.create({
data: {
...data,
- visibility: {
+ visibilityRead: {
+ connect: {
+ id: visibilityRead.id
+ }
+ },
+ visibilityAdmin: {
connect: {
- id: visivility.id
+ id: visibilityAdmin.id
}
}
}
diff --git a/src/services/images/collections/destroy.ts b/src/services/images/collections/destroy.ts
index 07e1b7a53..1c823a04e 100644
--- a/src/services/images/collections/destroy.ts
+++ b/src/services/images/collections/destroy.ts
@@ -2,7 +2,7 @@ import '@pn-server-only'
import { prisma } from '@/prisma/client'
import { ServerError } from '@/services/error'
import { prismaCall } from '@/services/prismaCall'
-import { destroyVisibility } from '@/services/visibility/destroy'
+import { visibilityOperations } from '@/services/visibility/operations'
import type { ImageCollection } from '@prisma/client'
export async function destroyImageCollection(collectionId: number): Promise {
@@ -21,7 +21,7 @@ export async function destroyImageCollection(collectionId: number): Promise(
{ page }: ReadPageInput,
- visibilityFilter: VisibilityFilter
): Promise {
const collections = await prismaCall(() => prisma.imageCollection.findMany({
- where: visibilityFilter,
include: {
coverImage: true,
images: {
@@ -100,16 +96,26 @@ export async function readSpecialImageCollection(special: SpecialCollection): Pr
}))
if (!collection) {
logger.warn(`Special collection ${special} did not exist, creating it`)
- const permissionLevels = specialCollectionsSpecialVisibilityMap[special]
- const visability = await readSpecialVisibility(permissionLevels.specialVisibility)
+
+ //Note: these visibilities do not actually do anything for special collections,
+ //but the schema requires them to exist - believe this implementation to be better than for
+ //regular collections to maybe be in invalid state with no visibility.
+ // TODO: use create method.
+ const visibilityAdmin = await visibilityOperations.create({ bypassAuth: true })
+ const visibilityRead = await visibilityOperations.create({ bypassAuth: true })
const newCollection = await prismaCall(() => prisma.imageCollection.create({
data: {
name: special,
special,
- visibility: {
+ visibilityRead: {
+ connect: {
+ id: visibilityRead.id
+ }
+ },
+ visibilityAdmin: {
connect: {
- id: visability.id
+ id: visibilityAdmin.id
}
}
}
diff --git a/src/services/visibility/ConfigVars.ts b/src/services/visibility/ConfigVars.ts
deleted file mode 100644
index cf5b67e51..000000000
--- a/src/services/visibility/ConfigVars.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { Permission, VisibilityPurpose, SpecialVisibilityPurpose } from '@prisma/client'
-
-/**
- * These are the permissions that buypass the visibiity system. i.e. for reading images
- * if the user has the permission it does not matter if the visibility is fullfilled.
- */
-export const BypassPermissions = {
- IMAGE: 'IMAGE_ADMIN',
- NEWS_ARTICLE: 'FRONTPAGE_ADMIN', // just changed this to make ts happy - bypass and purposes will be removed
- ARTICLE_CATEGORY: 'FRONTPAGE_ADMIN', // just changed this to make ts happy - bypass and purposes will be removed
- EVENT: 'EVENT_ADMIN',
- SPECIAL: null // null means there is no bypass permission
-} as const satisfies { [key in VisibilityPurpose]: Permission | null }
-
-export type BypassPermissions = typeof BypassPermissions[keyof typeof BypassPermissions]
-
-export const purposeTextsConfig = {
- IMAGE: 'Bilder',
- NEWS_ARTICLE: 'Nyheter',
- ARTICLE_CATEGORY: 'Artikkelkategorier',
- EVENT: 'Arrangementer (Hvad der hender)',
- SPECIAL: 'Spessielle ting'
-} as const satisfies { [key in VisibilityPurpose]: string }
-
-/**
- * Which permissions link to special visibility purposes
- * If the special visibility were to disappear, it will be regenerated from this.
- */
-export const specialVisibilityConfig = {
- OMBUL: {
- regularLevel: 'OMBUL_READ',
- adminLevel: 'OMBUL_CREATE'
- },
- PUBLIC: {
- regularLevel: null,
- adminLevel: 'FRONTPAGE_ADMIN' // just changed this to make ts happy
- },
- COMMITTEE: {
- regularLevel: 'COMMITTEE_READ',
- adminLevel: 'COMMITTEE_CREATE'
- },
- USER: {
- regularLevel: 'USERS_READ',
- adminLevel: 'USERS_CREATE'
- }
-} satisfies {[T in SpecialVisibilityPurpose]: {
- regularLevel: Permission | null,
- adminLevel: Permission | null
-}}
diff --git a/src/services/visibility/actions.ts b/src/services/visibility/actions.ts
deleted file mode 100644
index 4585c2c29..000000000
--- a/src/services/visibility/actions.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-'use server'
-
-import { createActionError, safeServerCall } from '@/services/actionError'
-import { checkVisibility } from '@/auth/checkVisibility'
-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'
-
-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(() => 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]
-
- if (!checkVisibility(await getUser(), visibility, 'ADMIN')) {
- return createActionError('UNAUTHORIZED', 'You do not have permission to admin this collection')
- }
- if (visibility.type === 'SPECIAL') {
- return {
- success: true,
- data: {
- published: visibility.published,
- purpose,
- type: 'SPECIAL',
- message: 'Denne syneligheten er spessiell',
- regular: `Brukere med ${visibility.regular} har vanlig tilgang`,
- admin: `Brukere med ${visibility.admin} har admin tilgang`,
- }
- }
- }
- const standardGroupingsRefular = ['CLASS', 'OMEGA_MEMBERSHIP_GROUP', 'STUDY_PROGRAMME'] satisfies GroupType[]
- const standardGroupingsAdmin = ['COMMITTEE', 'OMEGA_MEMBERSHIP_GROUP'] satisfies GroupType[]
- return {
- success: true,
- data: {
- published: visibility.published,
- purpose,
- type: 'REGULAR',
- regular: expandOneLevel(visibility.regular, groups, standardGroupingsRefular),
- admin: expandOneLevel(visibility.admin, groups, standardGroupingsAdmin),
- groups,
- }
- }
-}
-
-function expandOneLevel(
- matrix: GroupMatrix,
- groups: GroupsStructured,
- standardGroupings: GroupType[]
-): VisibilityRequiermentForAdmin[] {
- const res: VisibilityRequiermentForAdmin[] = []
- standardGroupings.forEach(groupType => {
- if (!groups[groupType]) return
- const standardRequriment: VisibilityRequiermentForAdmin = {
- name: groupTypesConfig[groupType].name,
- groups: groups[groupType].groups.map(group => ({
- ...group,
- selected: false
- }))
- }
- //Find out if there is a row in the matrix with just the group type.
- for (let i = 0; i < matrix.length; i++) {
- if (matrix[i].every(groupId => groupTypeOfId(groupId, groups) === groupType)) {
- standardRequriment.groups.forEach(group => {
- group.selected = matrix[i].includes(group.id)
- })
- //Remove the row from the matrix since it has been handled
- matrix.splice(i, 1)
- }
- }
- res.push(standardRequriment)
- })
-
- //Handle all non standard groupings
- matrix.forEach(row => {
- const groups_ = row.reduce((acc, id) => {
- const group = findGroupOfId(id, groups)
- if (group) acc.push(group)
- return acc
- }, [] as ExpandedGroup[])
- const nonStandardRequriment: VisibilityRequiermentForAdmin = {
- name: 'ekstra',
- groups: groups_.map(group => ({
- ...group,
- selected: true
- }))
- }
- res.push(nonStandardRequriment)
- })
-
- return res
-}
-
-function findGroupOfId(id: number, groups: GroupsStructured): ExpandedGroup | null {
- let found: ExpandedGroup | null = null
- Object.values(groups).forEach(groupType => {
- groupType.groups.forEach(group => {
- if (group.id === id) {
- found = group
- }
- })
- })
- return found
-}
-
-function groupTypeOfId(id: number, groups: GroupsStructured): GroupType {
- let type: GroupType | null = null
- Object.values(groups).forEach((groupType) => {
- groupType.groups.forEach(group => {
- if (group.id === id) {
- type = group.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
deleted file mode 100644
index de946d19c..000000000
--- a/src/services/visibility/create.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import '@pn-server-only'
-import { updateVisibility } from './update'
-import { prismaCall } from '@/services/prismaCall'
-import { prisma } from '@/prisma/client'
-import type { Visibility, VisibilityPurpose } from '@prisma/client'
-import type { VisibilityLevelMatrices } from './types'
-
-/**
- * A function to create visibility
- * @param data - the visibility to create with.. If not given it will create a empty visibility (accessable to all)
- * @returns
- */
-export async function createVisibility(
- purpose: VisibilityPurpose,
- data?: VisibilityLevelMatrices
-): Promise {
- const visibility = await prismaCall(() => prisma.visibility.create({
- data: {
- purpose,
- regularLevel: {
- create: {}
- },
- adminLevel: {
- create: {}
- }
- }
- }))
- await updateVisibility(visibility.id, data || {
- admin: [],
- regular: []
- })
- return visibility
-}
diff --git a/src/services/visibility/destroy.ts b/src/services/visibility/destroy.ts
deleted file mode 100644
index bdfa7d279..000000000
--- a/src/services/visibility/destroy.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import '@pn-server-only'
-import { ServerError } from '@/services/error'
-import { prismaCall } from '@/services/prismaCall'
-import { prisma } from '@/prisma/client'
-
-/**
- * Destroy a visibility. This will also destroy the visibility levels associated with the visibility,
- * to prevent orphaned visibility levels.
- * @param id - The id of the visibility to destroy
- */
-export async function destroyVisibility(id: number): Promise {
- const visibility = await prismaCall(() => prisma.visibility.delete({
- where: {
- id
- }
- }))
- if (visibility.specialPurpose) {
- throw new ServerError('BAD PARAMETERS', 'Kan ikke slette en spesiell visibility')
- }
- await Promise.all([
- prismaCall(() => prisma.visibilityLevel.deleteMany({
- where: {
- id: visibility.adminLevelId
- }
- })),
- prismaCall(() => prisma.visibilityLevel.deleteMany({
- where: {
- id: visibility.regularLevelId
- }
- }))
- ])
-}
diff --git a/src/services/visibility/implement.ts b/src/services/visibility/implement.ts
new file mode 100644
index 000000000..5bd231d03
--- /dev/null
+++ b/src/services/visibility/implement.ts
@@ -0,0 +1,69 @@
+import { visibilityOperations } from './operations'
+import type { ArgsAuthGetterAndOwnershipCheck, PrismaPossibleTransaction } from '@/services/serviceOperation'
+import type { AutherResult } from '@/auth/auther/Auther'
+import type { Prisma } from '@prisma/client'
+import type { visibilitySchemas } from './schemas'
+import type { z } from 'zod'
+
+type ParamsSchema = typeof visibilitySchemas.params
+type OwnedVisibility = Prisma.ArticleGetPayload<{
+ include: {
+ coverImage: true,
+ articleSections: {
+ include: {
+ cmsImage: true,
+ cmsParagraph: true,
+ cmsLink: true,
+ }
+ }
+ }
+}>
+/**
+ * This utility implements the read and update operations for visibility
+ */
+export function implementVisibilityOperations<
+ const ImplementationParamsSchema extends z.ZodTypeAny
+>({
+ implementationParamsSchema,
+ authorizers,
+ ownedVisibility,
+}: {
+ implementationParamsSchema: ImplementationParamsSchema,
+ authorizers: {
+ update: (
+ args: {
+ prisma: PrismaPossibleTransaction,
+ implementationParams: z.infer
+ }
+ ) => AutherResult | Promise
+ read: (
+ args: {
+ prisma: PrismaPossibleTransaction,
+ implementationParams: z.infer
+ }
+ ) => AutherResult | Promise
+ },
+ ownedVisibility: (
+ args: {
+ prisma: PrismaPossibleTransaction,
+ implementationParams: z.infer
+ }
+ ) => Promise
+}) {
+ const ownershipCheckVisibility = async (
+ args: Omit, 'data'>
+ ) => (await ownedVisibility(args)).id === args.params.visibilityId
+
+ return {
+ read: visibilityOperations.read.implement({
+ implementationParamsSchema,
+ authorizer: authorizers.read,
+ ownershipCheck: ownershipCheckVisibility
+ }),
+ update: visibilityOperations.update.implement({
+ implementationParamsSchema,
+ authorizer: authorizers.update,
+ ownershipCheck: ownershipCheckVisibility
+ })
+ } as const
+}
diff --git a/src/services/visibility/operations.ts b/src/services/visibility/operations.ts
new file mode 100644
index 000000000..d34454325
--- /dev/null
+++ b/src/services/visibility/operations.ts
@@ -0,0 +1,81 @@
+import '@pn-server-only'
+import { visibilitySchemas } from './schemas'
+import { defineOperation, defineSubOperation } from '@/services/serviceOperation'
+import { readCurrentOmegaOrder } from '@/services/omegaOrder/read'
+import { ServerOnly } from '@/auth/auther/ServerOnly'
+import { ServerError } from '@/services/error'
+import type { VisibilityMatrix } from './types'
+
+export const visibilityOperations = {
+ create: defineOperation({
+ authorizer: ServerOnly,
+ operation: ({ prisma }) =>
+ prisma.visibility.create({ data: {} })
+ }),
+
+ destroy: defineOperation({
+ authorizer: ServerOnly,
+ paramsSchema: visibilitySchemas.params,
+ operation: ({ prisma, params }) =>
+ prisma.visibility.delete({ where: { id: params.visibilityId } })
+ }),
+
+ update: defineSubOperation({
+ paramsSchema: () => visibilitySchemas.params,
+ dataSchema: () => visibilitySchemas.update,
+ operation: () => async ({ prisma, params, data }) => {
+ //Remove all current visibilityRequirements (and by cascade all visibilityRequirementGroups)
+ await prisma.visibilityRequirement.deleteMany({
+ where: { visibilityId: params.visibilityId }
+ })
+
+ const currentOrder = await readCurrentOmegaOrder()
+
+ return prisma.visibility.update({
+ where: { id: params.visibilityId },
+ data: {
+ requirements: {
+ create: data.requirements.map(requirement => ({
+ conditions: {
+ create: requirement.conditions.map(condition => ({
+ groupId: condition.groupId,
+ type: condition.type,
+ order: condition.type === 'ORDER' ? condition.order : currentOrder.order
+ }))
+ }
+ }))
+ }
+ }
+ })
+ }
+ }),
+
+ read: defineSubOperation({
+ paramsSchema: () => visibilitySchemas.params,
+ operation: () => async ({ prisma, params }): Promise => {
+ const visibility = await prisma.visibility.findUnique({
+ where: { id: params.visibilityId },
+ include: {
+ requirements: {
+ include: {
+ conditions: true
+ }
+ }
+ }
+ })
+ if (!visibility) throw new ServerError('NOT FOUND', 'Fant ikke synlighet')
+ return {
+ requirements: visibility.requirements.map(requirement => ({
+ conditions: requirement.conditions.map(condition => (condition.type === 'ORDER' ? {
+ groupId: condition.groupId,
+ type: condition.type,
+ order: condition.order
+ } : {
+ groupId: condition.groupId,
+ type: condition.type,
+ }))
+ }))
+ }
+ }
+ })
+} as const
diff --git a/src/services/visibility/read.ts b/src/services/visibility/read.ts
deleted file mode 100644
index 824c1e163..000000000
--- a/src/services/visibility/read.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import '@pn-server-only'
-import { specialVisibilityConfig } from './ConfigVars'
-import { prismaCall } from '@/services/prismaCall'
-import { ServerError } from '@/services/error'
-import { prisma } from '@/prisma/client'
-import type { SpecialVisibilityPurpose, VisibilityRequirmenetGroup } from '@prisma/client'
-import type { VisibilityCollapsed } from './types'
-
-const levelSelector = {
- select: {
- permission: true,
- requirements: {
- select: {
- visibilityRequirmenetGroups: true
- }
- }
- }
-}
-
-/**
- * Reads visibility with levels, and with relation to groups, and returns the data
- * in a matrix format if type is REGULAR, or as permissions if type is SPECIAL i.e has
- * a special purpose.
- * @param id - the id of the visibility to read
- * @returns
- */
-export async function readVisibilityCollapsed(id: number): Promise {
- const visibility = await prismaCall(() => prisma.visibility.findUnique({
- where: { id },
- include: {
- regularLevel: levelSelector,
- adminLevel: levelSelector,
- }
- }))
- if (!visibility) throw new ServerError('NOT FOUND', `Visibility ikke funnet for id ${id}`)
- if (visibility.specialPurpose) {
- return {
- type: 'SPECIAL',
- published: visibility.published,
- id: visibility.id,
- regular: visibility.regularLevel.permission,
- admin: visibility.adminLevel.permission,
- purpose: visibility.purpose
- }
- }
- return {
- type: 'REGULAR',
- published: visibility.published,
- purpose: visibility.purpose,
- id: visibility.id,
- regular: collapseVisibilityLevel(visibility.regularLevel),
- admin: collapseVisibilityLevel(visibility.adminLevel)
- }
-}
-
-/**
- * This function reads a special visibility, and creates it if it does not exist
- * (this should not happen in production)
- * @param specialPurpose - The purpose of the special visibility
- * @returns - The visibility
- */
-export async function readSpecialVisibility(specialPurpose: SpecialVisibilityPurpose): Promise {
- const visibility = await prismaCall(() => prisma.visibility.findFirst({
- where: { specialPurpose },
- include: {
- regularLevel: levelSelector,
- adminLevel: levelSelector,
- }
- }))
- if (!visibility) {
- const created = await prismaCall(() => prisma.visibility.create({
- data: {
- purpose: 'SPECIAL',
- specialPurpose,
- published: true,
- regularLevel: {
- create: {
- permission: specialVisibilityConfig[specialPurpose].regularLevel
- }
- },
- adminLevel: {
- create: {
- permission: specialVisibilityConfig[specialPurpose].adminLevel
- }
- },
- },
- include: {
- regularLevel: levelSelector,
- adminLevel: levelSelector,
- }
- }))
- return {
- type: 'SPECIAL',
- published: created.published,
- id: created.id,
- regular: created.regularLevel.permission,
- admin: created.adminLevel.permission,
- purpose: created.purpose
- }
- }
- return {
- type: 'SPECIAL',
- published: visibility.published,
- id: visibility.id,
- regular: visibility.regularLevel.permission,
- admin: visibility.adminLevel.permission,
- purpose: visibility.purpose
- }
-}
-
-/**
- * A higher order function that includes visibility in the result of a server call
- * @param serverCall - A function to get some data, with a relation to visibility
- * @param getVisibilityId - A methode to retrive the visibility id from the result of the server call
- * @returns - The data from the server call, with the visibility included
- */
-export async function includeVisibility(serverCall: () => Promise, getVisibilityId: (result: T) => number) {
- const result = await serverCall()
- const visibility = await readVisibilityCollapsed(getVisibilityId(result))
- return { ...result, visibility }
-}
-
-function collapseVisibilityLevel(level: {requirements: {visibilityRequirmenetGroups: VisibilityRequirmenetGroup[]}[]}) {
- return level.requirements.map(requirement =>
- requirement.visibilityRequirmenetGroups.map(groupItem => groupItem.groupId)
- )
-}
diff --git a/src/services/visibility/schemas.ts b/src/services/visibility/schemas.ts
new file mode 100644
index 000000000..27f7af8cc
--- /dev/null
+++ b/src/services/visibility/schemas.ts
@@ -0,0 +1,32 @@
+import { VisibilityRequirementGroupType } from '@prisma/client'
+import { z } from 'zod'
+
+
+const baseSchema = z.object({
+ requirements: z.array(
+ z.object({
+ conditions: z.array(
+ z.union([
+ z.object({
+ type: z.literal(VisibilityRequirementGroupType.ORDER),
+ order: z.number().min(0),
+ groupId: z.number()
+ }),
+ z.object({
+ type: z.literal(VisibilityRequirementGroupType.ACTIVE),
+ groupId: z.number()
+ })
+ ])
+ )
+ })
+ )
+})
+
+export const visibilitySchemas = {
+ update: baseSchema.pick({
+ requirements: true
+ }),
+ params: z.object({
+ visibilityId: z.number()
+ })
+} as const
diff --git a/src/services/visibility/types.ts b/src/services/visibility/types.ts
index 39e4bb48e..0f268e089 100644
--- a/src/services/visibility/types.ts
+++ b/src/services/visibility/types.ts
@@ -1,55 +1,18 @@
-import type { ExpandedGroup, GroupsStructured } from '@/services/groups/types'
-import type { Matrix } from '@/lib/checkMatrix'
-import type { Permission, VisibilityPurpose } from '@prisma/client'
+import type { VisibilityRequirementGroupType } from '@prisma/client'
-export type GroupMatrix = Matrix
-
-export type VisibilityLevelType = 'ADMIN' | 'REGULAR'
-
-export type VisibilityLevelMatrices = {
- regular: GroupMatrix,
- admin: GroupMatrix
-}
-
-/**
- * A type that represents a visibility in a simple way.
- * Either type is SPECIAL and and levels are based on Permissions,
- * or type is REGULAR and the levels are represented by matrix of ids of groups
- */
-export type VisibilityCollapsed = {
- id: number,
- purpose: VisibilityPurpose
- published: boolean,
-} & ({
- type: 'REGULAR',
- regular: GroupMatrix,
- admin: GroupMatrix
+export type VisibilityCondition = {
+ type: Extract
+ groupId: number
+ order: number
} | {
- type: 'SPECIAL'
- regular: Permission | null,
- admin: Permission | null
-})
+ type: Extract
+ groupId: number
+}
-export type VisibilityRequiermentForAdmin = {
- name: string
- groups: (ExpandedGroup & {
- selected: boolean
- })[]
+export type VisibilityRequirement = {
+ conditions: VisibilityCondition[]
}
-export type VisibilityStructuredForAdmin = {
- published: boolean
- purpose: string
-} & (
- {
- type: 'REGULAR'
- groups: GroupsStructured
- regular: VisibilityRequiermentForAdmin[]
- admin: VisibilityRequiermentForAdmin[]
- } | {
- type: 'SPECIAL'
- message: string
- regular: string
- admin: string
- }
-)
+export type VisibilityMatrix = {
+ requirements: VisibilityRequirement[]
+}
diff --git a/src/services/visibility/update.ts b/src/services/visibility/update.ts
deleted file mode 100644
index 9b8697dd2..000000000
--- a/src/services/visibility/update.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import '@pn-server-only'
-import { prismaCall } from '@/services/prismaCall'
-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
-
- await prismaCall(() => prisma.$transaction([
- //first remove all the old conditions. This will also remove all join tables to groups on cascade.
- prisma.visibilityRequirement.deleteMany({
- where: {
- visibilityId: id
- }
- }),
- //Then create many new requirements
- prisma.visibility.update({
- where: {
- id
- },
- data: {
- regularLevel: {
- create: {
- requirements: {
- create: data.regular.map(row => ({
- visibilityRequirementGroups: {
- create: row.map(groupId => ({
- groupId
- }))
- }
- }))
- }
- }
- },
- adminLevel: {
- create: {
- requirements: {
- create: data.admin.map(row => ({
- visibilityRequirementGroups: {
- create: row.map(groupId => ({
- groupId
- }))
- }
- }))
- }
- }
- }
- }
- })
- ]))
-}
-
-export async function updateVisibilityPublished(id: number, published: boolean): Promise {
- await prismaCall(() => prisma.visibility.update({
- where: {
- id
- },
- data: {
- published
- }
- }))
-}