diff --git a/backend/api/profile.py b/backend/api/profile.py index e74042a4a..e05c37259 100644 --- a/backend/api/profile.py +++ b/backend/api/profile.py @@ -69,6 +69,8 @@ def update_profile( bio=profile.bio, linkedin=profile.linkedin, website=profile.website, + profile_emoji=profile.profile_emoji, + emoji_expiration=profile.emoji_expiration, ) user = user_svc.create(user, user) else: @@ -81,6 +83,8 @@ def update_profile( user.bio = profile.bio user.linkedin = profile.linkedin user.website = profile.website + user.profile_emoji = profile.profile_emoji + user.emoji_expiration = profile.emoji_expiration user = user_svc.update(user, user) user_details = user_svc.get(user.pid) diff --git a/backend/entities/user_entity.py b/backend/entities/user_entity.py index daeb4ae5d..755f7cef3 100644 --- a/backend/entities/user_entity.py +++ b/backend/entities/user_entity.py @@ -1,8 +1,9 @@ """Definition of SQLAlchemy table-backed object mapping entity for Users.""" -from sqlalchemy import Boolean, Integer, String +from sqlalchemy import Boolean, Integer, String, DateTime from sqlalchemy.orm import Mapped, mapped_column, relationship from typing import Self +from datetime import datetime from backend.entities.academics.section_member_entity import SectionMemberEntity from backend.models.academics.section_member import SectionMember @@ -54,6 +55,10 @@ class UserEntity(EntityBase): linkedin: Mapped[str | None] = mapped_column(String(), nullable=True) # Website of the user website: Mapped[str | None] = mapped_column(String(), nullable=True) + # Profile emoji for the user + profile_emoji: Mapped[str | None] = mapped_column(String(10), nullable=True) + # Expiration time for the profile emoji + emoji_expiration: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # All of the roles for the given user. # NOTE: This field establishes a many-to-many relationship between the users and roles table. @@ -124,6 +129,8 @@ def from_model(cls, model: User) -> Self: bio=model.bio, linkedin=model.linkedin, website=model.website, + profile_emoji=model.profile_emoji, + emoji_expiration=model.emoji_expiration, ) def to_model(self) -> User: @@ -148,6 +155,8 @@ def to_model(self) -> User: bio=self.bio, linkedin=self.linkedin, website=self.website, + profile_emoji=self.profile_emoji, + emoji_expiration=self.emoji_expiration, ) def update(self, model: User) -> None: @@ -171,6 +180,8 @@ def update(self, model: User) -> None: self.bio = model.bio self.linkedin = model.linkedin self.website = model.website + self.profile_emoji = model.profile_emoji + self.emoji_expiration = model.emoji_expiration def to_public_model(self) -> PublicUser: return PublicUser( @@ -185,4 +196,6 @@ def to_public_model(self) -> PublicUser: bio=self.bio, linkedin=self.linkedin, website=self.website, + profile_emoji=self.profile_emoji, + emoji_expiration=self.emoji_expiration, ) diff --git a/backend/migrations/versions/edc00c908bac_add_profile_emoji_fields.py b/backend/migrations/versions/edc00c908bac_add_profile_emoji_fields.py new file mode 100644 index 000000000..1417c386e --- /dev/null +++ b/backend/migrations/versions/edc00c908bac_add_profile_emoji_fields.py @@ -0,0 +1,26 @@ +"""add_profile_emoji_fields + +Revision ID: edc00c908bac +Revises: a9f09b49d862 +Create Date: 2025-10-04 13:30:55.791927 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'edc00c908bac' +down_revision = 'a9f09b49d862' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column("user", sa.Column("profile_emoji", sa.String(10), nullable=True)) + op.add_column("user", sa.Column("emoji_expiration", sa.DateTime(), nullable=True)) + + +def downgrade() -> None: + op.drop_column("user", "emoji_expiration") + op.drop_column("user", "profile_emoji") diff --git a/backend/models/public_user.py b/backend/models/public_user.py index 2cc8bf0eb..d88d764cb 100644 --- a/backend/models/public_user.py +++ b/backend/models/public_user.py @@ -1,4 +1,5 @@ from pydantic import BaseModel +from datetime import datetime from .registration_type import RegistrationType @@ -31,3 +32,5 @@ def __hash__(self) -> int: bio: str | None = None linkedin: str | None = None website: str | None = None + profile_emoji: str | None = None + emoji_expiration: datetime | None = None diff --git a/backend/models/user.py b/backend/models/user.py index cee9c2f4d..098369d45 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -1,6 +1,7 @@ """User model serves as the data object for representing registered users across application layers.""" from pydantic import BaseModel +from datetime import datetime __authors__ = ["Kris Jordan"] __copyright__ = "Copyright 2023" @@ -39,6 +40,8 @@ class User(UserIdentity, BaseModel): bio: str | None = None linkedin: str | None = None website: str | None = None + profile_emoji: str | None = None + emoji_expiration: datetime | None = None class NewUser(User, BaseModel): @@ -69,3 +72,5 @@ class ProfileForm(BaseModel): bio: str | None = None linkedin: str | None = None website: str | None = None + profile_emoji: str | None = None + emoji_expiration: datetime | None = None diff --git a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts index 213786613..59c001a5a 100644 --- a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts +++ b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts @@ -62,10 +62,10 @@ const canActivateEditor: CanActivateFn = ( } }; @Component({ - selector: 'app-section-editor', - templateUrl: './section-editor.component.html', - styleUrls: ['./section-editor.component.css'], - standalone: false + selector: 'app-section-editor', + templateUrl: './section-editor.component.html', + styleUrls: ['./section-editor.component.css'], + standalone: false }) export class SectionEditorComponent { /** Route information to be used in the Routing Module */ @@ -213,7 +213,9 @@ export class SectionEditorComponent { github: '', bio: '', linkedin: '', - website: '' + website: '', + emoji_expiration: null, + profile_emoji: null }; }) ?? []; } diff --git a/frontend/src/app/event/event-details/event-details.component.html b/frontend/src/app/event/event-details/event-details.component.html index 7135f3d75..628b64603 100644 --- a/frontend/src/app/event/event-details/event-details.component.html +++ b/frontend/src/app/event/event-details/event-details.component.html @@ -65,7 +65,7 @@ [src]="organizer.github_avatar" /> } - {{ organizer.first_name }} {{ organizer.last_name }} + {{ getDisplayName(organizer) }} } diff --git a/frontend/src/app/event/event-details/event-details.component.ts b/frontend/src/app/event/event-details/event-details.component.ts index 796d6772e..9ffc72137 100644 --- a/frontend/src/app/event/event-details/event-details.component.ts +++ b/frontend/src/app/event/event-details/event-details.component.ts @@ -9,7 +9,7 @@ import { Component, OnInit, WritableSignal, signal } from '@angular/core'; import { eventResolver } from '../event.resolver'; -import { Profile, ProfileService } from 'src/app/profile/profile.service'; +import { Profile, ProfileService, PublicProfile } from 'src/app/profile/profile.service'; import { ActivatedRoute, ActivatedRouteSnapshot, @@ -178,4 +178,20 @@ export class EventDetailsComponent implements OnInit { .getRegisteredUsersForEvent(this.event(), paginationParams) .subscribe((page) => this.eventRegistrationsPage.set(page)); } + + /** Check if emoji should be displayed for a user (not expired) */ + shouldDisplayEmoji(user: PublicProfile): boolean { + if (!user.profile_emoji) return false; + if (!user.emoji_expiration) return true; + return new Date(user.emoji_expiration) > new Date(); + } + + /** Get display name with emoji if applicable */ + getDisplayName(user: PublicProfile): string { + const name = `${user.first_name} ${user.last_name}`; + if (this.shouldDisplayEmoji(user)) { + return `${name} ${user.profile_emoji}`; + } + return name; + } } diff --git a/frontend/src/app/models.module.ts b/frontend/src/app/models.module.ts index 2452d89a7..9cac3e1f3 100644 --- a/frontend/src/app/models.module.ts +++ b/frontend/src/app/models.module.ts @@ -24,6 +24,8 @@ export interface Profile { bio: string | null; linkedin: string | null; website: string | null; + profile_emoji: string | null; + emoji_expiration: Date | null; } /** Interface for UserSummary Type (used on frontend for user requests) */ diff --git a/frontend/src/app/navigation/navigation.component.html b/frontend/src/app/navigation/navigation.component.html index de8a06589..cd32b93fa 100644 --- a/frontend/src/app/navigation/navigation.component.html +++ b/frontend/src/app/navigation/navigation.component.html @@ -154,7 +154,7 @@