Skip to content

feat(event): restrict event join to piscine/42cursus audience - #706

Open
4n4k1n wants to merge 5 commits into
devfrom
restrict-event-join
Open

feat(event): restrict event join to piscine/42cursus audience#706
4n4k1n wants to merge 5 commits into
devfrom
restrict-event-join

Conversation

@4n4k1n

@4n4k1n 4n4k1n commented Jul 18, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Events can now target all participants, Piscine students, or 42cursus students via a new audience setting.
    • Event creators can select the audience when creating events (and it’s supported in event settings updates).
    • “My Team” now gates access based on registration/eligibility and shows audience-aware messaging.
    • 42 account details now include Piscine and 42cursus enrollment flags.
  • Bug Fixes

    • Improved and more consistent messaging when users cannot access an event’s team area due to audience eligibility/registration.

@4n4k1n

4n4k1n commented Jul 18, 2026

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@PaulicStudios, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: baa959ac-bae1-43e8-bb3b-4538d7186d39

📥 Commits

Reviewing files that changed from the base of the PR and between a5e3fc5 and 85050ba.

📒 Files selected for processing (9)
  • api/db/migrations/1784375696042-socialAccountCursusFlags.ts
  • api/src/auth/auth.controller.ts
  • api/src/auth/fortytwo.strategy.ts
  • api/src/event/event.service.ts
  • api/src/user/entities/social-account.entity.ts
  • api/src/user/social-account.service.ts
  • frontend/src/app/actions/social-accounts.ts
  • frontend/src/lib/constants/cursus-status.ts
  • frontend/src/routes/events/$id/my-team.tsx
📝 Walkthrough

Walkthrough

Adds Piscine/cursus enrollment flags from 42 OAuth profiles, introduces audience-scoped events, enforces audience eligibility during registration, and updates event creation and team-access UI flows.

Changes

Event audience eligibility

Layer / File(s) Summary
42 enrollment flag persistence
api/src/auth/..., api/src/user/..., api/db/migrations/1784375696042-socialAccountCursusFlags.ts
42 cursus data is converted into Piscine and cursus flags and persisted on social accounts during OAuth upsert.
Event audience contract and storage
api/src/event/entities/event.entity.ts, api/src/event/dtos/..., api/db/migrations/1784375696043-eventAudience.ts
Events support PISCINE, CURSUS, and BOTH audiences with validated DTO fields and database persistence.
Backend audience registration flow
api/src/event/event.controller.ts, api/src/event/event.service.ts
Event creation and settings updates carry audience values, while joining and registration enforce audience eligibility alongside private-event checks.
Frontend audience creation and team access
frontend/src/lib/constants/event-audience.ts, frontend/src/app/actions/..., frontend/src/routes/events/..., frontend/src/components/event-navbar.tsx
Event types and creation forms expose audience selection, and My Team checks registration before rendering team content.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant EventController
  participant EventService
  participant SocialAccountService
  User->>EventController: Request to join event
  EventController->>EventService: Check event audience
  EventService->>SocialAccountService: Read FortyTwo enrollment flags
  SocialAccountService-->>EventService: Piscine/cursus eligibility
  EventService-->>EventController: Accept or reject registration
  EventController-->>User: Join result
Loading

Possibly related PRs

Suggested reviewers: peu77

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: restricting event joining by audience to piscine/42cursus users.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch restrict-event-join

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved the merge conflicts in this PR and pushed the fix in commit a5e3fc5.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
api/src/event/entities/event.entity.ts (1)

107-109: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using type: "enum" for consistency with other entities.

In SocialAccountEntity, enums are mapped using @Column({ type: "enum", enum: ... }). Using type: "text" works, but explicitly using the enum type provides stricter database-level validation and consistency across your entities.

♻️ Proposed fix to map as an enum
-  `@Column`({ type: "text", default: EventAudience.BOTH })
+  `@Column`({ type: "enum", enum: EventAudience, default: EventAudience.BOTH })
   audience: EventAudience;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/event/entities/event.entity.ts` around lines 107 - 109, Update the
audience column in EventEntity to use the enum column type, supplying
EventAudience through the enum option. Preserve EventAudience.BOTH as the
default value and match the mapping pattern used by SocialAccountEntity.
api/src/event/event.controller.ts (1)

178-204: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optimize social account fetching to avoid duplicate queries.

this.userService.getUserWithSocialAccounts(userId) already retrieves the user's social accounts, including their 42 profile. Calling this.eventService.isUserEligibleForEventAudience(event.audience, userId) immediately afterwards triggers a second database query for the exact same social account.

Consider updating isUserEligibleForEventAudience to accept an optional fortyTwoAccount argument (or evaluating the boolean flags directly in the controller) to eliminate the redundant database hit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/event/event.controller.ts` around lines 178 - 204, Avoid the
duplicate social-account query in the event join flow: update
eventService.isUserEligibleForEventAudience to accept and reuse the
already-resolved fortyTwoAccount from getUserWithSocialAccounts, or evaluate the
audience eligibility directly in the controller. Preserve the existing audience
validation behavior while ensuring no second lookup occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/routes/events/`$id/my-team.tsx:
- Around line 194-215: Update the unregistered-user branch in the event page to
use the social account’s isPiscineStudent and isCursusStudent eligibility flags,
rather than merely checking whether fortyTwoAccountQuery.data exists. Show the
audience-mismatch message only when the user lacks the required flag for the
event audience; otherwise retain the message directing the eligible user to join
from the Info tab.
- Around line 171-175: Update the loading condition in the event team route to
include fortyTwoAccountQuery.isLoading alongside the existing pending checks, so
the account-linking UI is not rendered while the enabled query is fetching for
the first time.

---

Nitpick comments:
In `@api/src/event/entities/event.entity.ts`:
- Around line 107-109: Update the audience column in EventEntity to use the enum
column type, supplying EventAudience through the enum option. Preserve
EventAudience.BOTH as the default value and match the mapping pattern used by
SocialAccountEntity.

In `@api/src/event/event.controller.ts`:
- Around line 178-204: Avoid the duplicate social-account query in the event
join flow: update eventService.isUserEligibleForEventAudience to accept and
reuse the already-resolved fortyTwoAccount from getUserWithSocialAccounts, or
evaluate the audience eligibility directly in the controller. Preserve the
existing audience validation behavior while ensuring no second lookup occurs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5d686e1-6d62-41a5-805b-6cf1868c8ae0

📥 Commits

Reviewing files that changed from the base of the PR and between 7d57f4e and a2d6804.

📒 Files selected for processing (17)
  • api/db/migrations/1784375696042-socialAccountCursusFlags.ts
  • api/db/migrations/1784375696043-eventAudience.ts
  • api/src/auth/auth.controller.ts
  • api/src/auth/fortytwo.strategy.ts
  • api/src/event/dtos/createEventDto.ts
  • api/src/event/dtos/updateEventSettingsDto.ts
  • api/src/event/entities/event.entity.ts
  • api/src/event/event.controller.ts
  • api/src/event/event.service.ts
  • api/src/user/entities/social-account.entity.ts
  • api/src/user/social-account.service.ts
  • frontend/src/app/actions/event.ts
  • frontend/src/app/actions/social-accounts.ts
  • frontend/src/components/event-navbar.tsx
  • frontend/src/lib/constants/event-audience.ts
  • frontend/src/routes/events/$id/my-team.tsx
  • frontend/src/routes/events/create.tsx

Comment thread frontend/src/routes/events/$id/my-team.tsx
Comment on lines +194 to +215
if (!isRegisteredQuery.data) {
const audience = eventQuery.data.audience
const audienceLabel =
audience === EventAudience.PISCINE ? 'piscine' : '42cursus'

const description =
audience !== EventAudience.BOTH
? fortyTwoAccountQuery.data
? `This event is only open to ${audienceLabel} students, and your linked 42 intra account doesn't meet that requirement.`
: `This event is only open to ${audienceLabel} students. Link your 42 intra account in your profile settings to check your eligibility.`
: "You haven't joined this event yet. Head to the Info tab to join."

return (
<main className="container mx-auto max-w-4xl px-4 py-8">
<Alert>
<AlertTitle>Team unavailable</AlertTitle>
<AlertDescription>{description}</AlertDescription>
</Alert>
</main>
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix eligibility logic for unregistered users on private events.

For private events, isRegisteredQuery.data being false can simply mean the user hasn't joined the event yet, even if they are perfectly eligible. The current logic incorrectly assumes that if they aren't registered and fortyTwoAccountQuery.data exists, they must be ineligible, resulting in an erroneous "doesn't meet that requirement" message.

Verify the actual eligibility flags (isPiscineStudent / isCursusStudent) on the user's social account to distinguish between a truly ineligible user and an eligible user who just needs to click join.

🐛 Proposed fix
-    const description =
-      audience !== EventAudience.BOTH
-        ? fortyTwoAccountQuery.data
-          ? `This event is only open to ${audienceLabel} students, and your linked 42 intra account doesn't meet that requirement.`
-          : `This event is only open to ${audienceLabel} students. Link your 42 intra account in your profile settings to check your eligibility.`
-        : "You haven't joined this event yet. Head to the Info tab to join."
+    let description = "You haven't joined this event yet. Head to the Info tab to join."
+
+    if (audience !== EventAudience.BOTH) {
+      if (!fortyTwoAccountQuery.data) {
+        description = `This event is only open to ${audienceLabel} students. Link your 42 intra account in your profile settings to check your eligibility.`
+      } else {
+        const isEligible =
+          audience === EventAudience.PISCINE
+            ? fortyTwoAccountQuery.data.isPiscineStudent
+            : fortyTwoAccountQuery.data.isCursusStudent
+
+        if (!isEligible) {
+          description = `This event is only open to ${audienceLabel} students, and your linked 42 intra account doesn't meet that requirement.`
+        }
+      }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!isRegisteredQuery.data) {
const audience = eventQuery.data.audience
const audienceLabel =
audience === EventAudience.PISCINE ? 'piscine' : '42cursus'
const description =
audience !== EventAudience.BOTH
? fortyTwoAccountQuery.data
? `This event is only open to ${audienceLabel} students, and your linked 42 intra account doesn't meet that requirement.`
: `This event is only open to ${audienceLabel} students. Link your 42 intra account in your profile settings to check your eligibility.`
: "You haven't joined this event yet. Head to the Info tab to join."
return (
<main className="container mx-auto max-w-4xl px-4 py-8">
<Alert>
<AlertTitle>Team unavailable</AlertTitle>
<AlertDescription>{description}</AlertDescription>
</Alert>
</main>
)
}
if (!isRegisteredQuery.data) {
const audience = eventQuery.data.audience
const audienceLabel =
audience === EventAudience.PISCINE ? 'piscine' : '42cursus'
let description = "You haven't joined this event yet. Head to the Info tab to join."
if (audience !== EventAudience.BOTH) {
if (!fortyTwoAccountQuery.data) {
description = `This event is only open to ${audienceLabel} students. Link your 42 intra account in your profile settings to check your eligibility.`
} else {
const isEligible =
audience === EventAudience.PISCINE
? fortyTwoAccountQuery.data.isPiscineStudent
: fortyTwoAccountQuery.data.isCursusStudent
if (!isEligible) {
description = `This event is only open to ${audienceLabel} students, and your linked 42 intra account doesn't meet that requirement.`
}
}
}
return (
<main className="container mx-auto max-w-4xl px-4 py-8">
<Alert>
<AlertTitle>Team unavailable</AlertTitle>
<AlertDescription>{description}</AlertDescription>
</Alert>
</main>
)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/routes/events/`$id/my-team.tsx around lines 194 - 215, Update
the unregistered-user branch in the event page to use the social account’s
isPiscineStudent and isCursusStudent eligibility flags, rather than merely
checking whether fortyTwoAccountQuery.data exists. Show the audience-mismatch
message only when the user lacks the required flag for the event audience;
otherwise retain the message directing the eligible user to join from the Info
tab.

4n4k1n and others added 2 commits July 18, 2026 18:03
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@PaulicStudios PaulicStudios left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also add the option to set an audience in the dashboard while the event is running

Comment on lines +52 to -56
{ name: 'My Team', path: `/events/${eventId}/my-team` },
]

if (isUserRegistered) {
items.push({ name: 'My Team', path: `/events/${eventId}/my-team` })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert, the isUserRegistered should be fixed instead so it returns true if an event is public && matches the audience

export enum EventAudience {
PISCINE = 'PISCINE',
CURSUS = 'CURSUS',
BOTH = 'BOTH',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call it all in case there will be more options

@PaulicStudios

Copy link
Copy Markdown
Member

Also update the Datenschutz page to include that we save if you are an active student or a pisciner

Comment on lines +198 to +199
? "This event is only open to piscine students."
: "This event is only open to 42cursus students.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add here that if you think this is wrong please relink your account in case your status updated. Or as there are already students linked they have to link again to get their correct status

@4n4k1n

4n4k1n commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

@Peu77

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants