Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/Interest Group Pages #347

Merged
merged 16 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
212123c
chore: adds cms paragraph for general interestgroups info and article…
JohanHjelsethStorstad Oct 18, 2024
f5ae912
fix: interest group dev seeding after adding articleSection to schema
JohanHjelsethStorstad Oct 18, 2024
33c0894
chore: adds basic interest-groups page with paragraph and correct lin…
JohanHjelsethStorstad Oct 18, 2024
dde7a3a
chore: changes the interest-group read service-methods to actually be…
JohanHjelsethStorstad Oct 18, 2024
2ab8e34
feat: display list of all interest-groups to end user
JohanHjelsethStorstad Oct 18, 2024
ed4cf49
feat: create interest groups from UI
JohanHjelsethStorstad Oct 18, 2024
370398a
chore: makes ceate form display better
JohanHjelsethStorstad Oct 18, 2024
40d2d60
refactor: brakes interestGroup into seperate component to handle auth…
JohanHjelsethStorstad Oct 18, 2024
ac0de46
chore: adds backend functionality to update meta about interest group
JohanHjelsethStorstad Oct 18, 2024
644a4a6
feat: adds UI to update interest group
JohanHjelsethStorstad Oct 18, 2024
2d0ab82
chore: adds action to delete interest group
JohanHjelsethStorstad Oct 18, 2024
4cfab6f
fix: also delete group when deleting interestGroup
JohanHjelsethStorstad Oct 18, 2024
32775b5
fix: make sure to invalidate sessions of user when memberships are up…
JohanHjelsethStorstad Oct 18, 2024
dc3970a
fix: makes sure membership is active in RequirePermissionOrGroupAdmin…
JohanHjelsethStorstad Oct 18, 2024
05b795c
style: linting
JohanHjelsethStorstad Oct 18, 2024
e76fc78
Merge branch 'main' into feat/interest-group-pages
JohanHjelsethStorstad Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/actions/groups/interestGroups/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use server'
import { Action } from '@/actions/Action'
import { InterestGroups } from '@/services/groups/interestGroups'

export const createInterestGroupAction = Action(InterestGroups.create)
5 changes: 5 additions & 0 deletions src/actions/groups/interestGroups/destroy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use server'
import { ActionNoData } from '@/actions/Action'
import { InterestGroups } from '@/services/groups/interestGroups'

export const destroyInterestGroupAction = ActionNoData(InterestGroups.destroy)
5 changes: 5 additions & 0 deletions src/actions/groups/interestGroups/read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use server'
import { ActionNoData } from '@/actions/Action'
import { InterestGroups } from '@/services/groups/interestGroups'

export const readInterestGroupsAction = ActionNoData(InterestGroups.readAll)
5 changes: 5 additions & 0 deletions src/actions/groups/interestGroups/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use server'
import { Action } from '@/actions/Action'
import { InterestGroups } from '@/services/groups/interestGroups'

export const updateInterestGroupAction = Action(InterestGroups.update)
2 changes: 1 addition & 1 deletion src/app/_components/NavBar/navDef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const itemsForMenu: NavItem[] = [
},
{
name: 'Intressegrupper',
href: '/infopages/interessegrupper',
href: '/interest-groups',
show: 'all',
icon: faGamepad,
},
Expand Down
19 changes: 19 additions & 0 deletions src/app/interest-groups/CreateInterestGroupForm.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@use '@/styles/ohma';

.CreateInterestGroupForm {
min-width: 50vw;
min-height: 50vh;
display: grid;
place-items: center;
form {
display: grid;
place-items: center;
> * {
width: 200px;
margin: 0;
}
button[type="submit"] {
width: 220px;
}
}
}
20 changes: 20 additions & 0 deletions src/app/interest-groups/CreateInterestGroupForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import styles from './CreateInterestGroupForm.module.scss'
import Form from '@/components/Form/Form'
import { createInterestGroupAction } from '@/actions/groups/interestGroups/create'
import TextInput from '@/components/UI/TextInput'

export default function CreateInterestGroupForm() {
return (
<div className={styles.CreateInterestGroupForm}>
<h2>Lag interessegruppe</h2>
<Form
refreshOnSuccess
action={createInterestGroupAction.bind(null, {})}
submitText="Lag interessegruppe"
>
<TextInput name="name" label="Navn" />
<TextInput name="shortName" label="Kortnavn" />
</Form>
</div>
)
}
16 changes: 16 additions & 0 deletions src/app/interest-groups/InterestGroup.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@use '@/styles/ohma';

.interestGroup {
margin-top: 2rem;
border-top: 8px solid ohma.$colors-secondary;
padding-top: 1rem;
position: relative;
h2 {
font-size: ohma.$fonts-xl;
}
.admin {
position: absolute;
top: 1em;
right: 1em;
}
}
80 changes: 80 additions & 0 deletions src/app/interest-groups/InterestGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import styles from './InterestGroup.module.scss'
import Form from '@/components/Form/Form'
import TextInput from '@/components/UI/TextInput'
import ArticleSection from '@/components/Cms/ArticleSection/ArticleSection'
import { DestroyInterestGroupAuther, UpdateInterestGroupAuther } from '@/services/groups/interestGroups/Auther'
import { SettingsHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp'
import { updateInterestGroupAction } from '@/actions/groups/interestGroups/update'
import { destroyInterestGroupAction } from '@/actions/groups/interestGroups/destroy'
import type { SessionMaybeUser } from '@/auth/Session'
import type { ExpandedInterestGroup } from '@/services/groups/interestGroups/Types'

type PropTypes = {
interestGroup: ExpandedInterestGroup
session: SessionMaybeUser
}

export default function InterestGroup({ interestGroup, session }: PropTypes) {
const canUpdate = UpdateInterestGroupAuther.dynamicFields({ groupId: interestGroup.groupId }).auth(session)
const canDestroy = DestroyInterestGroupAuther.dynamicFields({}).auth(session)

const PopUpKey = `Update interest group ${interestGroup.name}`

return (
<div className={styles.interestGroup}>
<h2>{interestGroup.name}</h2>
<div className={styles.admin}>
{
canUpdate.authorized || canDestroy.authorized ? (
<SettingsHeaderItemPopUp PopUpKey={PopUpKey}>
{
canUpdate.authorized && (
<>
<h2>Update interest group</h2>
<Form
refreshOnSuccess
closePopUpOnSuccess={PopUpKey}
action={updateInterestGroupAction.bind(
null, { id: interestGroup.id }
)}
submitText="Endre"
>
<TextInput
defaultValue={interestGroup.name}
name="name"
label="Navn"
/>
<TextInput
defaultValue={interestGroup.shortName}
name="shortName"
label="Kortnavn"
/>
</Form>
</>
)
}
{
canDestroy.authorized && (
<Form
refreshOnSuccess
closePopUpOnSuccess={PopUpKey}
action={destroyInterestGroupAction.bind(
null, { id: interestGroup.id }
)}
submitText="Slett"
submitColor="red"
confirmation={{
confirm: true,
text: `Er du sikker på at du vil slette ${interestGroup.name}?`
}}
/>
)
}
</SettingsHeaderItemPopUp>
) : <></>
}
</div>
<ArticleSection key={interestGroup.id} articleSection={interestGroup.articleSection} />
</div>
)
}
34 changes: 34 additions & 0 deletions src/app/interest-groups/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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 { CreateInterestGroupAuther } from '@/services/groups/interestGroups/Auther'

export default async function InterestGroups() {
const { session, ...interestGroupsRes } = await readInterestGroupsAction.bind(null, {})()
if (!interestGroupsRes.success) return <div>Failed to load interest groups</div> //TODO: Change to unwrap
const interestGroups = interestGroupsRes.data

const canCreate = CreateInterestGroupAuther.dynamicFields({}).auth(session)

return (
<PageWrapper title="Interessegrupper" headerItem={
canCreate.authorized && (
<AddHeaderItemPopUp PopUpKey="Create interest group">
<CreateInterestGroupForm />
</AddHeaderItemPopUp>
)
}>
<SpecialCmsParagraph special="INTEREST_GROUP_GENERAL_INFO" />
<main>
{
interestGroups.map(interestGroup => (
<InterestGroup session={session} key={interestGroup.id} interestGroup={interestGroup} />
))
}
</main>
</PageWrapper>
)
}
13 changes: 13 additions & 0 deletions src/auth/auther/RequirePermissionOrGroupAdmin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AutherFactory } from './Auther'
import type { Permission } from '@prisma/client'

export const RequirePermissionOrGroupAdmin = AutherFactory<
{ permission: Permission },
{ groupId: number },
'USER_NOT_REQUIERED_FOR_AUTHORIZED'
>(({ session, staticFields, dynamicFields }) => ({
success: session.permissions.includes(staticFields.permission) || session.memberships.some(
membersip => membersip.groupId === dynamicFields.groupId && membersip.admin && membersip.active
),
session,
}))
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Har du en interesse du har lyst til å dele med andre? Interessegrupper er noe vi har startet for at det skal være lettere for deg å samle folk rundt en felles interesse.

**Hva er en interessegruppe?** En interessegruppe er en gruppe med studenter som møtes for å snakke om eller dyrke en interesse. En interessegruppe kan omhandle akkurat det du interesserer deg for som du ønsker å dele med andre. Er det noe du føler mangler av tilbud i studenttilværelsen, er det godt mulig at interessegrupper er noe for deg!

**Hvordan bli en interessegruppe?** Det er meget lett å bli en interessegruppe, bare send en mail til [email protected] med navn på gruppen, kontaktinformasjon gruppeleder og en kort beskrivelse av hva dere gjør. Hvis dere trenger ekstra utstyr er det mulig å søke støtte fra fondet.

**Ønsker du å opprette en gruppe?** Send en mail til [email protected], så tar vi kontakt.

Få støtte! Ønsker din gruppe å søke støtte, send en søknad til [email protected]. For å få penger må gruppen bestå av flere aktive medlemmer i Omega, samt ha klart et budsjett over hva de planlegger å bruke pengene på. Søknaden vil så bli vurdert av fondsstyret. Det er altså ikke en garanti å få penger, men hvis formålet er noe Broedre iitem systre kan nyte godt av lover det godt for søknaden.

Høres dette vrient ut, så fortvil ikke: Vi er her for å hjelpe dere.
7 changes: 7 additions & 0 deletions src/prisma/prismaservice/src/development/seedDevGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ export default async function seedDevGroups(prisma: PrismaClient) {
data: {
name: `Interessegruppe ${i}`,
shortName: `IG${i}`,
articleSection: {
create: {
cmsImage: { create: {} },
cmsParagraph: { create: {} },
cmsLink: { create: {} },
}
},
group: {
create: {
groupType: 'INTEREST_GROUP',
Expand Down
4 changes: 4 additions & 0 deletions src/prisma/prismaservice/src/seedCmsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ export const seedSpecialCmsParagraphConfig: CmsParagraphSeedSpecialConfig = {
name: 'frontpage_4_paragraph',
file: 'frontpage/frontpage_4.md'
},
INTEREST_GROUP_GENERAL_INFO: {
name: 'interest_group_general_info',
file: 'interest_groups/general_info.md'
}
}


Expand Down
5 changes: 4 additions & 1 deletion src/prisma/schema/cms.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ enum SpecialCmsParagraph {
FRONTPAGE_2
FRONTPAGE_3
FRONTPAGE_4

INTEREST_GROUP_GENERAL_INFO
}

model CmsParagraph {
Expand Down Expand Up @@ -91,10 +93,11 @@ model ArticleSection {
order Int @default(autoincrement()) //The order "position" of the article section in the article

//If true, the article section will be deleted if it is empty, ie has no relation to paragraph, link or image
destroyOnEmpty Boolean @default(true)
destroyOnEmpty Boolean @default(true)
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
}
Expand Down
5 changes: 4 additions & 1 deletion src/prisma/schema/group.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ model InterestGroup {

name String
shortName String @unique
// TODOD - add intereset group data

articleSection ArticleSection @relation(fields: [articleSectionId], references: [id], onDelete: Restrict, onUpdate: Cascade)
articleSectionId Int @unique
// TODO - add intereset group data
}

model ManualGroup {
Expand Down
4 changes: 1 addition & 3 deletions src/prisma/schema/permission.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,8 @@ enum Permission {
COMMITTEE_DESTROY

// Groups - InterestGroups
INTEREST_GROUP_CREATE
INTEREST_GROUP_ADMIN
INTEREST_GROUP_READ
INTEREST_GROUP_UPDATE
INTEREST_GROUP_DESTROY

// Groups - OmegaMembershipGroup
OMEGA_MEMBERSHIP_GROUP_READ
Expand Down
13 changes: 9 additions & 4 deletions src/services/ServiceMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,13 @@ export function ServiceMethod<
return config.withData ? {
withData: true,
client: (prisma) => ({
execute: ({ data, params, session }, authRunConfig) => {
execute: async ({ data, params, session }, authRunConfig) => {
if (authRunConfig.withAuth) {
const authRes = config.auther.dynamicFields(
config.dynamicFields({
config.dynamicFields ? config.dynamicFields({
params,
data: config.serviceMethodHandler.detailedValidate(data)
}) : await config.dynamicFieldsAsync({
params,
data: config.serviceMethodHandler.detailedValidate(data)
})
Expand All @@ -99,10 +102,12 @@ export function ServiceMethod<
} satisfies ServiceMethod<true, TypeType, DetailedType, Params, Return, WantsToOpenTransaction, false> : {
withData: false,
client: (prisma) => ({
execute: ({ params, session }, authRunConfig) => {
execute: async ({ params, session }, authRunConfig) => {
if (authRunConfig.withAuth) {
const authRes = config.auther.dynamicFields(
config.dynamicFields({
config.dynamicFields ? config.dynamicFields({
params
}) : await config.dynamicFieldsAsync({
params
})
).auth(
Expand Down
8 changes: 7 additions & 1 deletion src/services/ServiceTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export type ServiceMethodHandlerConfig<
) => Promise<Return>,
} : {
withData: false,
wantsToOpenTransaction?: WantsToOpenTransaction,
handler: (
prisma: PrismaPossibleTransaction<WantsToOpenTransaction>,
params: Params,
Expand All @@ -172,8 +173,13 @@ type ServiceMethodHandlerAuthConfig<
DynamicFields extends object,
> = {
auther: AutherStaticFieldsBound<DynamicFields, 'USER_NOT_REQUIERED_FOR_AUTHORIZED' | 'USER_REQUIERED_FOR_AUTHORIZED'>
} & ({
dynamicFields: (dataParams: DynamicFieldsInput<WithValidation, Params, DetailedType>) => DynamicFields
}
dynamicFieldsAsync?: never
} | {
dynamicFieldsAsync: (dataParams: DynamicFieldsInput<WithValidation, Params, DetailedType>) => Promise<DynamicFields>
dynamicFields?: never
})

export type ServiceMethodConfig<
WithValidation extends boolean,
Expand Down
10 changes: 10 additions & 0 deletions src/services/groups/interestGroups/Auther.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { RequirePermission } from '@/auth/auther/RequirePermission'
import { RequirePermissionOrGroupAdmin } from '@/auth/auther/RequirePermissionOrGroupAdmin'

export const ReadInterestGroupAuther = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_READ' })

export const CreateInterestGroupAuther = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_ADMIN' })

export const UpdateInterestGroupAuther = RequirePermissionOrGroupAdmin.staticFields({ permission: 'INTEREST_GROUP_ADMIN' })

export const DestroyInterestGroupAuther = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_ADMIN' })
5 changes: 4 additions & 1 deletion src/services/groups/interestGroups/Types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import type { ExpandedArticleSection } from '@/services/cms/articleSections/Types'
import type { InterestGroup } from '@prisma/client'

export type ExpandedInterestGroup = InterestGroup
export type ExpandedInterestGroup = InterestGroup & {
articleSection: ExpandedArticleSection
}
Loading
Loading