feat: Implement hackathon registration module with controller, DTOs, repository, schema, and service

- Added registration_controller.rs to handle registration-related routes and logic.
- Created registration_dto.rs for data transfer objects related to registrations.
- Implemented registration_repository.rs for database interactions concerning registrations.
- Defined registration_schema.rs to represent the registration data structure.
- Developed registration_service.rs to encapsulate business logic for registrations.
- Established routes for registering, listing, updating, and checking in participants for hackathons.
- Added validation for registration requests and status updates.
- Included statistics retrieval for hackathon registrations.
This commit is contained in:
MythEclipse
2025-10-27 19:53:05 +07:00
parent 1caaa8404b
commit ece6499e2b
9 changed files with 714 additions and 0 deletions
+25
View File
@@ -42,6 +42,13 @@ use imphnen_hackathon::v1::registrations::{
CheckInResponseDto, RegistrationStatsDto, UserHackathonsResponseDto, UserHackathonDto,
RegistrationStatus, ParticipantRole,
};
use imphnen_hackathon::v1::notifications::{
notification_controller,
notification_dto::{
NotificationDto, NotificationListResponseDto, MarkAsReadResponseDto,
MarkAllAsReadResponseDto, DeleteNotificationResponseDto, UnreadCountResponseDto,
},
};
use imphnen_entities::{PermissionsItemDto, RolesDetailItemDto};
use imphnen_entities::{MessageResponseDto, MetaRequestDto, MetaResponseDto, ResponseListSuccessDto, ResponseSuccessDto};
use imphnen_iam::v1::auth::auth_dto::{AuthLoginRequestDto, AuthLoginResponsetDto, AuthNewPasswordRequestDto, AuthRefreshTokenRequestDto, AuthResendOtpRequestDto, AuthVerifyEmailRequestDto, TokenDto};
@@ -157,6 +164,11 @@ use utoipa::{
registration_controller::put_update_registration_status,
registration_controller::post_check_in_participant,
registration_controller::get_registration_stats,
notification_controller::get_notifications_handler,
notification_controller::mark_as_read_handler,
notification_controller::mark_all_as_read_handler,
notification_controller::delete_notification_handler,
notification_controller::get_unread_count_handler,
),
components(
schemas(
@@ -267,12 +279,23 @@ use utoipa::{
UserHackathonDto,
RegistrationStatus,
ParticipantRole,
NotificationDto,
NotificationListResponseDto,
MarkAsReadResponseDto,
MarkAllAsReadResponseDto,
DeleteNotificationResponseDto,
UnreadCountResponseDto,
ResponseSuccessDto<RegistrationResponseDto>,
ResponseSuccessDto<RegistrationListResponseDto>,
ResponseSuccessDto<UpdateRegistrationStatusResponseDto>,
ResponseSuccessDto<CheckInResponseDto>,
ResponseSuccessDto<RegistrationStatsDto>,
ResponseSuccessDto<UserHackathonsResponseDto>,
ResponseSuccessDto<NotificationListResponseDto>,
ResponseSuccessDto<MarkAsReadResponseDto>,
ResponseSuccessDto<MarkAllAsReadResponseDto>,
ResponseSuccessDto<DeleteNotificationResponseDto>,
ResponseSuccessDto<UnreadCountResponseDto>,
ResponseListSuccessDto<Vec<HackathonDto>>,
ResponseSuccessDto<HackathonDto>,
ResponseListSuccessDto<Vec<HackathonEventDto>>,
@@ -313,6 +336,8 @@ use utoipa::{
(name = "Hackathon Timeline", description = "Hackathon Timeline Management Endpoints"),
(name = "Hackathon Submissions", description = "Hackathon Submission Management Endpoints"),
(name = "registrations", description = "Hackathon Registration Management API"),
(name = "notifications", description = "User Notifications Management API"),
(name = "Teams", description = "Team Management Endpoints"),
)
)]
pub struct ApiDoc;
+3
View File
@@ -1,10 +1,12 @@
use axum::Router;
pub mod hackathon;
pub mod notifications;
pub mod registrations;
// Export the router function from hackathon module
pub use hackathon::hackathon_router;
pub use notifications::notifications_router;
pub use registrations::registrations_router;
// Main route constructor
@@ -17,6 +19,7 @@ pub fn hackathon_protected_routes() -> Router {
.route("/hackathons/submissions/{id}/status", axum::routing::patch(update_submission_status))
.route("/hackathons/{hackathon_id}/admin/results", axum::routing::get(get_admin_hackathon_results))
.merge(registrations_router())
.merge(notifications_router())
}
// Public routes for hackathons (only listing and retrieving)
@@ -0,0 +1,7 @@
pub mod notification_controller;
pub mod notification_dto;
pub mod notification_repository;
pub mod notification_schema;
pub mod notification_service;
pub use notification_controller::notifications_router;
@@ -0,0 +1,175 @@
use super::notification_dto::{
DeleteNotificationResponseDto, MarkAllAsReadResponseDto, MarkAsReadResponseDto,
NotificationDto, NotificationListQueryDto, NotificationListResponseDto,
UnreadCountResponseDto,
};
use super::notification_service::Service;
use axum::{
extract::{Extension, Path, Query},
http::{HeaderMap, Response, StatusCode},
response::IntoResponse,
routing::{delete, get, put},
Router, body::Body,
};
use imphnen_entities::common_dto::ResponseSuccessDto;
use imphnen_libs::AppState;
use imphnen_utils::{extract_email::extract_email, response_format::common_response};
/// Get user's notifications with optional filtering
#[utoipa::path(
get,
path = "/v1/notifications",
tags = ["notifications"],
params(
("page_size" = Option<usize>, Query, description = "Number of notifications per page (1-100, default: 20)"),
("page" = Option<usize>, Query, description = "Page number (min: 1, default: 1)"),
("is_read" = Option<bool>, Query, description = "Filter by read status"),
("notification_type" = Option<String>, Query, description = "Filter by notification type"),
),
responses(
(status = 200, description = "Successfully retrieved notifications", body = NotificationListResponseDto),
(status = 401, description = "Unauthorized - Invalid or missing token"),
),
security(
("bearer" = [])
)
)]
pub async fn get_notifications_handler(
headers: HeaderMap,
Query(query): Query<NotificationListQueryDto>,
Extension(state): Extension<AppState>,
) -> Response<Body> {
match extract_email(&headers) {
Some(email) => {
let service = Service::new(&state);
service.get_notifications(&email, query).await
}
None => common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
}
}
/// Mark a notification as read
#[utoipa::path(
put,
path = "/v1/notifications/{id}/read",
tags = ["notifications"],
params(
("id" = String, Path, description = "Notification ID"),
),
responses(
(status = 200, description = "Successfully marked notification as read", body = MarkAsReadResponseDto),
(status = 400, description = "Notification already marked as read"),
(status = 401, description = "Unauthorized - Invalid or missing token"),
(status = 403, description = "Forbidden - Not the notification owner"),
(status = 404, description = "Notification not found"),
),
security(
("bearer" = [])
)
)]
pub async fn mark_as_read_handler(
headers: HeaderMap,
Path(id): Path<String>,
Extension(state): Extension<AppState>,
) -> Response<Body> {
match extract_email(&headers) {
Some(email) => {
let service = Service::new(&state);
service.mark_as_read(&email, &id).await
}
None => common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
}
}
/// Mark all notifications as read
#[utoipa::path(
put,
path = "/v1/notifications/read-all",
tags = ["notifications"],
responses(
(status = 200, description = "Successfully marked all notifications as read", body = MarkAllAsReadResponseDto),
(status = 401, description = "Unauthorized - Invalid or missing token"),
),
security(
("bearer" = [])
)
)]
pub async fn mark_all_as_read_handler(
headers: HeaderMap,
Extension(state): Extension<AppState>,
) -> Response<Body> {
match extract_email(&headers) {
Some(email) => {
let service = Service::new(&state);
service.mark_all_as_read(&email).await
}
None => common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
}
}
/// Delete a notification
#[utoipa::path(
delete,
path = "/v1/notifications/{id}",
tags = ["notifications"],
params(
("id" = String, Path, description = "Notification ID"),
),
responses(
(status = 200, description = "Successfully deleted notification", body = DeleteNotificationResponseDto),
(status = 401, description = "Unauthorized - Invalid or missing token"),
(status = 403, description = "Forbidden - Not the notification owner"),
(status = 404, description = "Notification not found"),
),
security(
("bearer" = [])
)
)]
pub async fn delete_notification_handler(
headers: HeaderMap,
Path(id): Path<String>,
Extension(state): Extension<AppState>,
) -> Response<Body> {
match extract_email(&headers) {
Some(email) => {
let service = Service::new(&state);
service.delete_notification(&email, &id).await
}
None => common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
}
}
/// Get unread notifications count
#[utoipa::path(
get,
path = "/v1/notifications/unread/count",
tags = ["notifications"],
responses(
(status = 200, description = "Successfully retrieved unread count", body = UnreadCountResponseDto),
(status = 401, description = "Unauthorized - Invalid or missing token"),
),
security(
("bearer" = [])
)
)]
pub async fn get_unread_count_handler(
headers: HeaderMap,
Extension(state): Extension<AppState>,
) -> Response<Body> {
match extract_email(&headers) {
Some(email) => {
let service = Service::new(&state);
service.get_unread_count(&email).await
}
None => common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
}
}
pub fn notifications_router() -> Router {
Router::new()
.route("/notifications", get(get_notifications_handler))
.route("/notifications/:id/read", put(mark_as_read_handler))
.route("/notifications/read-all", put(mark_all_as_read_handler))
.route("/notifications/:id", delete(delete_notification_handler))
.route("/notifications/unread/count", get(get_unread_count_handler))
}
@@ -0,0 +1,72 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use validator::Validate;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct NotificationDto {
pub id: String,
pub notification_type: String,
pub title: String,
pub message: String,
pub is_read: bool,
pub created_at: String,
pub read_at: Option<String>,
pub related_id: Option<String>,
pub action_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct NotificationListResponseDto {
pub notifications: Vec<NotificationDto>,
pub total: usize,
pub unread_count: usize,
pub page: usize,
pub page_size: usize,
}
#[derive(Debug, Clone, Deserialize, Validate, ToSchema)]
pub struct NotificationListQueryDto {
#[validate(range(min = 1, max = 100))]
#[serde(default = "default_page_size")]
pub page_size: usize,
#[validate(range(min = 1))]
#[serde(default = "default_page")]
pub page: usize,
pub is_read: Option<bool>,
pub notification_type: Option<String>,
}
fn default_page_size() -> usize {
20
}
fn default_page() -> usize {
1
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct MarkAsReadResponseDto {
pub id: String,
pub is_read: bool,
pub read_at: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct MarkAllAsReadResponseDto {
pub updated_count: usize,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct DeleteNotificationResponseDto {
pub id: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UnreadCountResponseDto {
pub unread_count: usize,
}
@@ -0,0 +1,148 @@
use crate::v1::notifications::notification_schema::NotificationSchema;
use imphnen_libs::AppState;
use imphnen_utils::{get_id, make_thing};
use surrealdb::sql::Thing;
pub struct Repository<'a> {
state: &'a AppState,
}
impl<'a> Repository<'a> {
pub fn new(state: &'a AppState) -> Self {
Self { state }
}
pub async fn query_user_notifications(
&self,
user_id: &Thing,
is_read: Option<bool>,
notification_type: Option<String>,
page: usize,
page_size: usize,
) -> Result<Vec<NotificationSchema>, String> {
let db = &self.state.surrealdb_ws;
let offset = (page - 1) * page_size;
let mut query = "SELECT * FROM notifications WHERE user_id = $user_id ".to_string();
if let Some(is_read_val) = is_read {
query.push_str(&format!(" AND is_read = {} ", is_read_val));
}
if let Some(ref notif_type) = notification_type {
query.push_str(&format!(" AND notification_type = '{}' ", notif_type));
}
query.push_str(&format!(
" ORDER BY created_at DESC LIMIT {} START {} ",
page_size, offset
));
let user_id_clone = user_id.clone();
let mut result = db
.query(&query)
.bind(("user_id", user_id_clone))
.await
.map_err(|e| format!("Query failed: {}", e))?;
let notifications: Vec<NotificationSchema> = result.take(0).map_err(|e| format!("Failed to parse results: {}", e))?;
Ok(notifications)
}
pub async fn count_user_notifications(
&self,
user_id: &Thing,
is_read: Option<bool>,
notification_type: Option<String>,
) -> Result<usize, String> {
let db = &self.state.surrealdb_ws;
let mut query = "SELECT count() as total FROM notifications WHERE user_id = $user_id ".to_string();
if let Some(is_read_val) = is_read {
query.push_str(&format!(" AND is_read = {} ", is_read_val));
}
if let Some(ref notif_type) = notification_type {
query.push_str(&format!(" AND notification_type = '{}' ", notif_type));
}
query.push_str(" GROUP ALL ");
let user_id_clone = user_id.clone();
let mut result = db
.query(&query)
.bind(("user_id", user_id_clone))
.await
.map_err(|e| format!("Query failed: {}", e))?;
let count: Option<usize> = result.take("total").map_err(|e| format!("Failed to get count: {}", e))?;
Ok(count.unwrap_or(0))
}
pub async fn query_notification_by_id(
&self,
notification_id: &Thing,
) -> Result<NotificationSchema, String> {
let db = &self.state.surrealdb_ws;
let record_key = get_id(notification_id).map_err(|e| e.to_string())?;
let notification: Option<NotificationSchema> = db
.select(record_key)
.await
.map_err(|e| format!("Failed to fetch notification: {}", e))?;
notification.ok_or("Notification not found".to_string())
}
pub async fn update_notification(
&self,
notification_id: &Thing,
notification: NotificationSchema,
) -> Result<NotificationSchema, String> {
let db = &self.state.surrealdb_ws;
let record_key = get_id(notification_id).map_err(|e| e.to_string())?;
let updated: Option<NotificationSchema> = db
.update(record_key)
.content(notification)
.await
.map_err(|e| format!("Failed to update notification: {}", e))?;
updated.ok_or("Failed to update notification".to_string())
}
pub async fn mark_all_as_read(&self, user_id: &Thing) -> Result<usize, String> {
let db = &self.state.surrealdb_ws;
let query = "UPDATE notifications SET is_read = true, read_at = time::now() WHERE user_id = $user_id AND is_read = false";
let user_id_clone = user_id.clone();
let mut result = db
.query(query)
.bind(("user_id", user_id_clone))
.await
.map_err(|e| format!("Query failed: {}", e))?;
let updated: Vec<NotificationSchema> = result.take(0).map_err(|e| format!("Failed to parse results: {}", e))?;
Ok(updated.len())
}
pub async fn delete_notification(&self, notification_id: &Thing) -> Result<(), String> {
let db = &self.state.surrealdb_ws;
let record_key = get_id(notification_id).map_err(|e| e.to_string())?;
let _: Option<NotificationSchema> = db
.delete(record_key)
.await
.map_err(|e| format!("Failed to delete notification: {}", e))?;
Ok(())
}
pub async fn count_unread_notifications(&self, user_id: &Thing) -> Result<usize, String> {
let db = &self.state.surrealdb_ws;
let query = "SELECT count() as total FROM notifications WHERE user_id = $user_id AND is_read = false GROUP ALL";
let user_id_clone = user_id.clone();
let mut result = db
.query(query)
.bind(("user_id", user_id_clone))
.await
.map_err(|e| format!("Query failed: {}", e))?;
let count: Option<usize> = result.take("total").map_err(|e| format!("Failed to get count: {}", e))?;
Ok(count.unwrap_or(0))
}
}
@@ -0,0 +1,47 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NotificationType {
#[serde(rename = "registration_approved")]
RegistrationApproved,
#[serde(rename = "registration_rejected")]
RegistrationRejected,
#[serde(rename = "registration_waitlisted")]
RegistrationWaitlisted,
#[serde(rename = "hackathon_reminder")]
HackathonReminder,
#[serde(rename = "team_invite")]
TeamInvite,
#[serde(rename = "team_update")]
TeamUpdate,
#[serde(rename = "hackathon_update")]
HackathonUpdate,
#[serde(rename = "check_in_reminder")]
CheckInReminder,
#[serde(rename = "announcement")]
Announcement,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationSchema {
pub id: Thing,
pub user_id: Thing,
pub notification_type: NotificationType,
pub title: String,
pub message: String,
pub is_read: bool,
pub created_at: DateTime<Utc>,
pub read_at: Option<DateTime<Utc>>,
pub related_id: Option<Thing>, // Could be hackathon_id, registration_id, team_id, etc.
pub action_url: Option<String>,
pub metadata: Option<serde_json::Value>, // For additional flexible data
}
impl NotificationSchema {
pub fn mark_as_read(&mut self) {
self.is_read = true;
self.read_at = Some(Utc::now());
}
}
@@ -0,0 +1,233 @@
use super::notification_dto::{
DeleteNotificationResponseDto, MarkAllAsReadResponseDto, MarkAsReadResponseDto,
NotificationDto, NotificationListQueryDto, NotificationListResponseDto,
UnreadCountResponseDto,
};
use super::notification_repository::Repository;
use super::notification_schema::NotificationSchema;
use axum::http::{Response, StatusCode};
use axum::response::IntoResponse;
use axum::body::Body;
use imphnen_entities::common_dto::ResponseSuccessDto;
use imphnen_libs::AppState;
use imphnen_utils::{
extract_id, make_thing, response_format::success_response, validator::validate_request,
};
pub struct Service<'a> {
state: &'a AppState,
}
impl<'a> Service<'a> {
pub fn new(state: &'a AppState) -> Self {
Self { state }
}
pub async fn get_notifications(
&self,
user_email: &str,
query: NotificationListQueryDto,
) -> Response<Body> {
if let Err((status, message)) = validate_request(&query) {
return (status, message).into_response();
}
let user_id = make_thing("users", user_email);
let repository = Repository::new(self.state);
let notifications_result = repository
.query_user_notifications(
&user_id,
query.is_read,
query.notification_type.clone(),
query.page,
query.page_size,
)
.await;
let notifications = match notifications_result {
Ok(notifs) => notifs,
Err(err) => {
return (StatusCode::INTERNAL_SERVER_ERROR, err).into_response();
}
};
let total_result = repository
.count_user_notifications(&user_id, query.is_read, query.notification_type)
.await;
let total = match total_result {
Ok(count) => count,
Err(err) => {
return (StatusCode::INTERNAL_SERVER_ERROR, err).into_response();
}
};
let unread_count_result = repository.count_unread_notifications(&user_id).await;
let unread_count = match unread_count_result {
Ok(count) => count,
Err(err) => {
return (StatusCode::INTERNAL_SERVER_ERROR, err).into_response();
}
};
let notification_dtos: Vec<NotificationDto> = notifications
.into_iter()
.map(|n| NotificationDto {
id: extract_id(&n.id),
notification_type: format!("{:?}", n.notification_type),
title: n.title,
message: n.message,
is_read: n.is_read,
created_at: n.created_at.to_rfc3339(),
read_at: n.read_at.map(|dt| dt.to_rfc3339()),
related_id: n.related_id.map(|id| extract_id(&id)),
action_url: n.action_url,
})
.collect();
let response = NotificationListResponseDto {
notifications: notification_dtos,
total,
unread_count,
page: query.page,
page_size: query.page_size,
};
success_response(ResponseSuccessDto { data: response })
}
pub async fn mark_as_read(
&self,
user_email: &str,
notification_id: &str,
) -> Response<Body> {
let user_id = make_thing("users", user_email);
let notif_id = make_thing("notifications", notification_id);
let repository = Repository::new(self.state);
let notification_result = repository.query_notification_by_id(&notif_id).await;
let mut notification = match notification_result {
Ok(notif) => notif,
Err(_) => {
return (
StatusCode::NOT_FOUND,
"Notification not found".to_string(),
)
.into_response();
}
};
// Verify ownership
if notification.user_id != user_id {
return (
StatusCode::FORBIDDEN,
"You don't have permission to access this notification".to_string(),
)
.into_response();
}
if notification.is_read {
return (
StatusCode::BAD_REQUEST,
"Notification is already marked as read".to_string(),
)
.into_response();
}
notification.mark_as_read();
match repository.update_notification(&notif_id, notification.clone()).await {
Ok(updated) => {
let response = MarkAsReadResponseDto {
id: extract_id(&updated.id),
is_read: updated.is_read,
read_at: updated.read_at.unwrap().to_rfc3339(),
message: "Notification marked as read".to_string(),
};
success_response(ResponseSuccessDto { data: response })
}
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response(),
}
}
pub async fn mark_all_as_read(&self, user_email: &str) -> Response<Body> {
let user_id = make_thing("users", user_email);
let repository = Repository::new(self.state);
match repository.mark_all_as_read(&user_id).await {
Ok(count) => {
let response = MarkAllAsReadResponseDto {
updated_count: count,
message: format!("{} notification(s) marked as read", count),
};
success_response(ResponseSuccessDto { data: response })
}
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response(),
}
}
pub async fn delete_notification(
&self,
user_email: &str,
notification_id: &str,
) -> Response<Body> {
let user_id = make_thing("users", user_email);
let notif_id = make_thing("notifications", notification_id);
let repository = Repository::new(self.state);
let notification_result = repository.query_notification_by_id(&notif_id).await;
let notification = match notification_result {
Ok(notif) => notif,
Err(_) => {
return (
StatusCode::NOT_FOUND,
"Notification not found".to_string(),
)
.into_response();
}
};
// Verify ownership
if notification.user_id != user_id {
return (
StatusCode::FORBIDDEN,
"You don't have permission to delete this notification".to_string(),
)
.into_response();
}
match repository.delete_notification(&notif_id).await {
Ok(_) => {
let response = DeleteNotificationResponseDto {
id: notification_id.to_string(),
message: "Notification deleted successfully".to_string(),
};
success_response(ResponseSuccessDto { data: response })
}
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response(),
}
}
pub async fn get_unread_count(&self, user_email: &str) -> Response<Body> {
let user_id = make_thing("users", user_email);
let repository = Repository::new(self.state);
match repository.count_unread_notifications(&user_id).await {
Ok(count) => {
let response = UnreadCountResponseDto {
unread_count: count,
};
success_response(ResponseSuccessDto { data: response })
}
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response(),
}
}
}
+4
View File
@@ -53,6 +53,8 @@ pub enum ResourceEnum {
HackathonSubmissions,
/// Hackathon registrations table for participant registrations
HackathonRegistrations,
/// Notifications table for user notifications
Notifications,
/// Rate limiting table for IP-based rate limiting
RateLimit,
/// Audit log table for admin action tracking
@@ -85,6 +87,7 @@ impl fmt::Display for ResourceEnum {
ResourceEnum::HackathonTimeline => "app_hackathon_timeline",
ResourceEnum::HackathonSubmissions => "app_hackathon_submissions",
ResourceEnum::HackathonRegistrations => "hackathon_registrations",
ResourceEnum::Notifications => "notifications",
ResourceEnum::RateLimit => "app_rate_limit",
ResourceEnum::AuditLog => "app_audit_log",
ResourceEnum::Sessions => "app_sessions",
@@ -129,6 +132,7 @@ impl ResourceEnum {
ResourceEnum::HackathonTimeline => "app_hackathon_timeline",
ResourceEnum::HackathonSubmissions => "app_hackathon_submissions",
ResourceEnum::HackathonRegistrations => "hackathon_registrations",
ResourceEnum::Notifications => "notifications",
ResourceEnum::RateLimit => "app_rate_limit",
ResourceEnum::AuditLog => "app_audit_log",
ResourceEnum::Sessions => "app_sessions",