Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ jobs:
override: true
- uses: Swatinem/rust-cache@v2
- run: cargo build --release
# - run: cargo test
# - run: cargo test
12 changes: 7 additions & 5 deletions CONTRIBUTING.MD
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ We welcome and appreciate contributions from the community! Here’s how to get
## ⚠️ Avoid Generic Comments

Comments such as:

- 🚫 "Can I help with this?"
- 🚫 "I’d love to contribute!"
- 🚫 "Check out my profile!"
Expand All @@ -27,6 +28,7 @@ Comments such as:
...will not be considered.

Instead, please provide:

- A brief **introduction** about yourself
- A concise **plan (3–6 lines max)** outlining how you’ll solve the issue
- Your **estimated completion time (ETA)**
Expand All @@ -53,11 +55,11 @@ Please review our [Code of Conduct](CODE_OF_CONDUCT.md) to help us create a welc

## 🧵 Branch Naming Convention

| Type | Prefix | Example |
|----------|------------------|---------------------------|
| Feature | `feature/` | `feature/add-profile-tab`|
| Bugfix | `fix/` | `fix/login-validation` |
| Docs | `docs/` | `docs/update-contributing`|
| Type | Prefix | Example |
| ------- | ---------- | -------------------------- |
| Feature | `feature/` | `feature/add-profile-tab` |
| Bugfix | `fix/` | `fix/login-validation` |
| Docs | `docs/` | `docs/update-contributing` |

## ✅ Opening Issues

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Welcome to **ByteChain Academy**, a lightweight Web3 learning platform that offe
Follow these instructions to set up the project locally for development and testing purposes.

### 🔹 Backend Setup

The backend is built with [NestJS](https://nestjs.com/):

1. **Clone the Repository:**
Expand All @@ -39,6 +40,7 @@ The backend is built with [NestJS](https://nestjs.com/):
```

### 🔹 Frontend Setup

The frontend uses [Next.js](https://nextjs.org/):

1. **Navigate to the Frontend Directory:**
Expand Down Expand Up @@ -79,6 +81,7 @@ This project is licensed under the **MIT License**. See [LICENSE](LICENSE) for d
## 📧 Contact

For inquiries, discussions, or help, feel free to reach out to us:

- 📬 Email: [contact@nexacore.org](mailto:contact@nexacore.org)
- 🗣️ Telegram: [https://t.me/ByteChainAcademy](https://t.me/+E_iHswAzaPA4Yzk8)
- 🐛 Issues: Open an issue for feature requests or bug reports
Expand Down
17 changes: 9 additions & 8 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common';
import {
Injectable,
UnauthorizedException,
BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { RefreshToken } from './entities/refresh-token.entity';
Expand Down Expand Up @@ -117,7 +121,8 @@ export class AuthService {
default:
return 7 * 24 * 60 * 60 * 1000;
}
} async requestEmailVerification(email: string): Promise<void> {
}
async requestEmailVerification(email: string): Promise<void> {
const user = await this.adminRepo.findOne({ where: { email } });
if (!user) {
throw new BadRequestException('User not found');
Expand All @@ -126,7 +131,7 @@ export class AuthService {
// Invalidate any existing verification tokens
await this.emailVerificationRepo.update(
{ email, verified: false },
{ verified: true }
{ verified: true },
);

const token = randomBytes(32).toString('hex');
Expand Down Expand Up @@ -174,10 +179,7 @@ export class AuthService {
}

// Invalidate any existing reset tokens
await this.passwordResetRepo.update(
{ email, used: false },
{ used: true }
);
await this.passwordResetRepo.update({ email, used: false }, { used: true });

const token = randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
Expand Down Expand Up @@ -219,4 +221,3 @@ export class AuthService {
await this.passwordResetRepo.save(reset);
}
}

2 changes: 1 addition & 1 deletion backend/src/auth/dto/email-verification.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ export class RequestEmailVerificationDto {
@IsEmail()
@IsNotEmpty()
email: string;
}
}
2 changes: 1 addition & 1 deletion backend/src/auth/dto/password-reset.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export class ResetPasswordDto {
@IsNotEmpty()
@MinLength(8)
newPassword: string;
}
}
10 changes: 8 additions & 2 deletions backend/src/auth/entities/password-reset.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn } from 'typeorm';
import {
Entity,
Column,
PrimaryGeneratedColumn,
ManyToOne,
CreateDateColumn,
} from 'typeorm';
import { Admin } from '../../admin/entities/admin.entity';

@Entity()
Expand All @@ -23,4 +29,4 @@ export class PasswordReset {

@ManyToOne(() => Admin)
user: Admin;
}
}
6 changes: 3 additions & 3 deletions backend/src/auth/services/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class EmailService {

async sendVerificationEmail(email: string, token: string): Promise<void> {
const verificationUrl = `${process.env.FRONTEND_URL}/verify-email?token=${token}`;

await this.transporter.sendMail({
from: process.env.SMTP_FROM,
to: email,
Expand All @@ -35,7 +35,7 @@ export class EmailService {

async sendPasswordResetEmail(email: string, token: string): Promise<void> {
const resetUrl = `${process.env.FRONTEND_URL}/reset-password?token=${token}`;

await this.transporter.sendMail({
from: process.env.SMTP_FROM,
to: email,
Expand All @@ -49,4 +49,4 @@ export class EmailService {
`,
});
}
}
}
110 changes: 110 additions & 0 deletions backend/src/notification/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Notification Module

A comprehensive notification system for handling real-time and persistent notifications across different user roles (Student, Tutor, Admin).

## Features

- **Multi-role Support**: Handles notifications for Students, Tutors, and Admins
- **Type-based Notifications**: Predefined notification types for different platform events
- **Real-time Ready**: Architecture supports future WebSocket/SSE integration
- **Bulk Operations**: Send notifications to multiple recipients
- **Filtering & Pagination**: Advanced querying capabilities
- **Role-based Access Control**: Proper authentication and authorization
- **Extensible**: Easy to add new notification types and features

## API Endpoints

### User Endpoints (Authenticated)

- `GET /notification` - Get current user's notifications
- `GET /notification/unread-count` - Get unread notification count
- `PATCH /notification/:id/read` - Mark notification as read
- `PATCH /notification/mark-all-read` - Mark all notifications as read
- `DELETE /notification/:id` - Delete a notification

### Admin/Tutor Endpoints

- `POST /notification/send` - Send a notification (Admin/Tutor only)
- `POST /notification/send/bulk` - Send bulk notifications (Admin only)
- `GET /notification/admin/all` - Get all notifications (Admin only)
- `DELETE /notification/admin/:id` - Delete any notification (Admin only)

## Notification Types

- `COURSE_ENROLLMENT` - Course enrollment notifications
- `COURSE_COMPLETION` - Course completion notifications
- `NEW_LESSON` - New lesson notifications
- `LESSON_COMPLETION` - Lesson completion notifications
- `QUIZ_RESULT` - Quiz result notifications
- `QUIZ_REMINDER` - Quiz reminder notifications
- `DAO_UPDATE` - DAO update notifications
- `DAO_PROPOSAL` - DAO proposal notifications
- `DAO_VOTING` - DAO voting notifications
- `SYSTEM_ANNOUNCEMENT` - System announcements
- `MAINTENANCE` - Maintenance notifications
- `PROFILE_UPDATE` - Profile update notifications
- `PASSWORD_CHANGE` - Password change notifications

## Usage Examples

### Triggering Notifications from Other Services

\`\`\`typescript
// Inject NotificationService in your service
constructor(private notificationService: NotificationService) {}

// Trigger a quiz result notification
await this.notificationService.triggerNotification(
studentId,
UserRole.STUDENT,
NotificationType.QUIZ_RESULT,
`You scored 85% on the JavaScript Basics quiz!`,
{ quizName: 'JavaScript Basics', score: 85, passed: true }
);
\`\`\`

### Using Notification Hooks

\`\`\`typescript
// Inject NotificationHooks in your service
constructor(private notificationHooks: NotificationHooks) {}

// Trigger course enrollment notification
await this.notificationHooks.onCourseEnrollment(studentId, courseName);

// Trigger quiz result notification
await this.notificationHooks.onQuizResult(studentId, quizName, score, passed);
\`\`\`

## Database Schema

The notification entity includes:

- `id` - Unique identifier
- `recipientId` - ID of the notification recipient
- `recipientRole` - Role of the recipient (STUDENT, TUTOR, ADMIN)
- `type` - Type of notification
- `message` - Notification message
- `isRead` - Read status
- `metadata` - Additional data (JSON)
- `senderId` - ID of the sender (optional)
- `senderRole` - Role of the sender (optional)
- `createdAt` - Creation timestamp
- `updatedAt` - Update timestamp

## Testing

Run the tests:

\`\`\`bash
npm run test src/modules/notification
\`\`\`

## Future Enhancements

- WebSocket integration for real-time notifications
- Email notification service integration
- Push notification support
- Notification templates
- Scheduled notifications
- Notification preferences per user
35 changes: 35 additions & 0 deletions backend/src/notification/dto/create-notification.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
IsString,
IsUUID,
IsEnum,
IsOptional,
IsObject,
} from 'class-validator';
import { NotificationType } from '../enums/notification.enums';
import { UserRole } from 'src/roles/roles.enum';

export class CreateNotificationDto {
@IsUUID()
recipientId: string;

@IsEnum(UserRole)
recipientRole: UserRole;

@IsEnum(NotificationType)
type: NotificationType;

@IsString()
message: string;

@IsOptional()
@IsObject()
metadata?: Record<string, any>;

@IsOptional()
@IsUUID()
senderId?: string;

@IsOptional()
@IsEnum(UserRole)
senderRole?: UserRole;
}
30 changes: 30 additions & 0 deletions backend/src/notification/dto/query-notification.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { IsOptional, IsEnum, IsBoolean, IsDateString } from 'class-validator';
import { Transform } from 'class-transformer';
import { NotificationType } from '../enums/notification.enums';

export class QueryNotificationDto {
@IsOptional()
@IsEnum(NotificationType)
type?: NotificationType;

@IsOptional()
@Transform(({ value }) => value === 'true')
@IsBoolean()
isRead?: boolean;

@IsOptional()
@IsDateString()
fromDate?: string;

@IsOptional()
@IsDateString()
toDate?: string;

@IsOptional()
@Transform(({ value }) => Number.parseInt(String(value)))
page?: number = 1;

@IsOptional()
@Transform(({ value }) => Number.parseInt(String(value)))
limit?: number = 20;
}
46 changes: 46 additions & 0 deletions backend/src/notification/dto/send-notification.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
IsString,
IsUUID,
IsEnum,
IsOptional,
IsObject,
IsArray,
} from 'class-validator';
import { NotificationType, UserRole } from '../enums/notification.enums';

export class SendNotificationDto {
@IsUUID()
recipientId: string;

@IsEnum(UserRole)
recipientRole: UserRole;

@IsEnum(NotificationType)
type: NotificationType;

@IsString()
message: string;

@IsOptional()
@IsObject()
metadata?: Record<string, any>;
}

export class BulkSendNotificationDto {
@IsArray()
@IsUUID(undefined, { each: true })
recipientIds: string[];

@IsEnum(UserRole)
recipientRole: UserRole;

@IsEnum(NotificationType)
type: NotificationType;

@IsString()
message: string;

@IsOptional()
@IsObject()
metadata?: Record<string, any>;
}
Loading
Loading