├── Procfile ├── .prettierrc ├── nest-cli.json ├── src ├── shared │ ├── modules │ │ ├── chat │ │ │ ├── dto │ │ │ │ └── room.dto.ts │ │ │ ├── entities │ │ │ │ ├── room.entity.ts │ │ │ │ ├── user-joined-room.entity.ts │ │ │ │ └── message.entity.ts │ │ │ ├── chat.module.ts │ │ │ ├── room.controller.ts │ │ │ ├── chat.service.ts │ │ │ └── chat.gateway.ts │ │ └── aws │ │ │ ├── aws.module.ts │ │ │ └── aws.service.ts │ └── dto │ │ └── create-album.dto.ts ├── modules │ ├── playlist │ │ ├── dto │ │ │ └── playlist.dto.ts │ │ ├── playlist.repository.ts │ │ ├── playlist.entity.ts │ │ ├── playlist.module.ts │ │ ├── playlist.controller.ts │ │ └── playlist.service.ts │ ├── notification │ │ ├── classes │ │ │ ├── key.ts │ │ │ ├── content.ts │ │ │ ├── notification-data.ts │ │ │ ├── notification-payload.ts │ │ │ └── notification.ts │ │ ├── notification-payload.dto.ts │ │ ├── entities │ │ │ ├── notification.entity.ts │ │ │ ├── subscriber.entity.ts │ │ │ └── subscribers-notifications.entity.ts │ │ ├── notification.module.ts │ │ ├── notification.controller.ts │ │ └── notification.service.ts │ ├── auth │ │ ├── dto │ │ │ ├── email-login.dto.ts │ │ │ ├── auth-credentials.dto.ts │ │ │ ├── reset-password.dto.ts │ │ │ └── create-profile.dto.ts │ │ ├── entities │ │ │ ├── email-verification.entity.ts │ │ │ ├── forgotten-password.entity.ts │ │ │ └── user.entity.ts │ │ ├── stratigies │ │ │ ├── jwt-strategy.ts │ │ │ ├── google.strategy.ts │ │ │ └── facebook.strategy.ts │ │ ├── auth.module.ts │ │ ├── repositories │ │ │ └── user.repository.ts │ │ ├── auth.controller.ts │ │ └── auth.service.ts │ ├── musician │ │ ├── musician.entity.ts │ │ ├── musician.module.ts │ │ ├── musician.repository.ts │ │ ├── musician.controller.ts │ │ └── musician.service.ts │ ├── singer │ │ ├── singer.entity.ts │ │ ├── singer.module.ts │ │ ├── singer.repository.ts │ │ ├── singer.controller.ts │ │ └── singer.service.ts │ ├── track │ │ ├── track.module.ts │ │ ├── track.controller.ts │ │ ├── track.entity.ts │ │ └── track.service.ts │ ├── favorite │ │ ├── favorite.entity.ts │ │ ├── favorite.module.ts │ │ ├── favorite.controller.ts │ │ └── favorite.service.ts │ ├── singer-album │ │ ├── singer-album.entity.ts │ │ ├── singer-album.module.ts │ │ ├── singer-album.controller.ts │ │ └── singer-album.service.ts │ ├── musician-album │ │ ├── musician-album.entity.ts │ │ ├── musician-album.module.ts │ │ ├── musician-album.controller.ts │ │ └── musician-album.service.ts │ ├── profile │ │ ├── profile.module.ts │ │ ├── profile.entity.ts │ │ ├── profile.controller.ts │ │ └── profile.service.ts │ ├── music │ │ ├── music.entity.ts │ │ ├── music.module.ts │ │ ├── music.repository.ts │ │ ├── music.controller.ts │ │ └── music.service.ts │ └── song │ │ ├── song.module.ts │ │ ├── song.entity.ts │ │ ├── song.repository.ts │ │ ├── song.controller.ts │ │ └── song.service.ts ├── commons │ ├── enums │ │ ├── role.enum.ts │ │ ├── gender.enum.ts │ │ ├── provider.enum.ts │ │ ├── artist-type.enum.ts │ │ ├── song-language.enum.ts │ │ ├── song-type.enum.ts │ │ └── music-type.enum.ts │ ├── interfaces │ │ └── jwt-payload.interface.ts │ ├── classes │ │ ├── auth.ts │ │ ├── abstract-album.ts │ │ ├── abstract-music.ts │ │ └── abstract-artist.ts │ ├── constants │ │ └── auth-constants.ts │ ├── decorators │ │ ├── roles.decorator.ts │ │ └── get-authenticated-user.decorator.ts │ ├── helpers │ │ └── handling-files.helper.ts │ └── guards │ │ ├── admin-auth.guard.ts │ │ ├── accepted-auth.guard.ts │ │ └── user-auth.guard.ts ├── aws-desc.txt ├── app.controller.ts ├── exampleOfDI.txt ├── main.ts ├── config.ts ├── app.module.ts └── routes_endpoint.txt ├── files └── charlie puth-287d.jpg ├── tsconfig.build.json ├── test ├── jest-e2e.json └── app.e2e-spec.ts ├── tsconfig.json ├── .gitignore ├── .eslintrc.js ├── tslint.json ├── README.md └── package.json /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start:prod -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/modules/chat/dto/room.dto.ts: -------------------------------------------------------------------------------- 1 | export class RoomDto { 2 | name: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/modules/playlist/dto/playlist.dto.ts: -------------------------------------------------------------------------------- 1 | export class PlaylistDto { 2 | name: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/commons/enums/role.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Role { 2 | ADMIN ='ADMIN', 3 | USER = 'USER' 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/dto/create-album.dto.ts: -------------------------------------------------------------------------------- 1 | 2 | export class CreateAlbumDto { 3 | name: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/commons/enums/gender.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Gender { 2 | MALE ='MALE', 3 | FEMALE ='FEMALE' 4 | } 5 | -------------------------------------------------------------------------------- /src/commons/interfaces/jwt-payload.interface.ts: -------------------------------------------------------------------------------- 1 | export interface JwtPayload { 2 | email: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/modules/notification/classes/key.ts: -------------------------------------------------------------------------------- 1 | export class Key { 2 | p256dh: string; 3 | auth: string; 4 | } 5 | -------------------------------------------------------------------------------- /files/charlie puth-287d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohammadqaderi/music-land-api/HEAD/files/charlie puth-287d.jpg -------------------------------------------------------------------------------- /src/commons/enums/provider.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Provider { 2 | GOOGLE = 'GOOGLE', 3 | FACEBOOK = 'FACEBOOK' 4 | } -------------------------------------------------------------------------------- /src/modules/auth/dto/email-login.dto.ts: -------------------------------------------------------------------------------- 1 | export class EmailLoginDto { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/notification/classes/content.ts: -------------------------------------------------------------------------------- 1 | export class Content { 2 | action: string; 3 | title: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/commons/enums/artist-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum ArtistType { 2 | SINGLE ='SINGLE', 3 | MUSIC_BAND = 'MUSIC_BAND' 4 | } 5 | -------------------------------------------------------------------------------- /src/commons/classes/auth.ts: -------------------------------------------------------------------------------- 1 | export class Auth { 2 | validEmail: boolean; 3 | gmailId: string; 4 | facebookId: string; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/notification/notification-payload.dto.ts: -------------------------------------------------------------------------------- 1 | export class NotificationPayloadDto { 2 | title: string; 3 | body: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/notification/classes/notification-data.ts: -------------------------------------------------------------------------------- 1 | export class NotificationData { 2 | dateOfArrival: Date; 3 | primaryKey: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/commons/constants/auth-constants.ts: -------------------------------------------------------------------------------- 1 | export const AuthConstants = { 2 | secretKey: 'secretKey', 3 | strategies: ['jwt'], 4 | expiresIn: '10hr' 5 | }; 6 | -------------------------------------------------------------------------------- /src/modules/auth/dto/auth-credentials.dto.ts: -------------------------------------------------------------------------------- 1 | 2 | export class AuthCredentialsDto { 3 | username: string; 4 | 5 | email: string; 6 | 7 | password: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/notification/classes/notification-payload.ts: -------------------------------------------------------------------------------- 1 | import {Notification} from './notification'; 2 | 3 | export class NotificationPayload { 4 | notification: Notification 5 | } 6 | -------------------------------------------------------------------------------- /src/aws-desc.txt: -------------------------------------------------------------------------------- 1 | aws: { 2 | AWS_S3_BUCKET_NAME: 'AWS_S3_BUCKET_NAME', 3 | Access_Key_ID: 'Access_Key_ID', 4 | Secret_Access_Key: 'Secret_Access_Key', 5 | cdnUrl: 'cdnUrl', 6 | }, 7 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | @Controller() 4 | export class AppController { 5 | @Get() 6 | someTest() { 7 | return 'Api is working successfully'; 8 | } 9 | } -------------------------------------------------------------------------------- /src/commons/decorators/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Role } from '../enums/role.enum'; 2 | import { SetMetadata } from '@nestjs/common'; 3 | 4 | 5 | export const Roles = (roles: Role[]) => SetMetadata('roles', roles); 6 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/commons/enums/song-language.enum.ts: -------------------------------------------------------------------------------- 1 | export enum SongLanguage { 2 | ENGLISH = 'ENGLISH', 3 | ARABIC = 'ARABIC', 4 | FRANCE = 'FRANCE', 5 | TURKISH = 'TURKISH', 6 | SPANISH = 'SPANISH', 7 | ITALIC = 'ITALIC', 8 | LATINA = 'LATINA' 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/auth/dto/reset-password.dto.ts: -------------------------------------------------------------------------------- 1 | export class ResetPasswordDto { 2 | readonly email: string; 3 | readonly newPassword: string; 4 | readonly newPasswordToken: string; 5 | readonly currentPassword: string; 6 | readonly confirmPassword: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/shared/modules/aws/aws.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AwsService } from './aws.service'; 3 | 4 | 5 | @Module({ 6 | providers: [AwsService], 7 | exports: [AwsService] 8 | }) 9 | export class AwsModule { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/commons/enums/song-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum SongType { 2 | CLASSICAL = 'CLASSICAL', 3 | POP = 'POP', 4 | ROCK = 'ROCK', 5 | METAL = 'METAL', 6 | COUNTRY = 'COUNTRY', 7 | HIP_HOP = 'HIP_HOP', 8 | BALLADS = 'BALLADS', 9 | DANCE = 'DANCE', 10 | LOVE = 'LOVE', 11 | GOSPEL = 'GOSPEL' 12 | } 13 | -------------------------------------------------------------------------------- /src/commons/classes/abstract-album.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | export abstract class AbstractAlbum extends BaseEntity{ 4 | @PrimaryGeneratedColumn() 5 | id: number; 6 | 7 | @Column() 8 | name: string; 9 | 10 | @Column() 11 | image: string; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/notification/classes/notification.ts: -------------------------------------------------------------------------------- 1 | import { NotificationData } from './notification-data'; 2 | import { Content } from './content'; 3 | 4 | export class Notification { 5 | title: string; 6 | body: string; 7 | icon: string; 8 | vibrate: Array; 9 | data: NotificationData; 10 | actions: Content[]; 11 | } 12 | -------------------------------------------------------------------------------- /src/commons/decorators/get-authenticated-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | 3 | export const GetAuthenticatedUser = createParamDecorator( 4 | (data: any, context: ExecutionContext) => { 5 | const request = context.switchToHttp().getRequest(); 6 | return request.user; 7 | } 8 | ); 9 | -------------------------------------------------------------------------------- /src/modules/auth/dto/create-profile.dto.ts: -------------------------------------------------------------------------------- 1 | import { Gender } from '../../../commons/enums/gender.enum'; 2 | 3 | export class CreateProfileDto { 4 | 5 | firstName: string; 6 | 7 | lastName: string; 8 | 9 | age: number; 10 | 11 | phone: string; 12 | 13 | gender: Gender; 14 | 15 | country: string; 16 | 17 | city: string; 18 | 19 | address: string; 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es2017", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "incremental": true 13 | }, 14 | "exclude": ["node_modules", "dist"] 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/auth/entities/email-verification.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; 2 | 3 | @Entity('verified-emails') 4 | @Unique(['email', 'emailToken']) 5 | export class EmailVerification extends BaseEntity{ 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | 9 | @Column() 10 | email: string; 11 | 12 | @Column() 13 | emailToken: string; 14 | 15 | @Column() 16 | timestamp: Date; 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/auth/entities/forgotten-password.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; 2 | 3 | @Entity('forgotten-passwords') 4 | @Unique(['email', 'newPasswordToken']) 5 | export class ForgottenPassword extends BaseEntity{ 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | 9 | @Column() 10 | email: string; 11 | 12 | @Column() 13 | newPasswordToken: string; 14 | 15 | @Column() 16 | timestamp: Date; 17 | } 18 | -------------------------------------------------------------------------------- /src/commons/enums/music-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum MusicType { 2 | PIANO = 'PIANO', 3 | KEYBOARD = 'KEYBOARD', 4 | RECORDER = 'RECORDER', 5 | CLASSICAL_GUITAR = 'CLASSICAL_GUITAR', 6 | DRUM = 'DRUM', 7 | ELECTRIC_GUITAR = 'ELECTRIC_GUITAR', 8 | VIOLIN = 'VIOLIN', 9 | BASS_GUITAR = 'BASS_GUITAR', 10 | SAXOPHONE = 'SAXOPHONE', 11 | FLUTE = 'FLUTE', 12 | CELLO = 'CELLO', 13 | CLARINET = 'CLARINET', 14 | TRUMPET = 'TRUMPET', 15 | HARP = 'HARP', 16 | MIX = 'MIX' 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/musician/musician.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, OneToMany, Unique } from 'typeorm'; 2 | import { AbstractArtist } from '../../commons/classes/abstract-artist'; 3 | import { MusicianAlbum } from '../musician-album/musician-album.entity'; 4 | 5 | @Entity('musicians') 6 | export class Musician extends AbstractArtist{ 7 | @OneToMany(type => MusicianAlbum, 8 | musicianAlbum => musicianAlbum.musician, { 9 | eager: true 10 | }) 11 | musicianAlbums: MusicianAlbum[]; 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/singer/singer.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, OneToMany, Unique } from 'typeorm'; 2 | import { AbstractArtist } from '../../commons/classes/abstract-artist'; 3 | import { SingerAlbum } from '../singer-album/singer-album.entity'; 4 | 5 | @Entity('singers') 6 | @Unique(['name']) 7 | export class Singer extends AbstractArtist { 8 | @OneToMany(type => SingerAlbum, 9 | singerAlbum => singerAlbum.singer, { 10 | eager: true, 11 | }) 12 | singerAlbums: SingerAlbum[]; 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/track/track.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Track } from './track.entity'; 4 | import { TrackService } from './track.service'; 5 | import { TrackController } from './track.controller'; 6 | 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([Track])], 10 | controllers: [TrackController], 11 | providers: [TrackService], 12 | exports: [TrackService] 13 | }) 14 | export class TrackModule { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | .vercel -------------------------------------------------------------------------------- /src/modules/track/track.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Delete, Get, Param, Post, Put, UseGuards } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Track } from './track.entity'; 4 | import { Repository } from 'typeorm'; 5 | 6 | 7 | @Controller('tracks') 8 | export class TrackController { 9 | 10 | constructor(@InjectRepository(Track) private trackRepository: Repository) { 11 | } 12 | 13 | @Get() 14 | async getTracks() { 15 | return await this.trackRepository.find(); 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/favorite/favorite.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Entity, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Profile } from '../profile/profile.entity'; 3 | import { Track } from '../track/track.entity'; 4 | 5 | @Entity('favorite-lists') 6 | export class Favorite extends BaseEntity{ 7 | @PrimaryGeneratedColumn() 8 | id: number; 9 | 10 | @OneToOne(type => Profile, profile => profile.favorite) 11 | profile: Profile; 12 | 13 | 14 | @OneToMany(type => Track, track => track.favorite, { 15 | eager: true 16 | }) 17 | tracks: Track[]; 18 | } 19 | -------------------------------------------------------------------------------- /src/commons/classes/abstract-music.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | export abstract class AbstractMusic extends BaseEntity{ 4 | @PrimaryGeneratedColumn() 5 | id: number; 6 | 7 | @Column() 8 | name: string; 9 | 10 | @Column() 11 | description: string; 12 | 13 | @Column() 14 | artist: string; 15 | 16 | @Column({ 17 | default: 0 18 | }) 19 | rate: number; 20 | 21 | @Column() 22 | source: string; 23 | 24 | @Column({ 25 | default: new Date() 26 | }) 27 | publishedIn: Date; 28 | 29 | @Column() 30 | tempImage: string; 31 | } 32 | -------------------------------------------------------------------------------- /src/commons/helpers/handling-files.helper.ts: -------------------------------------------------------------------------------- 1 | import {extname} from 'path'; 2 | export const fileFilter = (req,file, callback) => { 3 | if(!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)){ 4 | return callback(new Error('only image file are allowed')) 5 | } 6 | callback(null, true); 7 | }; 8 | 9 | export const editFile = (req, file, callback) => { 10 | const name = file.originalname.split('.')[0]; 11 | const fileExtName = extname(file.originalname); 12 | const randomName = Array(4) 13 | .fill(null) 14 | .map(() => Math.round(Math.random() * 16).toString(16)) 15 | .join(''); 16 | callback(null, `${name}-${randomName}${fileExtName}`); 17 | }; 18 | -------------------------------------------------------------------------------- /src/modules/playlist/playlist.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Playlist } from './playlist.entity'; 3 | 4 | 5 | @EntityRepository(Playlist) 6 | export class PlaylistRepository extends Repository { 7 | 8 | async getUserPlaylists(userId: number): Promise { 9 | const query = this.createQueryBuilder('playlist').select(); 10 | if (userId) { 11 | query.where('playlist.userId = :userId', { userId }); 12 | const playlists = await query.leftJoinAndSelect('playlist.tracks', 'track').getMany(); 13 | return playlists; 14 | } else { 15 | return []; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/notification/entities/notification.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { SubscribersNotifications } from './subscribers-notifications.entity'; 3 | 4 | @Entity('notifications') 5 | export class NotificationEntity extends BaseEntity { 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | 9 | @Column() 10 | title: string; 11 | 12 | @Column() 13 | body: string; 14 | 15 | @OneToMany(type => SubscribersNotifications, 16 | subscribersNotifications => subscribersNotifications.notification, { 17 | eager: true, 18 | }) 19 | subscribersNotifications: SubscribersNotifications[]; 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/modules/singer-album/singer-album.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; 2 | import { AbstractAlbum } from '../../commons/classes/abstract-album'; 3 | import { Singer } from '../singer/singer.entity'; 4 | import { Song } from '../song/song.entity'; 5 | 6 | 7 | @Entity('singer-albums') 8 | @Unique(['name']) 9 | export class SingerAlbum extends AbstractAlbum { 10 | @ManyToOne(type => Singer, singer => singer.singerAlbums, { 11 | eager: false 12 | }) 13 | singer: Singer; 14 | 15 | @OneToMany(type => Song, song => song.singerAlbum, { 16 | eager: true 17 | }) 18 | songs: Song[]; 19 | 20 | //Foreign Key 21 | @Column() 22 | singerId: number; 23 | } 24 | -------------------------------------------------------------------------------- /src/commons/classes/abstract-artist.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, PrimaryGeneratedColumn, Unique } from 'typeorm'; 2 | import { Gender } from '../enums/gender.enum'; 3 | import { ArtistType } from '../enums/artist-type.enum'; 4 | export abstract class AbstractArtist extends BaseEntity{ 5 | @PrimaryGeneratedColumn() 6 | id: number; 7 | 8 | @Column() 9 | name: string; 10 | 11 | @Column() 12 | info: string; 13 | 14 | @Column() 15 | image: string; 16 | 17 | @Column({ 18 | type: 'enum', 19 | enum: ArtistType, 20 | array: false 21 | }) 22 | type: ArtistType; 23 | 24 | @Column({ 25 | nullable: true 26 | }) 27 | gender: Gender; 28 | 29 | @Column() 30 | nationality: string; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/musician-album/musician-album.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; 2 | import { AbstractAlbum } from '../../commons/classes/abstract-album'; 3 | import { Musician } from '../musician/musician.entity'; 4 | import { Music } from '../music/music.entity'; 5 | @Entity('musician-albums') 6 | @Unique(['name']) 7 | export class MusicianAlbum extends AbstractAlbum{ 8 | 9 | @ManyToOne(type => Musician, musician => musician.musicianAlbums, { 10 | eager: false 11 | }) 12 | musician: Musician; 13 | 14 | //Foreign Key 15 | @Column() 16 | musicianId: number; 17 | 18 | @OneToMany(type => Music, music => music.musicianAlbum, { 19 | eager: true 20 | }) 21 | musics: Music[]; 22 | } 23 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/modules/profile/profile.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Profile } from './profile.entity'; 4 | import { AwsModule } from '../../shared/modules/aws/aws.module'; 5 | import { ProfileController } from './profile.controller'; 6 | import { ProfileService } from './profile.service'; 7 | import { PassportModule } from '@nestjs/passport'; 8 | import { AuthConstants } from '../../commons/constants/auth-constants'; 9 | 10 | @Module({ 11 | imports: [TypeOrmModule.forFeature([Profile]), PassportModule.register({ 12 | defaultStrategy: AuthConstants.strategies, 13 | }), AwsModule], 14 | controllers: [ProfileController], 15 | providers: [ProfileService], 16 | exports: [ProfileService] 17 | }) 18 | export class ProfileModule { 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/playlist/playlist.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique } from 'typeorm'; 2 | import { User } from '../auth/entities/user.entity'; 3 | import { Track } from '../track/track.entity'; 4 | 5 | @Entity('playlists') 6 | @Unique(['name']) 7 | export class Playlist extends BaseEntity{ 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @Column() 12 | name: string; 13 | 14 | @Column({ 15 | default: new Date() 16 | }) 17 | createdAt: Date; 18 | 19 | @ManyToOne(type => User, user => user.playlists, { 20 | eager: false 21 | }) 22 | user: User; 23 | 24 | @OneToMany(type => Track, track => track.playlist, { 25 | eager: true 26 | }) 27 | tracks: Track[]; 28 | 29 | // Foreign Key 30 | @Column() 31 | userId: number 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/favorite/favorite.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Favorite } from './favorite.entity'; 4 | import { PassportModule } from '@nestjs/passport'; 5 | import { AuthConstants } from '../../commons/constants/auth-constants'; 6 | import { FavoriteController } from './favorite.controller'; 7 | import { FavoriteService } from './favorite.service'; 8 | import { TrackModule } from '../track/track.module'; 9 | 10 | @Module({ 11 | imports: [TypeOrmModule.forFeature([Favorite]), 12 | PassportModule.register({ 13 | defaultStrategy: AuthConstants.strategies, 14 | }), TrackModule], 15 | controllers: [FavoriteController], 16 | providers: [FavoriteService], 17 | exports: [FavoriteService] 18 | }) 19 | export class FavoriteModule { 20 | } 21 | -------------------------------------------------------------------------------- /src/shared/modules/chat/entities/room.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Message } from './message.entity'; 3 | import { UserJoinedRoom } from './user-joined-room.entity'; 4 | 5 | 6 | @Entity('rooms') 7 | export class Room extends BaseEntity { 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @Column() 12 | name: string; 13 | 14 | @Column({ 15 | default: new Date(), 16 | }) 17 | createdAt: Date; 18 | 19 | @Column() 20 | createdBy: string; 21 | 22 | @OneToMany(type => Message, message => message.room, { 23 | eager: true 24 | }) 25 | messages: Message[]; 26 | 27 | @OneToMany(type => UserJoinedRoom, 28 | userJoinedRoom => userJoinedRoom.room, { 29 | eager: true 30 | }) 31 | userJoinedRooms: UserJoinedRoom[]; 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/playlist/playlist.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { PlaylistController } from './playlist.controller'; 4 | import { PlaylistService } from './playlist.service'; 5 | import { PassportModule } from '@nestjs/passport'; 6 | import { AuthConstants } from '../../commons/constants/auth-constants'; 7 | import { PlaylistRepository } from './playlist.repository'; 8 | import { TrackModule } from '../track/track.module'; 9 | 10 | 11 | @Module({ 12 | imports: [TypeOrmModule.forFeature([PlaylistRepository]), 13 | PassportModule.register({ 14 | defaultStrategy: AuthConstants.strategies, 15 | }), TrackModule 16 | ], 17 | controllers: [PlaylistController], 18 | providers: [PlaylistService], 19 | exports: [PlaylistService], 20 | }) 21 | export class PlaylistModule { 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/modules/chat/entities/user-joined-room.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Room } from './room.entity'; 3 | import { User } from '../../../../modules/auth/entities/user.entity'; 4 | 5 | 6 | @Entity('users-joined-rooms') 7 | export class UserJoinedRoom extends BaseEntity { 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @Column({ 12 | default: new Date() 13 | }) 14 | joinedIn: Date; 15 | 16 | @Column() 17 | joinedUsername: string; 18 | 19 | @ManyToOne(type => Room, room => room.userJoinedRooms, { 20 | eager: false 21 | }) 22 | room: Room; 23 | 24 | @ManyToOne(type => User, user => user.userJoinedRooms, { 25 | eager: false 26 | }) 27 | user: User; 28 | 29 | 30 | // foreign keys 31 | @Column() 32 | userId: number; 33 | 34 | @Column() 35 | roomId: number; 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/singer/singer.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { SingerRepository } from './singer.repository'; 4 | import { SingerController } from './singer.controller'; 5 | import { SingerService } from './singer.service'; 6 | import { AwsModule } from '../../shared/modules/aws/aws.module'; 7 | import { PassportModule } from '@nestjs/passport'; 8 | import { AuthConstants } from '../../commons/constants/auth-constants'; 9 | import { SingerAlbumModule } from '../singer-album/singer-album.module'; 10 | 11 | @Module({ 12 | imports: [TypeOrmModule.forFeature([SingerRepository]), 13 | PassportModule.register({ 14 | defaultStrategy: AuthConstants.strategies, 15 | }), 16 | AwsModule, SingerAlbumModule], 17 | controllers: [SingerController], 18 | providers: [SingerService] 19 | 20 | }) 21 | export class SingerModule {} 22 | -------------------------------------------------------------------------------- /src/shared/modules/chat/entities/message.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Room } from './room.entity'; 3 | import { User } from '../../../../modules/auth/entities/user.entity'; 4 | 5 | 6 | @Entity('messages') 7 | export class Message extends BaseEntity { 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @Column() 12 | text: string; 13 | 14 | @Column() 15 | created: Date; 16 | 17 | @Column() 18 | roomName: string; 19 | 20 | @Column() 21 | sender: string; 22 | 23 | @ManyToOne(type => Room, room => room.messages, { 24 | eager: false 25 | }) 26 | room: Room; 27 | 28 | @ManyToOne(type => User, user => user.messages, { 29 | eager: false 30 | }) 31 | user: User; 32 | 33 | // foreign key 34 | @Column() 35 | roomId: number; 36 | 37 | // foreign key 38 | @Column() 39 | userId: number; 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/music/music.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; 2 | import { AbstractMusic } from '../../commons/classes/abstract-music'; 3 | import { MusicType } from '../../commons/enums/music-type.enum'; 4 | import { MusicianAlbum } from '../musician-album/musician-album.entity'; 5 | import { Track } from '../track/track.entity'; 6 | 7 | 8 | @Entity('musics') 9 | export class Music extends AbstractMusic { 10 | 11 | @Column({ 12 | type: 'enum', 13 | enum: MusicType, 14 | array: false 15 | }) 16 | type: MusicType; 17 | 18 | @ManyToOne(type => MusicianAlbum, 19 | musicianAlbum => musicianAlbum.musics, { 20 | eager: false, 21 | }) 22 | musicianAlbum: MusicianAlbum; 23 | 24 | @OneToMany(type => Track, track => track.playlist, { 25 | eager: true 26 | }) 27 | tracks: Track[]; 28 | 29 | // Foreign Key 30 | @Column() 31 | musicianAlbumId: number 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/musician/musician.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { MusicianRepository } from './musician.repository'; 4 | import { MusicianController } from './musician.controller'; 5 | import { MusicianService } from './musician.service'; 6 | import { AwsModule } from '../../shared/modules/aws/aws.module'; 7 | import { PassportModule } from '@nestjs/passport'; 8 | import { AuthConstants } from '../../commons/constants/auth-constants'; 9 | import { MusicianAlbumModule } from '../musician-album/musician-album.module'; 10 | 11 | @Module({ 12 | imports: [TypeOrmModule.forFeature([MusicianRepository]), 13 | PassportModule.register({ 14 | defaultStrategy: AuthConstants.strategies, 15 | }), AwsModule, MusicianAlbumModule], 16 | controllers: [MusicianController], 17 | providers: [MusicianService], 18 | }) 19 | export class MusicianModule { 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/singer-album/singer-album.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { SingerAlbum } from './singer-album.entity'; 4 | import { SingerAlbumController } from './singer-album.controller'; 5 | import { AwsModule } from '../../shared/modules/aws/aws.module'; 6 | import { SingerAlbumService } from './singer-album.service'; 7 | import { PassportModule } from '@nestjs/passport'; 8 | import { AuthConstants } from '../../commons/constants/auth-constants'; 9 | import { SongModule } from '../song/song.module'; 10 | 11 | @Module({ 12 | imports: [TypeOrmModule.forFeature([SingerAlbum]),PassportModule.register({ 13 | defaultStrategy: AuthConstants.strategies, 14 | }), AwsModule, SongModule], 15 | controllers: [SingerAlbumController], 16 | providers: [SingerAlbumService], 17 | exports: [SingerAlbumService] 18 | 19 | }) 20 | export class SingerAlbumModule {} 21 | -------------------------------------------------------------------------------- /src/modules/musician-album/musician-album.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { MusicianAlbum } from './musician-album.entity'; 4 | import { MusicianAlbumController } from './musician-album.controller'; 5 | import { AwsModule } from '../../shared/modules/aws/aws.module'; 6 | import { MusicianAlbumService } from './musician-album.service'; 7 | import { PassportModule } from '@nestjs/passport'; 8 | import { AuthConstants } from '../../commons/constants/auth-constants'; 9 | import { MusicModule } from '../music/music.module'; 10 | 11 | @Module({ 12 | imports: [TypeOrmModule.forFeature([MusicianAlbum]), AwsModule, 13 | PassportModule.register({ 14 | defaultStrategy: AuthConstants.strategies, 15 | }), MusicModule], 16 | controllers: [MusicianAlbumController], 17 | providers: [MusicianAlbumService], 18 | exports: [MusicianAlbumService] 19 | }) 20 | export class MusicianAlbumModule { 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/notification/entities/subscriber.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Key } from '../classes/key'; 3 | import { User } from '../../auth/entities/user.entity'; 4 | import { SubscribersNotifications } from './subscribers-notifications.entity'; 5 | 6 | @Entity('subscribers') 7 | export class Subscriber extends BaseEntity{ 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @Column() 12 | endpoint: string; 13 | 14 | @Column({nullable: true}) 15 | expirationTime: Date; 16 | 17 | @Column('simple-json') 18 | keys: Key; 19 | 20 | @OneToOne(type => User, user => user.subscriber, { 21 | eager: true 22 | }) 23 | user: User; 24 | 25 | @OneToMany(type => SubscribersNotifications, 26 | subscribersNotifications => subscribersNotifications.subscriber, { 27 | eager: true, 28 | }) 29 | subscribersNotifications: SubscribersNotifications[]; 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/commons/guards/admin-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { Reflector } from '@nestjs/core'; 4 | import { Role } from '../enums/role.enum'; 5 | import { User } from '../../modules/auth/entities/user.entity'; 6 | 7 | @Injectable() 8 | export class AdminAuthGuard implements CanActivate { 9 | constructor(private readonly reflector: Reflector) { 10 | } 11 | 12 | canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | 14 | const roles = this.reflector.get('roles', context.getHandler()); 15 | if (!roles) { 16 | return true; 17 | } 18 | 19 | const req = context.switchToHttp().getRequest(); 20 | const admin: User = req.user; 21 | if (admin) { 22 | const hasRole = () => admin.roles.some(role => role === Role.ADMIN); 23 | return hasRole(); 24 | } else { 25 | return false; 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/commons/guards/accepted-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { Reflector } from '@nestjs/core'; 4 | import { Role } from '../enums/role.enum'; 5 | import { User } from '../../modules/auth/entities/user.entity'; 6 | 7 | @Injectable() 8 | export class AcceptedAuthGuard implements CanActivate { 9 | constructor(private readonly reflector: Reflector) { 10 | } 11 | 12 | canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | const roles = this.reflector.get('roles', context.getHandler); 14 | if (!roles) { 15 | return true; 16 | } 17 | const req = context.switchToHttp().getRequest(); 18 | const user: User = req.user; 19 | if (user) { 20 | const hasRole = () => user.roles.some(role => role === Role.ADMIN || role === Role.USER); 21 | return hasRole(); 22 | } else { 23 | return false; 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/commons/guards/user-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { Reflector } from '@nestjs/core'; 4 | import { Role } from '../enums/role.enum'; 5 | import { User } from '../../modules/auth/entities/user.entity'; 6 | 7 | @Injectable() 8 | export class UserAuthGuard implements CanActivate { 9 | constructor(private readonly reflector: Reflector) { 10 | } 11 | 12 | canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | const roles = this.reflector.get('roles', context.getHandler); 14 | if (!roles) { 15 | return true; 16 | } 17 | const req = context.switchToHttp().getRequest(); // --> fetching request object 18 | const user: User = req.user; 19 | if (user) { 20 | const hasRole = () => user.roles.some(role => role === Role.USER); 21 | return hasRole(); 22 | } else { 23 | return false; 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/notification/notification.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { NotificationEntity } from './entities/notification.entity'; 4 | import { Subscriber } from './entities/subscriber.entity'; 5 | import { SubscribersNotifications } from './entities/subscribers-notifications.entity'; 6 | import { PassportModule } from '@nestjs/passport'; 7 | import { AuthConstants } from '../../commons/constants/auth-constants'; 8 | import { NotificationController } from './notification.controller'; 9 | import { NotificationService } from './notification.service'; 10 | 11 | @Module({ 12 | imports: [ 13 | TypeOrmModule.forFeature([NotificationEntity, Subscriber, SubscribersNotifications]), 14 | PassportModule.register({ 15 | defaultStrategy: AuthConstants.strategies, 16 | }), 17 | ], 18 | providers: [NotificationService], 19 | controllers: [NotificationController], 20 | exports: [NotificationService] 21 | }) 22 | export class NotificationModule { 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/song/song.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { SongRepository } from './song.repository'; 4 | import { SongController } from './song.controller'; 5 | import { SongService } from './song.service'; 6 | import { AwsModule } from '../../shared/modules/aws/aws.module'; 7 | import { FavoriteModule } from '../favorite/favorite.module'; 8 | import { PassportModule } from '@nestjs/passport'; 9 | import { AuthConstants } from '../../commons/constants/auth-constants'; 10 | import { PlaylistModule } from '../playlist/playlist.module'; 11 | import { TrackModule } from '../track/track.module'; 12 | 13 | @Module({ 14 | imports: [TypeOrmModule.forFeature([SongRepository]), 15 | AwsModule, FavoriteModule, PassportModule.register({ 16 | defaultStrategy: AuthConstants.strategies, 17 | }), PlaylistModule, TrackModule], 18 | controllers: [SongController], 19 | providers: [SongService], 20 | exports: [SongService] 21 | }) 22 | export class SongModule { 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/modules/chat/chat.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Room } from './entities/room.entity'; 4 | import { Message } from './entities/message.entity'; 5 | import { UserJoinedRoom } from './entities/user-joined-room.entity'; 6 | import { PassportModule } from '@nestjs/passport'; 7 | import { AuthConstants } from '../../../commons/constants/auth-constants'; 8 | import { RoomController } from './room.controller'; 9 | import { ChatService } from './chat.service'; 10 | import { AuthModule } from '../../../modules/auth/auth.module'; 11 | import { ChatGateway } from './chat.gateway'; 12 | 13 | @Module({ 14 | imports: [TypeOrmModule.forFeature([Room, Message, UserJoinedRoom]), 15 | PassportModule.register({ 16 | defaultStrategy: AuthConstants.strategies, 17 | }), 18 | forwardRef(() => AuthModule)], 19 | providers: [ChatGateway, ChatService], 20 | controllers: [RoomController], 21 | exports: [ChatService], 22 | }) 23 | export class ChatModule { 24 | } 25 | -------------------------------------------------------------------------------- /src/modules/music/music.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { MusicRepository } from './music.repository'; 4 | import { MusicController } from './music.controller'; 5 | import { AwsModule } from '../../shared/modules/aws/aws.module'; 6 | import { MusicService } from './music.service'; 7 | import { FavoriteModule } from '../favorite/favorite.module'; 8 | import { PassportModule } from '@nestjs/passport'; 9 | import { AuthConstants } from '../../commons/constants/auth-constants'; 10 | import { PlaylistModule } from '../playlist/playlist.module'; 11 | import { TrackModule } from '../track/track.module'; 12 | 13 | @Module({ 14 | imports: [TypeOrmModule.forFeature([MusicRepository]), 15 | forwardRef(() => PlaylistModule), AwsModule, FavoriteModule, 16 | PassportModule.register({ 17 | defaultStrategy: AuthConstants.strategies, 18 | }), TrackModule], 19 | controllers: [MusicController], 20 | providers: [MusicService], 21 | exports: [MusicService], 22 | }) 23 | export class MusicModule { 24 | } 25 | -------------------------------------------------------------------------------- /src/modules/music/music.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Music } from './music.entity'; 3 | import { MusicType } from '../../commons/enums/music-type.enum'; 4 | 5 | @EntityRepository(Music) 6 | export class MusicRepository extends Repository{ 7 | 8 | async getLimitedMusics(limit: number): Promise{ 9 | const query = this.createQueryBuilder('music').select(); 10 | if(limit){ 11 | query.limit(limit); 12 | } 13 | const musics = await query.leftJoinAndSelect('music.tracks', 'track').getMany(); 14 | return musics; 15 | } 16 | 17 | async getFilteredMusics(limit: number, type: MusicType, rate: number): Promise{ 18 | const query = this.createQueryBuilder('music').select(); 19 | if(limit){ 20 | query.limit(limit); 21 | } 22 | if(type){ 23 | query.where('music.type = :type', {type}); 24 | } 25 | if(rate){ 26 | query.andWhere('music.rate = :rate', {rate}); 27 | } 28 | const musics = await query.leftJoinAndSelect('music.tracks', 'track').getMany(); 29 | return musics; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/song/song.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; 2 | import { AbstractMusic } from '../../commons/classes/abstract-music'; 3 | import { SongType } from '../../commons/enums/song-type.enum'; 4 | import { SongLanguage } from '../../commons/enums/song-language.enum'; 5 | import { SingerAlbum } from '../singer-album/singer-album.entity'; 6 | import { Track } from '../track/track.entity'; 7 | 8 | @Entity('songs') 9 | @Unique(['name', 'source']) 10 | export class Song extends AbstractMusic{ 11 | 12 | @Column({ 13 | type: 'enum', 14 | enum: SongType, 15 | array: false 16 | }) 17 | type: SongType; 18 | 19 | @Column({ 20 | type: 'enum', 21 | enum: SongLanguage, 22 | array: false 23 | }) 24 | language: SongLanguage; 25 | 26 | @ManyToOne(type => SingerAlbum, singerAlbum => singerAlbum.songs, { 27 | eager: false 28 | }) 29 | singerAlbum: SingerAlbum; 30 | 31 | @OneToMany(type => Track, track => track.playlist, { 32 | eager: true 33 | }) 34 | tracks: Track[]; 35 | 36 | // Foreign Key 37 | @Column() 38 | singerAlbumId: number; 39 | } 40 | -------------------------------------------------------------------------------- /src/exampleOfDI.txt: -------------------------------------------------------------------------------- 1 | 2 | // without dependency injection 3 | 4 | class Dog { 5 | name: string; 6 | age: number; 7 | } 8 | 9 | class Cat { 10 | name: string; 11 | age: number; 12 | dog: Dog; 13 | constructor(){ 14 | this.dog = new Dog(); // creating instance (object) from Dog class. 15 | } 16 | 17 | printDogInfo(){ 18 | this.dog.name = 'john'; 19 | this.dog.age = 12; 20 | console.log(this.dog.name); 21 | console.log(this.dog.age); 22 | } 23 | } 24 | 25 | 26 | 27 | 28 | @Injectable() 29 | export class Dog { 30 | name: string; 31 | age: number; 32 | doSomething(){ 33 | console.log('hey'); 34 | } 35 | } 36 | 37 | 38 | // with dependency injection 39 | @Injectable() 40 | export class Cat { 41 | constructor(public dogService: Dog){ 42 | 43 | } 44 | printDogInfo(){ 45 | this.dog.name = 'john'; 46 | this.dog.age = 12; 47 | console.log(this.dog.name); 48 | console.log(this.dog.age); 49 | } 50 | } 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { NestExpressApplication } from '@nestjs/platform-express'; 4 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 5 | import * as webPush from 'web-push'; 6 | import { config } from './config'; 7 | const bootstrap = async () => { 8 | const app = await NestFactory.create(AppModule); 9 | app.enableCors(); 10 | const options = new DocumentBuilder() 11 | .setTitle('Music Land API') 12 | .setDescription('This is the official API of Music Land System') 13 | .setVersion('1.0') 14 | .addTag('songs, music, albums, artists, musicians, singers, notifications, chats, gateways, rooms, auth, strategies, jwt') 15 | .build(); 16 | const document = SwaggerModule.createDocument(app, options); 17 | SwaggerModule.setup('api', app, document); 18 | webPush.setVapidDetails( 19 | 'mailto:example@yourdomain.org', 20 | config.vapidKeys.publicKey, 21 | config.vapidKeys.privateKey, 22 | ); 23 | const port: number = parseInt(`${process.env.PORT}`) || 3000; 24 | 25 | await app.listen(port); 26 | }; 27 | bootstrap(); 28 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [false, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "whitespace": false, 12 | "no-inferrable-types": false, 13 | "prefer-const": false, 14 | "variable-name": false, 15 | "semicolon": false, 16 | "typedef": false, 17 | "no-shadowed-variable": false, 18 | "typedef-whitespace": false, 19 | "max-classes-per-file": false, 20 | "no-use-before-declare": false, 21 | "align": false, 22 | "prefer-for-of": false, 23 | "newline-before-return": false, 24 | "one-line": false, 25 | "trailing-comma": false, 26 | "space-within-parens": false, 27 | "max-line-length": [true, 150], 28 | "member-ordering": [false], 29 | "interface-name": [false], 30 | "eofline": false, 31 | "no-trailing-whitespace": false, 32 | "arrow-parens": false, 33 | "no-console": false, 34 | "no-consecutive-blank-lines": false, 35 | "no-empty": false, 36 | "object-literal-sort-keys": false 37 | }, 38 | "rulesDirectory": [] 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/auth/stratigies/jwt-strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Strategy, ExtractJwt } from 'passport-jwt'; 4 | import { AuthConstants } from '../../../commons/constants/auth-constants'; 5 | import { User } from '../entities/user.entity'; 6 | import { JwtPayload } from '../../../commons/interfaces/jwt-payload.interface'; 7 | import { InjectRepository } from '@nestjs/typeorm'; 8 | import { UserRepository } from '../repositories/user.repository'; 9 | 10 | 11 | @Injectable() 12 | export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { 13 | constructor(@InjectRepository(User) private userRepository: UserRepository) { 14 | super({ 15 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 16 | ignoreExpiration: false, 17 | secretOrKey: AuthConstants.secretKey, 18 | }); 19 | } 20 | 21 | async validate(payload: JwtPayload): Promise { 22 | const { email } = payload; 23 | const user = await this.userRepository.findByEmail(email); 24 | if (!user) { 25 | throw new UnauthorizedException('User Is Not Authorized'); 26 | } 27 | return user; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/modules/song/song.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Song } from './song.entity'; 3 | import { SongType } from '../../commons/enums/song-type.enum'; 4 | import { SongLanguage } from '../../commons/enums/song-language.enum'; 5 | 6 | @EntityRepository(Song) 7 | export class SongRepository extends Repository{ 8 | 9 | async getLimitedSongs(limit: number): Promise{ 10 | const query = this.createQueryBuilder('song').select(); 11 | if(limit){ 12 | query.limit(limit); 13 | } 14 | const songs = await query.leftJoinAndSelect('song.tracks', 'track').getMany(); 15 | return songs; 16 | } 17 | 18 | async getFilteredSongs(limit: number, type: SongType, language: SongLanguage, rate: number): Promise{ 19 | const query = this.createQueryBuilder('song').select(); 20 | if(limit){ 21 | query.limit(limit); 22 | } 23 | if(type){ 24 | query.where('song.type = :type', {type}); 25 | } 26 | if(language){ 27 | query.andWhere('song.language = :language', {language}); 28 | } 29 | if(rate){ 30 | query.andWhere('song.rate = :rate', {rate}); 31 | } 32 | const songs = await query.leftJoinAndSelect('song.tracks', 'track').getMany(); 33 | return songs; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/profile/profile.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; 2 | import { Gender } from '../../commons/enums/gender.enum'; 3 | import { User } from '../auth/entities/user.entity'; 4 | import { Favorite } from '../favorite/favorite.entity'; 5 | 6 | @Entity('profiles') 7 | @Unique(['phone']) 8 | export class Profile extends BaseEntity { 9 | @PrimaryGeneratedColumn() 10 | id: number; 11 | 12 | @Column() 13 | firstName: string; 14 | 15 | @Column() 16 | lastName: string; 17 | 18 | @Column({ 19 | nullable:true 20 | }) 21 | gender: Gender; 22 | 23 | @Column({ 24 | nullable:true 25 | }) 26 | age: number; 27 | 28 | @Column({ 29 | nullable:true 30 | }) 31 | country: string; 32 | 33 | @Column({ 34 | nullable:true 35 | }) 36 | city: string; 37 | 38 | @Column({ 39 | nullable:true 40 | }) 41 | address: string; 42 | 43 | @Column({ 44 | nullable:true 45 | }) 46 | phone: string; 47 | 48 | @Column({ 49 | nullable: true 50 | }) 51 | image: string; 52 | 53 | 54 | @OneToOne(type => User, user => user.profile, { 55 | eager: true 56 | }) 57 | user: User; 58 | 59 | @OneToOne(type => Favorite, favorite => favorite.profile) 60 | @JoinColumn() 61 | favorite: Favorite; 62 | 63 | @Column() 64 | favoriteId: number; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/modules/notification/entities/subscribers-notifications.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { NotificationData } from '../classes/notification-data'; 3 | import { Subscriber } from './subscriber.entity'; 4 | import { NotificationEntity } from './notification.entity'; 5 | 6 | @Entity('subscribers-notifications') 7 | export class SubscribersNotifications extends BaseEntity { 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @Column() 12 | title: string; 13 | 14 | @Column() 15 | body: string; 16 | 17 | 18 | // Optional 19 | @Column('simple-json') 20 | data: NotificationData; 21 | 22 | @Column({ 23 | type: 'jsonb', 24 | array: false, 25 | }) 26 | actions: Array<{ title: string, action: string }>; 27 | 28 | @Column('int', { 29 | array: true, 30 | }) 31 | vibrate: Array; 32 | 33 | @ManyToOne(type => Subscriber, 34 | subscriber => subscriber.subscribersNotifications, { 35 | eager: false, 36 | }) 37 | subscriber: Subscriber; 38 | 39 | @ManyToOne(type => NotificationEntity, 40 | notification => notification.subscribersNotifications, { 41 | eager: false, 42 | }) 43 | notification: NotificationEntity; 44 | 45 | 46 | // foreign keys 47 | @Column() 48 | subscriberId: number; 49 | 50 | @Column() 51 | notificationId: number; 52 | } 53 | -------------------------------------------------------------------------------- /src/modules/track/track.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, Generated, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Playlist } from '../playlist/playlist.entity'; 3 | import { Favorite } from '../favorite/favorite.entity'; 4 | import { Song } from '../song/song.entity'; 5 | import { Music } from '../music/music.entity'; 6 | 7 | @Entity('tracks') 8 | export class Track extends BaseEntity{ 9 | @PrimaryGeneratedColumn() 10 | id: number; 11 | 12 | @Column() 13 | title: string; 14 | 15 | @Column() 16 | link: string; 17 | 18 | @Generated() 19 | @Column() 20 | index: number; 21 | 22 | 23 | @ManyToOne(type => Playlist, playlist => playlist.tracks, { 24 | eager: false, 25 | }) 26 | playlist: Playlist; 27 | 28 | @ManyToOne(type => Favorite, favorite => favorite.tracks, { 29 | eager: false 30 | }) 31 | favorite: Favorite; 32 | 33 | @ManyToOne(type => Song, song => song.tracks, { 34 | eager: false 35 | }) 36 | song: Song; 37 | 38 | @ManyToOne(type => Music, music => music.tracks, { 39 | eager: false 40 | }) 41 | music: Music; 42 | 43 | @Column({ 44 | nullable: true 45 | }) 46 | playlistId: number; 47 | 48 | @Column({ 49 | nullable: true 50 | }) 51 | favoriteId: number; 52 | 53 | @Column({ 54 | nullable: true 55 | }) 56 | songId: number; 57 | 58 | @Column({ 59 | nullable: true 60 | }) 61 | musicId: number; 62 | } 63 | -------------------------------------------------------------------------------- /src/modules/singer/singer.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Singer } from './singer.entity'; 3 | import { ArtistType } from '../../commons/enums/artist-type.enum'; 4 | import { Gender } from '../../commons/enums/gender.enum'; 5 | 6 | 7 | // this is a provider 8 | @EntityRepository(Singer) 9 | export class SingerRepository extends Repository { 10 | async getLimitedSingers(limit: number): Promise { 11 | const query = this.createQueryBuilder('singer').select(); 12 | if (limit) { 13 | query.limit(limit); 14 | } 15 | const singers = await query.leftJoinAndSelect('singer.singerAlbums', 'singer-album').getMany(); 16 | return singers; 17 | } 18 | 19 | async getFilteredSingers(limit: number, nationality: string, type: ArtistType, 20 | gender: Gender): Promise { 21 | const query = this.createQueryBuilder('singer').select(); 22 | if (limit) { 23 | query.limit(limit); 24 | } 25 | if (nationality) { 26 | query.where('singer.nationality LIKE :nationality', {nationality}); 27 | } 28 | if (type) { 29 | query.andWhere('singer.type = :type', {type}); 30 | } 31 | if (gender) { 32 | query.andWhere('singer.gender = :gender', {gender}); 33 | } 34 | const singers = await query.leftJoinAndSelect('singer.singerAlbums', 'singer-albums').getMany(); 35 | return singers; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | db: { 3 | type: 'postgres', 4 | host: 'host', 5 | port: 5432, 6 | database: 'database', 7 | username: 'username', 8 | password: 'password', 9 | entities: [__dirname + '/**/*.entity{.ts,.js}'], 10 | synchronize: true, 11 | }, 12 | aws: { 13 | AWS_S3_BUCKET_NAME: 'AWS_S3_BUCKET_NAME', 14 | ACCESS_KEY_ID: 'ACCESS_KEY_ID', 15 | SECRET_ACCESS_KEY: 'SECRET_ACCESS_KEY', 16 | cdnUrl: 'cdnUrl', 17 | }, 18 | nodeMailerOptions: { 19 | transport: { 20 | host: 'smtp.gmail.com', 21 | port: 465, 22 | secure: true, 23 | auth: { 24 | username: 'username', 25 | pass: 'pass', 26 | }, 27 | tls: { 28 | rejectUnauthorized: false, 29 | }, 30 | }, 31 | }, 32 | frontEndKeys: { 33 | url: 'localhost', 34 | port: 4200, 35 | endpoints: ['auth/reset-password', 'auth/verify-email'], 36 | }, 37 | 38 | 39 | vapidKeys: { 40 | publicKey: 'publicKey', 41 | privateKey: 'privateKey' 42 | }, 43 | 44 | oAuthGoogle: { 45 | GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID', 46 | GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET', 47 | CALL_BACK_URI: 'CALL_BACK_URI', 48 | SCOPE: ['email', 'profile'], 49 | }, 50 | 51 | oAuthFacebook: { 52 | FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID', 53 | FACEBOOK_SECRET_ID: 'FACEBOOK_SECRET_ID', 54 | CALL_BACK_URI: 'CALL_BACK_URI', 55 | SCOPE: ['email'], 56 | } 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /src/modules/musician/musician.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { ArtistType } from '../../commons/enums/artist-type.enum'; 3 | import { Gender } from '../../commons/enums/gender.enum'; 4 | import { Musician } from './musician.entity'; 5 | 6 | 7 | @EntityRepository(Musician) 8 | export class MusicianRepository extends Repository { 9 | async getLimitedMusicians(limit: number): Promise { 10 | const query = this.createQueryBuilder('musician').select(); 11 | if (limit) { 12 | query.limit(limit); 13 | } 14 | const musicians = await query.leftJoinAndSelect('musician.musicianAlbums', 'musician-album').getMany(); 15 | return musicians; 16 | } 17 | 18 | async getFilteredMusicians(limit: number, nationality: string, type: ArtistType, 19 | gender: Gender): Promise { 20 | const query = this.createQueryBuilder('musician').select(); 21 | if (limit) { 22 | query.limit(limit); 23 | } 24 | if (nationality) { 25 | query.where('musician.nationality LIKE :nationality', { nationality }); 26 | } 27 | if (type) { 28 | query.andWhere('musician.type = :type', { type }); 29 | } 30 | if (gender) { 31 | query.andWhere('musician.gender = :gender', { gender }); 32 | } 33 | const musicians = await query.leftJoinAndSelect('musician.musicianAlbums', 'musician-album').getMany(); 34 | return musicians; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/favorite/favorite.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Delete, Get, Param, ParseIntPipe, UseGuards } from '@nestjs/common'; 2 | import { FavoriteService } from './favorite.service'; 3 | import { AuthGuard } from '@nestjs/passport'; 4 | import { Roles } from '../../commons/decorators/roles.decorator'; 5 | import { Role } from '../../commons/enums/role.enum'; 6 | import { UserAuthGuard } from '../../commons/guards/user-auth.guard'; 7 | 8 | 9 | @UseGuards(AuthGuard(), UserAuthGuard) 10 | @Roles([Role.USER]) 11 | @Controller('favorite-lists') 12 | export class FavoriteController { 13 | constructor(private favoriteListService: FavoriteService) { 14 | } 15 | 16 | @Get(':id') 17 | getUserFavoriteList(@Param('id', ParseIntPipe) id: number) { 18 | return this.favoriteListService.getUserFavoriteList(id); 19 | } 20 | 21 | /* 22 | the following endpoints related to the interaction between the favorite entity 23 | and music, song entities 24 | * */ 25 | 26 | @Delete(':id/clear-favorite-list') 27 | clearFavoriteList(@Param('id', ParseIntPipe) id: number) { 28 | return this.favoriteListService.clearFavoriteListContent(id); 29 | } 30 | 31 | @Delete(':favoriteId/remove-track-from-favorite-list/:trackId') 32 | removeTrackFromFavoriteList(@Param('favoriteId', ParseIntPipe) favoriteId: number, 33 | @Param('trackId', ParseIntPipe) trackId: number) { 34 | return this.favoriteListService.removeTrackFromFavouriteList(favoriteId, trackId); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/auth/stratigies/google.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Strategy } from 'passport-google-oauth20'; 4 | import { config } from '../../../config'; 5 | import { UserRepository } from '../repositories/user.repository'; 6 | import { AuthService } from '../auth.service'; 7 | 8 | @Injectable() 9 | export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { 10 | constructor(private userRepository: UserRepository, private authService: AuthService) { 11 | super({ 12 | clientID: config.oAuthGoogle.GOOGLE_CLIENT_ID, 13 | clientSecret: config.oAuthGoogle.GOOGLE_CLIENT_SECRET, 14 | callbackURL: config.oAuthGoogle.CALL_BACK_URI, 15 | passReqToCallback: true, 16 | scope: config.oAuthGoogle.SCOPE, 17 | }); 18 | } 19 | 20 | async validate(request: any, accessToken: string, 21 | refreshToken: string, profile: any, done: any){ 22 | // check if the user exist on the database or not 23 | const {id} = profile; 24 | let user = await this.userRepository.findOne({ 25 | where: { 26 | googleId: id, 27 | }, 28 | }); 29 | if (user) { 30 | const { emails } = profile; 31 | const jwt = this.authService.generateJwtToken(emails[0].value); 32 | done(null, { user, jwt }); 33 | } else { 34 | const { user, jwt } = await this.authService.SignInGoogle(profile, id); 35 | done(null, { user, jwt }); 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/modules/auth/stratigies/facebook.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Strategy } from 'passport-facebook'; 4 | import { config } from '../../../config'; 5 | import { UserRepository } from '../repositories/user.repository'; 6 | import { AuthService } from '../auth.service'; 7 | 8 | @Injectable() 9 | export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') { 10 | constructor(private userRepository: UserRepository, private authService: AuthService) { 11 | super({ 12 | clientID: config.oAuthFacebook.FACEBOOK_CLIENT_ID, 13 | clientSecret: config.oAuthFacebook.FACEBOOK_SECRET_ID, 14 | callbackURL: config.oAuthFacebook.CALL_BACK_URI, 15 | scope: config.oAuthFacebook.SCOPE, 16 | profileFields: ['id', 'displayName', 'email', 'photos', 'name'] 17 | }); 18 | } 19 | 20 | async validate(accessToken: string, 21 | refreshToken: string, profile: any, done: any){ 22 | // check if the user exist on the database or not 23 | const {id} = profile; 24 | let user = await this.userRepository.findOne({ 25 | where: { 26 | facebookId: id, 27 | }, 28 | }); 29 | if (user) { 30 | const { emails } = profile; 31 | const jwt = this.authService.generateJwtToken(emails[0].value); 32 | done(null, { user, jwt }); 33 | } else { 34 | const { user, jwt } = await this.authService.SingInFacebook(profile, id); 35 | done(null, { user, jwt }); 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/shared/modules/aws/aws.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as AWS from 'aws-sdk'; 3 | import { extname } from 'path'; 4 | import { config } from '../../../config'; 5 | const s3 = new AWS.S3(); 6 | AWS.config.update({ 7 | accessKeyId: config.aws.ACCESS_KEY_ID, 8 | secretAccessKey: config.aws.SECRET_ACCESS_KEY, 9 | }); 10 | @Injectable() 11 | export class AwsService { 12 | async fileUpload(file: any, folderName: string): Promise { 13 | return new Promise((resolve, reject) => { 14 | const name = file.originalname.split('.')[0]; 15 | const fileExtName = extname(file.originalname); 16 | const randomName = Array(4) 17 | .fill(null) 18 | .map(() => Math.round(Math.random() * 16).toString(16)) 19 | .join(''); 20 | const params: AWS.S3.Types.PutObjectRequest = { 21 | Bucket: 'music-land', 22 | Key: `${folderName}/${name}-${randomName}${fileExtName}`, 23 | Body: file.buffer, 24 | ACL: 'public-read', 25 | }; 26 | s3.upload(params, (err, data: AWS.S3.ManagedUpload.SendData) => { 27 | if (err) { 28 | return reject(err); 29 | } 30 | resolve(`${config.aws.cdnUrl}/${data.Key}`); 31 | }); 32 | }); 33 | } 34 | 35 | 36 | async fileDelete(filename: string): Promise { 37 | return new Promise((resolve, reject) => { 38 | const params: AWS.S3.DeleteObjectRequest = { 39 | Bucket: 'music-land', 40 | Key: filename.substring(46), 41 | }; 42 | s3.deleteObject(params, (err, data) => { 43 | if (err) { 44 | return reject(err); 45 | } 46 | resolve(data); 47 | }); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/modules/track/track.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Track } from './track.entity'; 4 | import { Repository } from 'typeorm'; 5 | import { Song } from '../song/song.entity'; 6 | import { Music } from '../music/music.entity'; 7 | import { Favorite } from '../favorite/favorite.entity'; 8 | import { Playlist } from '../playlist/playlist.entity'; 9 | 10 | 11 | @Injectable() 12 | export class TrackService { 13 | constructor(@InjectRepository(Track) private trackRepository: Repository) { 14 | } 15 | 16 | async pushToFavoriteList(song: Song, music: Music, favorite: Favorite) { 17 | let track = new Track(); 18 | track = this.checkTrackType(track, song, music); 19 | track.favorite = favorite; // / creation of a foreign key called favoriteId 20 | return await track.save(); 21 | } 22 | 23 | async pushToPlaylist(song: Song, music: Music, playlist: Playlist) { 24 | let track = new Track(); 25 | track = this.checkTrackType(track, song, music); 26 | track.playlist = playlist; // creation of a foreign key called playlistId 27 | return await track.save(); 28 | } 29 | 30 | async deleteTrack(id: number) { 31 | const result = await this.trackRepository.delete(id); 32 | if (result.affected === 0) { 33 | throw new NotFoundException(`Track with Id ${id} Does not found`); 34 | } 35 | return result; 36 | } 37 | 38 | checkTrackType(track: Track, song: Song, music: Music) { 39 | if (song) { 40 | track.song = song; 41 | track.title = song.name; 42 | track.link = song.source; 43 | } else if (music) { 44 | track.music = music; 45 | track.title = music.name; 46 | track.link = music.source; 47 | } 48 | return track; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/shared/modules/chat/room.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, 7 | ParseIntPipe, 8 | Post, 9 | Put, 10 | UseGuards, 11 | } from '@nestjs/common'; 12 | import { AuthGuard } from '@nestjs/passport'; 13 | import { UserAuthGuard } from '../../../commons/guards/user-auth.guard'; 14 | import { Roles } from '../../../commons/decorators/roles.decorator'; 15 | import { Role } from '../../../commons/enums/role.enum'; 16 | import { GetAuthenticatedUser } from '../../../commons/decorators/get-authenticated-user.decorator'; 17 | import { User } from '../../../modules/auth/entities/user.entity'; 18 | import { RoomDto } from './dto/room.dto'; 19 | import { ChatService } from './chat.service'; 20 | 21 | 22 | @UseGuards(AuthGuard(), UserAuthGuard) 23 | @Roles([Role.USER]) 24 | @Controller('rooms') 25 | export class RoomController { 26 | constructor(private chatService: ChatService) { 27 | } 28 | 29 | @Get() 30 | getAllRooms() { 31 | return this.chatService.getAllRooms(); 32 | } 33 | 34 | @Get(':id') 35 | getRoomById(@Param('id', ParseIntPipe) id: number) { 36 | return this.chatService.getRoomById(id); 37 | } 38 | 39 | 40 | @Get('user-rooms') 41 | getUserRooms(@GetAuthenticatedUser() user: User) { 42 | return this.chatService.getUserRooms(user); 43 | } 44 | 45 | @Post() 46 | createNewRoom(@GetAuthenticatedUser() user: User, 47 | @Body() createRoomDto: RoomDto) { 48 | return this.chatService.createNewRoom(user, createRoomDto); 49 | } 50 | 51 | @Put(':id/edit-room') 52 | updateRoom(@Param('id', ParseIntPipe) id: number, 53 | @Body() updateRoomDto: RoomDto) { 54 | return this.chatService.updateRoom(id, updateRoomDto); 55 | } 56 | 57 | @Delete(':id/delete-room') 58 | deleteRoom(@Param('id', ParseIntPipe) id: number) { 59 | return this.chatService.deleteRoom(id); 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/modules/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { UserRepository } from './repositories/user.repository'; 4 | import { JwtModule } from '@nestjs/jwt'; 5 | import { PassportModule } from '@nestjs/passport'; 6 | import { AuthConstants } from '../../commons/constants/auth-constants'; 7 | import { EmailVerification } from './entities/email-verification.entity'; 8 | import { JwtStrategy } from './stratigies/jwt-strategy'; 9 | import { AuthService } from './auth.service'; 10 | import { AuthController } from './auth.controller'; 11 | import { ForgottenPassword } from './entities/forgotten-password.entity'; 12 | import { ProfileModule } from '../profile/profile.module'; 13 | import { FavoriteModule } from '../favorite/favorite.module'; 14 | import { PlaylistModule } from '../playlist/playlist.module'; 15 | import { ChatModule } from '../../shared/modules/chat/chat.module'; 16 | import { NotificationModule } from '../notification/notification.module'; 17 | import { GoogleStrategy } from './stratigies/google.strategy'; 18 | import { FacebookStrategy } from './stratigies/facebook.strategy'; 19 | 20 | @Module({ 21 | imports: [ 22 | PassportModule.register({ 23 | defaultStrategy: 'jwt', 24 | }), 25 | JwtModule.register({ 26 | secret: AuthConstants.secretKey, 27 | signOptions: { 28 | expiresIn: AuthConstants.expiresIn, 29 | }, 30 | }), 31 | TypeOrmModule.forFeature([UserRepository, EmailVerification, ForgottenPassword]), 32 | ProfileModule, 33 | FavoriteModule, 34 | PlaylistModule, 35 | NotificationModule, 36 | forwardRef(() => ChatModule), 37 | ], 38 | providers: [AuthService, JwtStrategy, GoogleStrategy, FacebookStrategy], 39 | controllers: [AuthController], 40 | exports: [AuthService, JwtStrategy, GoogleStrategy, FacebookStrategy, JwtModule, PassportModule], 41 | }) 42 | export class AuthModule { 43 | } 44 | -------------------------------------------------------------------------------- /src/modules/profile/profile.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Patch, 7 | Post, 8 | Put, 9 | UploadedFile, 10 | UseGuards, 11 | UseInterceptors, 12 | } from '@nestjs/common'; 13 | import { FileInterceptor } from '@nestjs/platform-express'; 14 | import { CreateProfileDto } from '../auth/dto/create-profile.dto'; 15 | import { GetAuthenticatedUser } from '../../commons/decorators/get-authenticated-user.decorator'; 16 | import { User } from '../auth/entities/user.entity'; 17 | import { AuthGuard } from '@nestjs/passport'; 18 | import { AcceptedAuthGuard } from '../../commons/guards/accepted-auth.guard'; 19 | import { Roles } from '../../commons/decorators/roles.decorator'; 20 | import { Role } from '../../commons/enums/role.enum'; 21 | import { ProfileService } from './profile.service'; 22 | 23 | 24 | @UseGuards(AuthGuard(), AcceptedAuthGuard) 25 | @Roles([Role.ADMIN, Role.USER]) 26 | @Controller('profiles') 27 | export class ProfileController { 28 | 29 | constructor(private profileService: ProfileService) { 30 | } 31 | 32 | @Get('user-profile') 33 | getUserProfile(@GetAuthenticatedUser() user: User) { 34 | return this.profileService.getProfileData(user); 35 | } 36 | 37 | @Post('user-profile/set-profile-image') 38 | @UseInterceptors(FileInterceptor('image')) 39 | setProfileImage(@GetAuthenticatedUser() user: User, @UploadedFile() image: any) { 40 | return this.profileService.setProfileImage(user, image); 41 | } 42 | 43 | @Patch('user-profile/change-profile-image') 44 | @UseInterceptors(FileInterceptor('image')) 45 | changeProfileImage(@GetAuthenticatedUser() user: User, @UploadedFile() image: any) { 46 | return this.profileService.changeProfileImage(user, image); 47 | } 48 | 49 | @Put('user-profile/edit-profile') 50 | editProfile(@GetAuthenticatedUser() user: User, @Body() createProfileDto: CreateProfileDto) { 51 | return this.profileService.editProfile(user, createProfileDto); 52 | } 53 | 54 | @Delete('user-profile/delete-profile-image') 55 | deleteProfileImage(@GetAuthenticatedUser() user: User) { 56 | return this.profileService.deleteProfileImage(user); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/modules/playlist/playlist.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, UseGuards } from '@nestjs/common'; 2 | import { GetAuthenticatedUser } from '../../commons/decorators/get-authenticated-user.decorator'; 3 | import { User } from '../auth/entities/user.entity'; 4 | import { AuthGuard } from '@nestjs/passport'; 5 | import { UserAuthGuard } from '../../commons/guards/user-auth.guard'; 6 | import { Roles } from '../../commons/decorators/roles.decorator'; 7 | import { Role } from '../../commons/enums/role.enum'; 8 | import { PlaylistDto } from './dto/playlist.dto'; 9 | import { PlaylistService } from './playlist.service'; 10 | 11 | @UseGuards(AuthGuard(), UserAuthGuard) 12 | @Roles([Role.USER]) 13 | @Controller('playlists') 14 | export class PlaylistController { 15 | 16 | constructor(private playlistService: PlaylistService) { 17 | } 18 | 19 | @Get('user-playlists') 20 | getAllUserPlaylists(@GetAuthenticatedUser() user: User) { 21 | return this.playlistService.getUserPlaylists(user); 22 | } 23 | 24 | @Get(':id') 25 | getPlaylist(@Param('id', ParseIntPipe) id: number) { 26 | return this.playlistService.getPlaylistById(id); 27 | 28 | } 29 | 30 | @Post('new-playlist') 31 | newPlaylist(@GetAuthenticatedUser() user: User, @Body() playlistDto: PlaylistDto) { 32 | return this.playlistService.newPlaylist(user, playlistDto); 33 | } 34 | 35 | @Put(':id/update-playlist') 36 | updatePlaylist(@Param('id', ParseIntPipe) id: number, @Body() playlistDto: PlaylistDto) { 37 | return this.playlistService.updatePlaylist(id, playlistDto); 38 | 39 | } 40 | 41 | @Delete(':id/delete-playlist') 42 | deletePlaylist(@Param('id', ParseIntPipe) id: number) { 43 | return this.playlistService.deletePlaylist(id); 44 | } 45 | 46 | 47 | 48 | @Delete(':id/clear-playlist') 49 | clearPlaylistContent(@Param('id', ParseIntPipe) id: number) { 50 | return this.playlistService.clearPlaylistContent(id); 51 | } 52 | 53 | @Delete(':playlistId/remove-track-from-playlist/:trackId') 54 | removeTrackFromFavoriteList(@Param('playlistId', ParseIntPipe) playlistId: number, 55 | @Param('trackId', ParseIntPipe) trackId: number) { 56 | return this.playlistService.removeTrackFromPlaylist(playlistId, trackId); 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/modules/musician-album/musician-album.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, ParseIntPipe, 7 | Post, 8 | Put, 9 | UploadedFile, 10 | UseGuards, 11 | UseInterceptors, 12 | } from '@nestjs/common'; 13 | import { CreateAlbumDto } from '../../shared/dto/create-album.dto'; 14 | import { MusicianAlbumService } from './musician-album.service'; 15 | import { FileInterceptor } from '@nestjs/platform-express'; 16 | import { MusicType } from '../../commons/enums/music-type.enum'; 17 | import { AuthGuard } from '@nestjs/passport'; 18 | import { AdminAuthGuard } from '../../commons/guards/admin-auth.guard'; 19 | import { Roles } from '../../commons/decorators/roles.decorator'; 20 | import { Role } from '../../commons/enums/role.enum'; 21 | 22 | @Controller('musicians-albums') 23 | export class MusicianAlbumController { 24 | constructor(private musicianAlbumService: MusicianAlbumService){} 25 | @Get() 26 | getAllMusicianAlbums() { 27 | return this.musicianAlbumService.getAllMusicianAlbums(); 28 | } 29 | 30 | @Get(':id') 31 | getMusicianAlbum(@Param('id', ParseIntPipe) id: number) { 32 | return this.musicianAlbumService.getMusicianAlbumById(id); 33 | } 34 | 35 | @Post(':id/new-music') 36 | @UseGuards(AuthGuard(), AdminAuthGuard) 37 | @Roles([Role.ADMIN]) 38 | @UseInterceptors(FileInterceptor('source')) 39 | createNewMusic(@Param('id', ParseIntPipe) id: number, 40 | @Body('name') name: string, 41 | @Body('description') description: string, 42 | @Body('artist') artist: string, 43 | @Body('type') type: MusicType, 44 | @UploadedFile() source: any) { 45 | return this.musicianAlbumService.createNewMusic(id, name, description, artist, type, source); 46 | } 47 | 48 | @Put(':id/update-album') 49 | @UseGuards(AuthGuard(), AdminAuthGuard) 50 | @Roles([Role.ADMIN]) 51 | updateAlbum(@Param('id', ParseIntPipe) id: number, @Body() createAlbumDto: CreateAlbumDto){ 52 | return this.musicianAlbumService.updateMusicianAlbum(id, createAlbumDto); 53 | } 54 | 55 | @Delete(':id/delete-album') 56 | @UseGuards(AuthGuard(), AdminAuthGuard) 57 | @Roles([Role.ADMIN]) 58 | deleteAlbum(@Param('id', ParseIntPipe) id: number){ 59 | return this.musicianAlbumService.deleteMusicianAlbum(id); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; 3 | import { config } from './config'; 4 | import { AuthModule } from './modules/auth/auth.module'; 5 | import { ProfileModule } from './modules/profile/profile.module'; 6 | import { MusicianModule } from './modules/musician/musician.module'; 7 | import { FavoriteModule } from './modules/favorite/favorite.module'; 8 | import { PlaylistModule } from './modules/playlist/playlist.module'; 9 | import { SongModule } from './modules/song/song.module'; 10 | import { MusicModule } from './modules/music/music.module'; 11 | import { MusicianAlbumModule } from './modules/musician-album/musician-album.module'; 12 | import { NotificationModule } from './modules/notification/notification.module'; 13 | import { SingerModule } from './modules/singer/singer.module'; 14 | import { SingerAlbumModule } from './modules/singer-album/singer-album.module'; 15 | import { TrackModule } from './modules/track/track.module'; 16 | import { AwsModule } from './shared/modules/aws/aws.module'; 17 | import { MulterModule } from '@nestjs/platform-express'; 18 | import { NodemailerDrivers, NodemailerModule, NodemailerOptions } from '@crowdlinker/nestjs-mailer'; 19 | import { ChatModule } from './shared/modules/chat/chat.module'; 20 | import { AppController } from './app.controller'; 21 | 22 | @Module({ 23 | imports: [TypeOrmModule.forRoot(config.db as TypeOrmModuleOptions), 24 | MulterModule.register({ 25 | dest: './files', 26 | }), 27 | NodemailerModule.forRoot( 28 | { 29 | transport: { 30 | host: 'smtp.gmail.com', 31 | port: 465, 32 | secure: true, 33 | auth: { 34 | user: 'user', 35 | pass: 'pass', 36 | }, 37 | tls: { 38 | rejectUnauthorized: false, 39 | }, 40 | }, 41 | } as NodemailerOptions), 42 | AuthModule, 43 | ChatModule, 44 | ProfileModule, 45 | SingerModule, 46 | MusicianModule, 47 | FavoriteModule, 48 | PlaylistModule, 49 | SongModule, 50 | MusicModule, 51 | SingerAlbumModule, 52 | MusicianAlbumModule, 53 | TrackModule, 54 | NotificationModule, 55 | AwsModule, 56 | ], 57 | controllers: [AppController], 58 | }) 59 | export class AppModule { 60 | } 61 | -------------------------------------------------------------------------------- /src/modules/notification/notification.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Param, ParseIntPipe, Post, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { AdminAuthGuard } from '../../commons/guards/admin-auth.guard'; 4 | import { Role } from '../../commons/enums/role.enum'; 5 | import { Roles } from '../../commons/decorators/roles.decorator'; 6 | import { AcceptedAuthGuard } from '../../commons/guards/accepted-auth.guard'; 7 | import { GetAuthenticatedUser } from '../../commons/decorators/get-authenticated-user.decorator'; 8 | import { User } from '../auth/entities/user.entity'; 9 | import { NotificationPayloadDto } from './notification-payload.dto'; 10 | import { NotificationService } from './notification.service'; 11 | import { UserAuthGuard } from '../../commons/guards/user-auth.guard'; 12 | 13 | @Controller('notifications') 14 | export class NotificationController { 15 | 16 | constructor(private notificationService: NotificationService) { 17 | } 18 | 19 | @Get('subscribers') 20 | @UseGuards(AuthGuard(), AdminAuthGuard) 21 | @Roles([Role.ADMIN]) 22 | getAllSubscribers() { 23 | return this.notificationService.getAllSubscribers(); 24 | } 25 | 26 | @Get('subscribers/subscriber-notifications') 27 | @UseGuards(AuthGuard(), UserAuthGuard) 28 | @Roles([Role.USER]) 29 | getSubscriberNotifications(@GetAuthenticatedUser() user: User) { 30 | if (user.subscriberId) { 31 | return this.notificationService.getSubscriberNotifications(user.subscriberId); 32 | } else { 33 | return null; 34 | } 35 | } 36 | 37 | @Get('subscribers/:id') 38 | @UseGuards(AuthGuard(), AcceptedAuthGuard) 39 | @Roles([Role.ADMIN, Role.USER]) 40 | getSubscriberById(@Param('id', ParseIntPipe) id: number) { 41 | return this.notificationService.getSubscriberById(id); 42 | } 43 | 44 | 45 | @Post('subscribers/new') 46 | @UseGuards(AuthGuard(), AcceptedAuthGuard) 47 | @Roles([Role.ADMIN, Role.USER]) 48 | newSubscriber(@GetAuthenticatedUser() user: User, @Body() subscriber: any) { 49 | return this.notificationService.newSubscriber(user, subscriber); 50 | } 51 | 52 | @Post('send-notification') 53 | @UseGuards(AuthGuard(), AdminAuthGuard) 54 | @Roles([Role.ADMIN]) 55 | sendNotification(@Body() notificationPayloadDto: NotificationPayloadDto) { 56 | return this.notificationService.sendNewNotification(notificationPayloadDto); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/modules/auth/repositories/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { User } from '../entities/user.entity'; 3 | import { BadRequestException, ForbiddenException, ConflictException, NotFoundException } from '@nestjs/common'; 4 | import { Role } from '../../../commons/enums/role.enum'; 5 | import { EmailLoginDto } from '../dto/email-login.dto'; 6 | import * as bcrypt from 'bcryptjs'; 7 | 8 | @EntityRepository(User) 9 | export class UserRepository extends Repository { 10 | async findByEmail(email: string): Promise { 11 | return await this.findOne({ email }); 12 | } 13 | 14 | async findByUsername(username: string): Promise { 15 | return await this.findOne({ username }); 16 | } 17 | 18 | async validateUserPassword(emailLoginDto: EmailLoginDto): Promise<{ email: string, user: User }> { 19 | const { email, password } = emailLoginDto; 20 | const user = await this.findByEmail(email); 21 | if (!user) { 22 | throw new NotFoundException('User does not exist in the database'); 23 | } 24 | // check if the user has account from social envoiroments like google and facebook. 25 | if (!user.password) { 26 | const errMessage = `You Cannot login from this gate, it's only for the main users, 27 | use the google or facebook gateways to login`; 28 | throw new ConflictException(errMessage, errMessage); 29 | } 30 | if ((await user.validatePassword(password))) { 31 | return { email, user }; 32 | } else { 33 | throw new BadRequestException('Your Password in incorrect, please enter another one'); 34 | } 35 | } 36 | 37 | async validateAdminPassword(emailLoginDto: EmailLoginDto) { 38 | const { email, password } = emailLoginDto; 39 | const user = await this.findByEmail(email); 40 | if (!user) { 41 | throw new NotFoundException('User does not exist in the database'); 42 | } 43 | const isAdmin = (): boolean => user.roles.some(role => role === Role.ADMIN); 44 | if (!isAdmin()) { 45 | throw new ForbiddenException('This Resource Is Forbidden'); 46 | } 47 | if (user && (await user.validatePassword(password))) { 48 | return { email, user }; 49 | } else { 50 | throw new BadRequestException('Your Password in incorrect, please enter another one'); 51 | } 52 | } 53 | 54 | async hashPassword(password, salt: string): Promise { 55 | return await bcrypt.hash(password, salt); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/modules/favorite/favorite.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Favorite } from './favorite.entity'; 4 | import { Repository } from 'typeorm'; 5 | import { Profile } from '../profile/profile.entity'; 6 | import { TrackService } from '../track/track.service'; 7 | import { Music } from '../music/music.entity'; 8 | import { Song } from '../song/song.entity'; 9 | 10 | 11 | @Injectable() 12 | export class FavoriteService { 13 | constructor(@InjectRepository(Favorite) private readonly favoriteRepository: Repository, 14 | private trackService: TrackService) { 15 | } 16 | 17 | async getUserFavoriteList(id: number, profile?: Profile): Promise { 18 | let favoriteList = null; 19 | if (id) { 20 | favoriteList = await this.favoriteRepository.findOne({ 21 | where: { 22 | id, 23 | }, 24 | }); 25 | } else if (profile) { 26 | favoriteList = await this.favoriteRepository.findOne({ profile }); 27 | } else { 28 | throw new NotFoundException('Favorite list does not found'); 29 | } 30 | return favoriteList; 31 | } 32 | 33 | async deleteFavoriteList(id: number): Promise { 34 | await this.clearFavoriteListContent(id); 35 | const result = await this.favoriteRepository.delete(id); 36 | if (result.affected === 0) { 37 | throw new NotFoundException('favorite list does not found'); 38 | } 39 | } 40 | 41 | async clearFavoriteListContent(id: number): Promise { 42 | const favorite = await this.getUserFavoriteList(id); 43 | for (let i = 0; i < favorite.tracks.length; i++) { 44 | await this.trackService.deleteTrack(favorite.tracks[i].id); 45 | } 46 | favorite.tracks = []; 47 | return await favorite.save(); 48 | } 49 | 50 | async removeTrackFromFavouriteList(favouriteId: number, trackId: number): Promise { 51 | const favorite = await this.getUserFavoriteList(favouriteId); 52 | for (let i = 0; i < favorite.tracks.length; i++) { 53 | if (trackId === favorite.tracks[i].id) { 54 | await this.trackService.deleteTrack( 55 | trackId, 56 | ); 57 | favorite.tracks.splice(i,1); 58 | break; 59 | } 60 | } 61 | return await favorite.save(); 62 | } 63 | 64 | async createFavoriteTrack(song: Song, music: Music, favoriteListId: number) { 65 | const favorite = await this.getUserFavoriteList(favoriteListId); 66 | const track = this.trackService.pushToFavoriteList(song, music, favorite); 67 | return track; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/modules/singer-album/singer-album.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, ParseIntPipe, 7 | Post, 8 | Put, 9 | UploadedFile, 10 | UseGuards, 11 | UseInterceptors, 12 | } from '@nestjs/common'; 13 | import { CreateAlbumDto } from '../../shared/dto/create-album.dto'; 14 | import { FileInterceptor } from '@nestjs/platform-express'; 15 | import { SongType } from '../../commons/enums/song-type.enum'; 16 | import { SongLanguage } from '../../commons/enums/song-language.enum'; 17 | import { SingerAlbumService } from './singer-album.service'; 18 | import { AuthGuard } from '@nestjs/passport'; 19 | import { AdminAuthGuard } from '../../commons/guards/admin-auth.guard'; 20 | import { Roles } from '../../commons/decorators/roles.decorator'; 21 | import { Role } from '../../commons/enums/role.enum'; 22 | 23 | @Controller('singers-albums') 24 | export class SingerAlbumController { 25 | 26 | constructor(private singerAlbumService: SingerAlbumService) { 27 | } 28 | 29 | @Get() 30 | getAllSingerAlbums() { 31 | return this.singerAlbumService.getAllSingerAlbums(); 32 | } 33 | 34 | @Get(':id') 35 | getSingerAlbum(@Param('id', ParseIntPipe) id: number) { 36 | return this.singerAlbumService.getSingerAlbumById(id); 37 | } 38 | 39 | @Post(':id/new-song') 40 | @UseGuards(AuthGuard(), AdminAuthGuard) 41 | @Roles([Role.ADMIN]) 42 | @UseInterceptors(FileInterceptor('source')) 43 | createNewSong(@Param('id', ParseIntPipe) id: number, 44 | @Body('name') name: string, 45 | @Body('description') description: string, 46 | @Body('artist') artist: string, 47 | @Body('type') type: SongType, 48 | @Body('language') language: SongLanguage, 49 | @UploadedFile() source: any, 50 | ) { 51 | return this.singerAlbumService.createNewSong(id, name, description, artist, type, language, source); 52 | } 53 | 54 | @Put(':id/update-album') 55 | @UseGuards(AuthGuard(), AdminAuthGuard) 56 | @Roles([Role.ADMIN]) 57 | updateAlbum(@Param('id', ParseIntPipe) id: number, @Body() createAlbumDto: CreateAlbumDto) { 58 | return this.singerAlbumService.updateSingerAlbum(id, createAlbumDto); 59 | } 60 | 61 | @Delete(':id/delete-album') 62 | @UseGuards(AuthGuard(), AdminAuthGuard) 63 | @Roles([Role.ADMIN]) 64 | deleteAlbum(@Param('id', ParseIntPipe) id: number) { 65 | return this.singerAlbumService.deleteSingerAlbum(id); 66 | } 67 | 68 | 69 | @Delete(':id/clear-singer-album') 70 | @UseGuards(AuthGuard(), AdminAuthGuard) 71 | @Roles([Role.ADMIN]) 72 | clearSingerAlbum(@Param('id', ParseIntPipe) id: number) { 73 | return this.singerAlbumService.clearSingerAlbum(id); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/modules/auth/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; 2 | import { Role } from '../../../commons/enums/role.enum'; 3 | import * as bcrypt from 'bcryptjs'; 4 | import { Profile } from '../../profile/profile.entity'; 5 | import { Playlist } from '../../playlist/playlist.entity'; 6 | import { Message } from '../../../shared/modules/chat/entities/message.entity'; 7 | import { UserJoinedRoom } from '../../../shared/modules/chat/entities/user-joined-room.entity'; 8 | import { Subscriber } from '../../notification/entities/subscriber.entity'; 9 | 10 | @Entity('users') 11 | @Unique(['username', 'email']) 12 | export class User extends BaseEntity { 13 | @PrimaryGeneratedColumn() 14 | id: number; 15 | 16 | @Column() 17 | username: string; 18 | 19 | @Column({ 20 | nullable: true, 21 | }) 22 | password: string; 23 | 24 | @Column() 25 | email: string; 26 | 27 | @Column({ 28 | nullable: true, 29 | }) 30 | salt: string; 31 | 32 | @Column({ 33 | type: 'enum', 34 | enum: Role, 35 | array: true, 36 | }) 37 | roles: Role[]; 38 | 39 | 40 | // new column 41 | @Column({ 42 | default: false, 43 | }) 44 | isEmailVerified: boolean; 45 | 46 | 47 | // new column 48 | // this column is related to the functionality of signIn with facebook 49 | @Column({ 50 | nullable: true, 51 | }) 52 | googleId: string; 53 | 54 | // new column 55 | // this column is related to the functionality of signIn with facebook 56 | @Column({ 57 | nullable: true, 58 | }) 59 | facebookId: string; 60 | 61 | 62 | 63 | async validatePassword(password: string): Promise { 64 | const hash = await bcrypt.hash(password, this.salt); 65 | return hash === this.password; 66 | } 67 | 68 | @OneToOne(type => Profile, profile => profile.user) 69 | @JoinColumn() 70 | profile: Profile; 71 | 72 | @OneToOne(type => Subscriber, subscriber => subscriber.user) 73 | @JoinColumn() 74 | subscriber: Subscriber; 75 | 76 | @OneToMany(type => Playlist, playlist => playlist.user, { 77 | eager: true, 78 | }) 79 | playlists: Playlist[]; 80 | 81 | @OneToMany(type => Message, message => message.user, { 82 | eager: true, 83 | }) 84 | messages: Message[]; 85 | 86 | @OneToMany(type => UserJoinedRoom, 87 | userJoinedRoom => userJoinedRoom.user, { 88 | eager: true, 89 | }) 90 | userJoinedRooms: UserJoinedRoom[]; 91 | 92 | // Foreign Key 93 | @Column() 94 | profileId: number; 95 | 96 | // Foreign Key 97 | @Column({ 98 | nullable: true, 99 | }) 100 | subscriberId: number; 101 | 102 | 103 | // this column related to socket io 104 | @Column({ 105 | nullable: true, 106 | }) 107 | clientId: string; 108 | } 109 | -------------------------------------------------------------------------------- /src/modules/musician-album/musician-album.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { DeleteResult, Repository } from 'typeorm'; 4 | import { AwsService } from '../../shared/modules/aws/aws.service'; 5 | import { CreateAlbumDto } from '../../shared/dto/create-album.dto'; 6 | import { MusicianAlbum } from './musician-album.entity'; 7 | import { MusicType } from '../../commons/enums/music-type.enum'; 8 | import { Music } from '../music/music.entity'; 9 | import { MusicService } from '../music/music.service'; 10 | 11 | @Injectable() 12 | export class MusicianAlbumService { 13 | 14 | constructor(@InjectRepository(MusicianAlbum) private musicianAlbumRepository: Repository, 15 | private awsService: AwsService, 16 | private musicService: MusicService) { 17 | } 18 | 19 | async getAllMusicianAlbums(): Promise { 20 | return await this.musicianAlbumRepository.find(); 21 | } 22 | 23 | async getMusicianAlbumById(id: number): Promise { 24 | const musicianAlbum = await this.musicianAlbumRepository.findOne({ 25 | where: { 26 | id, 27 | }, 28 | }); 29 | if (!musicianAlbum) { 30 | throw new NotFoundException(`Musician Album Album with id ${id} does not found`); 31 | } 32 | return musicianAlbum; 33 | } 34 | 35 | async createNewMusic(musicianAlbumId: number, name: string, 36 | description: string, 37 | artist: string, 38 | type: MusicType, 39 | source: any, 40 | ): Promise { 41 | const music = new Music(); 42 | const musicianAlbum = await this.getMusicianAlbumById(musicianAlbumId); 43 | music.name = name; 44 | music.description = description; 45 | music.artist = artist; 46 | music.type = type; 47 | music.tempImage = musicianAlbum.image; 48 | music.source = await this.awsService.fileUpload(source, 'musics'); 49 | music.musicianAlbum = musicianAlbum; 50 | const savedMusic = await music.save(); 51 | return savedMusic; 52 | } 53 | 54 | async updateMusicianAlbum(id: number, createAlbumDto: CreateAlbumDto): Promise { 55 | const musicianAlbum = await this.getMusicianAlbumById(id); 56 | const { name } = createAlbumDto; 57 | if (name) { 58 | musicianAlbum.name = name; 59 | } 60 | const savedMusicianAlbum = await musicianAlbum.save(); 61 | return savedMusicianAlbum; 62 | } 63 | 64 | async deleteMusicianAlbum(id: number): Promise{ 65 | const musicianAlbum = await this.getMusicianAlbumById(id); 66 | for (let i = 0; i < musicianAlbum.musics.length; i++) { 67 | await this.musicService.deleteMusic(musicianAlbum.musics[i].id); 68 | } 69 | const result = await this.musicianAlbumRepository.delete(id); 70 | if (result.affected === 0) { 71 | throw new NotFoundException(`Musician Album with id ${id} does not found`); 72 | } 73 | return result; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/modules/music/music.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, ParseArrayPipe, ParseIntPipe, 7 | Post, 8 | Put, 9 | Query, 10 | UploadedFile, 11 | UseGuards, 12 | UseInterceptors, 13 | } from '@nestjs/common'; 14 | import { MusicType } from '../../commons/enums/music-type.enum'; 15 | import { MusicService } from './music.service'; 16 | import { FileInterceptor } from '@nestjs/platform-express'; 17 | import { AuthGuard } from '@nestjs/passport'; 18 | import { UserAuthGuard } from '../../commons/guards/user-auth.guard'; 19 | import { Roles } from '../../commons/decorators/roles.decorator'; 20 | import { Role } from '../../commons/enums/role.enum'; 21 | import { AdminAuthGuard } from '../../commons/guards/admin-auth.guard'; 22 | 23 | @Controller('musics') 24 | export class MusicController { 25 | constructor(private musicService: MusicService) { 26 | 27 | } 28 | 29 | @Get() 30 | getAllMusics() { 31 | return this.musicService.getAllMusics(); 32 | } 33 | 34 | @Get('limited') 35 | getLimitedMusics(@Query('limit') limit: number) { 36 | return this.musicService.getLimitedMusics(limit); 37 | } 38 | 39 | @Get('filtered') 40 | getFilteredMusics(@Query('limit') limit: number, 41 | @Query('type') type: MusicType, 42 | @Query('rate') rate: number) { 43 | return this.musicService.getFilteredMusics(limit, type, rate); 44 | } 45 | 46 | 47 | 48 | @Get(':id') 49 | getMusicById(@Param('id', ParseIntPipe) id: number) { 50 | return this.musicService.getMusicById(id); 51 | } 52 | 53 | 54 | 55 | 56 | @Put(':id/update-music') 57 | @UseGuards(AuthGuard(), AdminAuthGuard) 58 | @Roles([Role.ADMIN]) 59 | @UseInterceptors(FileInterceptor('source')) 60 | updateMusic(@Param('id', ParseIntPipe) id: number, 61 | @Body('name') name: string, 62 | @Body('description') description: string, 63 | @Body('artist') artist: string, 64 | @Body('type') type: MusicType, 65 | @UploadedFile() source: any) { 66 | return this.musicService.updateMusic(id, name, description, artist, type, source); 67 | } 68 | 69 | @Delete(':id/delete-music') 70 | @UseGuards(AuthGuard(), AdminAuthGuard) 71 | @Roles([Role.ADMIN]) 72 | delete(@Param('id', ParseIntPipe) id: number) { 73 | return this.musicService.deleteMusic(id); 74 | } 75 | 76 | @Post(':musicId/add-to-playlist/:playlistId') 77 | @UseGuards(AuthGuard(), UserAuthGuard) 78 | @Roles([Role.USER]) 79 | addToPlaylist(@Param('musicId', ParseIntPipe) musicId: number, 80 | @Param('playlistId', ParseIntPipe) playlistId: number) { 81 | return this.musicService.pushToPlaylist(musicId, playlistId); 82 | } 83 | 84 | @Post(':musicId/save-to-favorite-list/:favoriteId') 85 | @UseGuards(AuthGuard(), UserAuthGuard) 86 | @Roles([Role.USER]) 87 | saveToFavoriteList(@Param('musicId', ParseIntPipe) musicId: number, 88 | @Param('favoriteId', ParseIntPipe) favoriteId: number) { 89 | return this.musicService.pushToFavoriteList(musicId, favoriteId); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/shared/modules/chat/chat.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Room } from './entities/room.entity'; 4 | import { Repository } from 'typeorm'; 5 | import { Message } from './entities/message.entity'; 6 | import { UserJoinedRoom } from './entities/user-joined-room.entity'; 7 | import { User } from '../../../modules/auth/entities/user.entity'; 8 | import { RoomDto } from './dto/room.dto'; 9 | 10 | 11 | @Injectable() 12 | export class ChatService { 13 | constructor( 14 | @InjectRepository(Message) private messageRepository: Repository, 15 | @InjectRepository(Room) private roomRepository: Repository, 16 | @InjectRepository(UserJoinedRoom) private userJoinedRoomRepository: Repository) { 17 | 18 | } 19 | 20 | 21 | async getAllRooms() { 22 | return this.roomRepository.find(); 23 | } 24 | 25 | async getRoomById(id: number) { 26 | const room = await this.roomRepository.findOne({ 27 | where: { id }, 28 | }); 29 | if (!room) { 30 | throw new NotFoundException(`Room with id ${id} does not found`); 31 | } 32 | return room; 33 | } 34 | 35 | async deleteUserMessages(user: User) { 36 | for (let i = 0; i < user.messages.length; i++) { 37 | await this.messageRepository.delete(user.messages[i].id); 38 | } 39 | } 40 | 41 | async deleteUserJoinedRooms(user: User) { 42 | for (let i = 0; i < user.userJoinedRooms.length; i++) { 43 | await this.userJoinedRoomRepository.delete(user.userJoinedRooms[i].id); 44 | } 45 | } 46 | 47 | 48 | async getUserRooms(user: User) { 49 | const query = this.roomRepository.createQueryBuilder('room'); 50 | query.select() 51 | .where('room.createdBy LIKE :username', { username: user.username }); 52 | const rooms = await query.getMany(); 53 | return rooms; 54 | } 55 | 56 | async createNewRoom(user: User, 57 | createRoomDto: RoomDto) { 58 | const { name } = createRoomDto; 59 | const room = new Room(); 60 | room.name = name; 61 | room.messages = []; 62 | room.userJoinedRooms = []; 63 | room.createdBy = user.username; 64 | return await room.save(); 65 | } 66 | 67 | async updateRoom(id: number, 68 | updateRoomDto: RoomDto) { 69 | const { name } = updateRoomDto; 70 | const room = await this.getRoomById(id); 71 | if (name) { 72 | room.name = name; 73 | } 74 | return await room.save(); 75 | } 76 | 77 | async deleteRoom(id: number) { 78 | const room = await this.getRoomById(id); 79 | for (let i = 0; i < room.messages.length; i++) { 80 | await this.messageRepository.delete(room.messages[i].id); 81 | } 82 | for (let i = 0; i < room.userJoinedRooms.length; i++) { 83 | await this.userJoinedRoomRepository.delete(room.userJoinedRooms[i].id); 84 | } 85 | const result = await this.roomRepository.delete(id); 86 | if (result.affected === 0) { 87 | throw new NotFoundException(`Room with id ${id} does not found`); 88 | } 89 | return true; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/modules/playlist/playlist.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { Playlist } from './playlist.entity'; 3 | import { User } from '../auth/entities/user.entity'; 4 | import { PlaylistRepository } from './playlist.repository'; 5 | import { PlaylistDto } from './dto/playlist.dto'; 6 | import { DeleteResult } from 'typeorm'; 7 | import { Song } from '../song/song.entity'; 8 | import { Music } from '../music/music.entity'; 9 | import { TrackService } from '../track/track.service'; 10 | 11 | @Injectable() 12 | export class PlaylistService { 13 | 14 | constructor(private playlistRepository: PlaylistRepository, 15 | private trackService: TrackService) { 16 | } 17 | 18 | async getUserPlaylists(user: User): Promise { 19 | return await this.playlistRepository.getUserPlaylists(user.id); 20 | } 21 | 22 | async getPlaylistById(id: number): Promise { 23 | const playlist = await this.playlistRepository.findOne({ 24 | where: { 25 | id, 26 | }, 27 | }); 28 | if (!playlist) { 29 | throw new NotFoundException(`Playlist with Id ${id} Does not found`); 30 | } 31 | return playlist; 32 | } 33 | 34 | async newPlaylist(user: User, playlistDto: PlaylistDto): Promise { 35 | const { name } = playlistDto; 36 | const playlist = new Playlist(); 37 | playlist.name = name; 38 | playlist.user = user; // this will create a foreign key called userId 39 | playlist.tracks = []; 40 | return await playlist.save(); 41 | } 42 | 43 | async updatePlaylist(id: number, playlistDto: PlaylistDto): Promise { 44 | const { name } = playlistDto; 45 | const playlist = await this.getPlaylistById(id); 46 | if (name) { 47 | playlist.name = name; 48 | } 49 | return await playlist.save(); 50 | } 51 | 52 | async deletePlaylist(id: number): Promise { 53 | await this.clearPlaylistContent(id); 54 | const result = await this.playlistRepository.delete(id); 55 | if (result.affected === 0) { 56 | throw new NotFoundException(`Playlist with Id ${id} Does not found`); 57 | } 58 | return result; 59 | } 60 | 61 | async clearPlaylistContent(id: number): Promise{ 62 | const playlist = await this.getPlaylistById(id); 63 | for (let i = 0; i < playlist.tracks.length; i++) { 64 | await this.trackService.deleteTrack(playlist.tracks[i].id); 65 | } 66 | playlist.tracks = []; 67 | return await playlist.save(); 68 | } 69 | 70 | async removeTrackFromPlaylist(playlistId: number, trackId: number): Promise{ 71 | const playlist = await this.getPlaylistById(playlistId); 72 | for (let i = 0; i < playlist.tracks.length; i++) { 73 | if(playlist.tracks[i].id === trackId){ 74 | await this.trackService.deleteTrack(trackId); 75 | playlist.tracks.splice(i, 1); 76 | break; 77 | } 78 | } 79 | return await playlist.save(); 80 | } 81 | async createPlaylistTrack(song: Song, music: Music, playlistId: number) { 82 | const playlist = await this.getPlaylistById(playlistId); 83 | const track = this.trackService.pushToPlaylist(song, music, playlist); 84 | return track; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/modules/song/song.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, 7 | Post, 8 | Put, 9 | Query, 10 | UploadedFile, 11 | UseGuards, 12 | UseInterceptors, 13 | ParseIntPipe, 14 | } from '@nestjs/common'; 15 | import { SongLanguage } from '../../commons/enums/song-language.enum'; 16 | import { SongType } from '../../commons/enums/song-type.enum'; 17 | import { SongService } from './song.service'; 18 | import { FileInterceptor } from '@nestjs/platform-express'; 19 | import { AuthGuard } from '@nestjs/passport'; 20 | import { UserAuthGuard } from '../../commons/guards/user-auth.guard'; 21 | import { Roles } from '../../commons/decorators/roles.decorator'; 22 | import { Role } from '../../commons/enums/role.enum'; 23 | import { AdminAuthGuard } from '../../commons/guards/admin-auth.guard'; 24 | 25 | @Controller('songs') 26 | export class SongController { 27 | 28 | constructor(private songService: SongService) { 29 | 30 | } 31 | 32 | @Get() 33 | getAllSongs() { 34 | return this.songService.getAllSongs(); 35 | } 36 | 37 | @Get('limited') 38 | getLimitedSongs(@Query('limit') limit: number) { 39 | return this.songService.getLimitedSongs(limit); 40 | } 41 | 42 | @Get('filtered') 43 | getFilteredSongs(@Query('limit') limit: number, 44 | @Query('type') type: SongType, 45 | @Query('language') language: SongLanguage, 46 | @Query('rate') rate: number) { 47 | return this.songService.getFilteredSong(limit, type, language, rate); 48 | } 49 | 50 | @Get(':id') 51 | getSongById(@Param('id', ParseIntPipe) id: number) { 52 | return this.songService.getSongById(id); 53 | } 54 | 55 | 56 | @Put(':id/update-song') 57 | @UseGuards(AuthGuard(), AdminAuthGuard) 58 | @Roles([Role.ADMIN]) 59 | @UseInterceptors(FileInterceptor('source')) 60 | updateSong(@Param('id', ParseIntPipe) id: number, 61 | @Body('name') name: string, 62 | @Body('description') description: string, 63 | @Body('artist') artist: string, 64 | @Body('type') type: SongType, 65 | @Body('language') language: SongLanguage, 66 | @UploadedFile() source: any, 67 | ) { 68 | return this.songService.updateSong(id, name, description, artist, type, language, source); 69 | } 70 | 71 | @Delete(':id/delete-song') 72 | @UseGuards(AuthGuard(), AdminAuthGuard) 73 | @Roles([Role.ADMIN]) 74 | delete(@Param('id', ParseIntPipe) id: number) { 75 | return this.songService.deleteSong(id); 76 | } 77 | 78 | @Post(':songId/add-to-playlist/:playlistId') 79 | @UseGuards(AuthGuard(), UserAuthGuard) 80 | @Roles([Role.USER]) 81 | addToPlaylist(@Param('songId', ParseIntPipe) songId: number, 82 | @Param('playlistId', ParseIntPipe) playlistId: number) { 83 | return this.songService.pushToPlaylist(songId, playlistId); 84 | } 85 | 86 | @Post(':songId/save-to-favorite-list/:favoriteId') 87 | @UseGuards(AuthGuard(), UserAuthGuard) 88 | @Roles([Role.USER]) 89 | saveToFavoriteList(@Param('songId', ParseIntPipe) songId: number, 90 | @Param('favoriteId', ParseIntPipe) favoriteId: number) { 91 | return this.songService.pushToFavoriteList(songId, favoriteId); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/modules/profile/profile.service.ts: -------------------------------------------------------------------------------- 1 | import { ConflictException, Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Profile } from './profile.entity'; 4 | import { Repository } from 'typeorm'; 5 | import { User } from '../auth/entities/user.entity'; 6 | import { CreateProfileDto } from '../auth/dto/create-profile.dto'; 7 | import { AwsService } from '../../shared/modules/aws/aws.service'; 8 | 9 | 10 | @Injectable() 11 | export class ProfileService { 12 | constructor(@InjectRepository(Profile) private profileRepository: Repository, 13 | private awsService: AwsService) { 14 | } 15 | 16 | async getProfileData(user: User): Promise { 17 | const profile = await this.profileRepository.findOne({ 18 | where: { 19 | id: user.profileId, 20 | }, 21 | }); 22 | if (!profile) { 23 | throw new NotFoundException('profile does not found'); 24 | } 25 | return profile; 26 | } 27 | 28 | async deleteProfile(id: number): Promise { 29 | const result = await this.profileRepository.delete(id); 30 | if (result.affected === 0) { 31 | throw new NotFoundException('profile does not found'); 32 | } 33 | } 34 | 35 | async editProfile(user: User, createProfileDto: CreateProfileDto): Promise { 36 | const profile = await this.getProfileData(user); 37 | const { firstName, lastName, phone, age, address, city, country, gender } 38 | = createProfileDto; 39 | if (firstName) { 40 | console.log(firstName); 41 | profile.firstName = firstName; 42 | } 43 | if (lastName) { 44 | profile.lastName = lastName; 45 | } 46 | if (phone) { 47 | profile.phone = phone; 48 | } 49 | if (age) { 50 | profile.age = age; 51 | } 52 | if (address) { 53 | profile.address = address; 54 | } 55 | if (city) { 56 | profile.city = city; 57 | } 58 | if (country) { 59 | profile.country = country; 60 | } 61 | if (gender) { 62 | profile.gender = gender; 63 | } 64 | const savedProfile = await profile.save(); 65 | return savedProfile; 66 | } 67 | 68 | async setProfileImage(user: User, image: any): Promise { 69 | const profile = await this.getProfileData(user); 70 | if (image) { 71 | profile.image = await this.awsService.fileUpload(image, 'profile-images'); 72 | } 73 | const savedProfile = await profile.save(); 74 | return savedProfile; 75 | } 76 | 77 | async changeProfileImage(user: User, image: any): Promise { 78 | const profile = await this.getProfileData(user); 79 | if (image) { 80 | await this.awsService.fileDelete(profile.image); 81 | profile.image = await this.awsService.fileUpload(image, 'profile-images'); 82 | } 83 | const savedProfile = await profile.save(); 84 | return savedProfile; 85 | } 86 | 87 | async deleteProfileImage(user: User): Promise { 88 | const profile = await this.getProfileData(user); 89 | if (!profile.image) { 90 | throw new ConflictException('the profile is already set to null!'); 91 | } 92 | await this.awsService.fileDelete(profile.image); 93 | profile.image = null; 94 | const savedProfile = await profile.save(); 95 | return savedProfile; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/modules/singer-album/singer-album.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { SingerAlbum } from './singer-album.entity'; 4 | import { DeleteResult, Repository } from 'typeorm'; 5 | import { Song } from '../song/song.entity'; 6 | import { SongType } from '../../commons/enums/song-type.enum'; 7 | import { SongLanguage } from '../../commons/enums/song-language.enum'; 8 | import { AwsService } from '../../shared/modules/aws/aws.service'; 9 | import { CreateAlbumDto } from '../../shared/dto/create-album.dto'; 10 | import { SongService } from '../song/song.service'; 11 | 12 | @Injectable() 13 | export class SingerAlbumService { 14 | 15 | constructor(@InjectRepository(SingerAlbum) private singerAlbumRepository: Repository, 16 | private awsService: AwsService, 17 | private songService: SongService) { 18 | } 19 | 20 | async getAllSingerAlbums(): Promise { 21 | return await this.singerAlbumRepository.find(); 22 | } 23 | 24 | async getSingerAlbumById(id: number): Promise { 25 | const singerAlbum = await this.singerAlbumRepository.findOne({ 26 | where: { 27 | id, 28 | }, 29 | }); 30 | if (!singerAlbum) { 31 | throw new NotFoundException(`Singer Album with id ${id} does not found`); 32 | } 33 | return singerAlbum; 34 | } 35 | 36 | async createNewSong(singerAlbumId: number, name: string, 37 | description: string, 38 | artist: string, 39 | type: SongType, 40 | language: SongLanguage, 41 | source: any, 42 | ): Promise { 43 | const song = new Song(); 44 | const singerAlbum = await this.getSingerAlbumById(singerAlbumId); 45 | song.name = name; 46 | song.description = description; 47 | song.artist = artist; 48 | song.type = type; 49 | song.language = language; 50 | song.tempImage = singerAlbum.image; 51 | song.source = await this.awsService.fileUpload(source, 'songs'); 52 | song.singerAlbum = singerAlbum; 53 | const savedSong = await song.save(); 54 | return savedSong; 55 | } 56 | 57 | async updateSingerAlbum(id: number, createAlbumDto: CreateAlbumDto): Promise { 58 | const singerAlbum = await this.getSingerAlbumById(id); 59 | const { name } = createAlbumDto; 60 | if (name) { 61 | singerAlbum.name = name; 62 | } 63 | const savedSingerAlbum = await singerAlbum.save(); 64 | return savedSingerAlbum; 65 | } 66 | 67 | async deleteSingerAlbum(id: number): Promise{ 68 | const singerAlbum = await this.getSingerAlbumById(id); 69 | for (let i = 0; i < singerAlbum.songs.length; i++) { 70 | await this.songService.deleteSong(singerAlbum.songs[i].id); 71 | } 72 | const result = await this.singerAlbumRepository.delete(id); 73 | if (result.affected === 0) { 74 | throw new NotFoundException(`Singer Album with id ${id} does not found`); 75 | } 76 | return result; 77 | } 78 | 79 | async clearSingerAlbum(id: number): Promise{ 80 | const singerAlbum = await this.getSingerAlbumById(id); 81 | for (let i = 0; i < singerAlbum.songs.length; i++) { 82 | await this.songService.deleteSong(singerAlbum.songs[i].id); 83 | } 84 | singerAlbum.songs = []; 85 | return await singerAlbum.save(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /src/modules/singer/singer.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, ParseIntPipe, 7 | Post, 8 | Put, 9 | Query, 10 | UploadedFile, 11 | UseGuards, 12 | UseInterceptors, 13 | } from '@nestjs/common'; 14 | import { ArtistType } from '../../commons/enums/artist-type.enum'; 15 | import { Gender } from '../../commons/enums/gender.enum'; 16 | import { CreateAlbumDto } from '../../shared/dto/create-album.dto'; 17 | import { SingerService } from './singer.service'; 18 | import { FileInterceptor } from '@nestjs/platform-express'; 19 | import { AuthGuard } from '@nestjs/passport'; 20 | import { AdminAuthGuard } from '../../commons/guards/admin-auth.guard'; 21 | import { Roles } from '../../commons/decorators/roles.decorator'; 22 | import { Role } from '../../commons/enums/role.enum'; 23 | @Controller('singers') 24 | export class SingerController { 25 | 26 | constructor(private singerService: SingerService) { 27 | } 28 | 29 | //localhost:3000/singers 30 | @Get() 31 | getAllSingers() { 32 | return this.singerService.getAllSingers(); 33 | } 34 | 35 | @Get('filtered') 36 | getFilteredSingers(@Query('limit') limit: number, 37 | @Query('type') type: ArtistType, 38 | @Query('nationality') nationality: string, 39 | @Query('gender') gender: Gender) { 40 | 41 | return this.singerService.getFilteredSingers(limit, nationality, type, gender); 42 | } 43 | 44 | @Get('limited') 45 | getLimitedSingers(@Query('limit') limit: number) { 46 | return this.singerService.getLimitedSingers(limit); 47 | } 48 | 49 | 50 | //localhost:3000/singers 51 | @Post() 52 | @UseInterceptors(FileInterceptor('image')) 53 | @UseGuards(AuthGuard(), AdminAuthGuard) 54 | @Roles([Role.ADMIN]) 55 | createNewSinger(@Body('name') name: string, 56 | @Body('info') info: string, 57 | @Body('gender') gender: Gender, 58 | @Body('nationality') nationality: string, 59 | @Body('type') type: ArtistType, 60 | @UploadedFile() image: any) { 61 | return this.singerService.createNewSinger(name, info, gender, type, nationality, image); 62 | } 63 | 64 | //localhost:3000/singers/:id 65 | @Get(':id') 66 | getSingerById(@Param('id', ParseIntPipe) id: number) { 67 | return this.singerService.getSingerById(id); 68 | } 69 | 70 | @Post(':id/new-album') 71 | @UseGuards(AuthGuard(), AdminAuthGuard) 72 | @Roles([Role.ADMIN]) 73 | createNewAlbum(@Param('id', ParseIntPipe) id: number, 74 | @Body() createAlbumDto: CreateAlbumDto) { 75 | 76 | return this.singerService.createNewAlbum(id, createAlbumDto); 77 | } 78 | 79 | @Put(':id/update-singer') 80 | @UseGuards(AuthGuard(), AdminAuthGuard) 81 | @Roles([Role.ADMIN]) 82 | @UseInterceptors(FileInterceptor('image')) 83 | updateSinger(@Param('id', ParseIntPipe) id: number, 84 | @Body('name') name: string, 85 | @Body('info') info: string, 86 | @Body('gender') gender: Gender, 87 | @Body('nationality') nationality: string, 88 | @Body('type') type: ArtistType, 89 | @UploadedFile() image: any) { 90 | return this.singerService.updateSinger(id, name, info, gender, nationality, type, image); 91 | } 92 | 93 | @Delete(':id/delete-singer') 94 | @UseGuards(AuthGuard(), AdminAuthGuard) 95 | @Roles([Role.ADMIN]) 96 | deleteSinger(@Param('id', ParseIntPipe) id: number) { 97 | return this.singerService.deleteSinger(id); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "music-land-api", 3 | "version": "0.0.1", 4 | "description": "This is the official API of Music Land Application", 5 | "author": "Mohammad Qaderi", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main.js", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@crowdlinker/nestjs-mailer": "0.0.3", 25 | "@nest-modules/mailer": "^1.1.3", 26 | "@nestjs/common": "^7.0.0", 27 | "@nestjs/core": "^7.0.0", 28 | "@nestjs/jwt": "^7.0.0", 29 | "@nestjs/passport": "^7.0.0", 30 | "@nestjs/platform-express": "^7.0.0", 31 | "@nestjs/platform-fastify": "^7.2.0", 32 | "@nestjs/platform-socket.io": "^7.2.0", 33 | "@nestjs/swagger": "^4.5.12", 34 | "@nestjs/typeorm": "^7.0.0", 35 | "@nestjs/websockets": "^7.2.0", 36 | "@types/passport-local": "^1.0.33", 37 | "aws-sdk": "^2.698.0", 38 | "bcryptjs": "^2.4.3", 39 | "class-transformer": "^0.2.3", 40 | "class-validator": "^0.12.1", 41 | "csurf": "^1.11.0", 42 | "express-rate-limit": "^5.1.3", 43 | "fastify-compress": "^2.0.1", 44 | "helmet": "^3.23.1", 45 | "jsonwebtoken": "^8.5.1", 46 | "multer": "^1.4.2", 47 | "nodemailer": "^6.4.6", 48 | "passport": "^0.4.1", 49 | "passport-facebook": "^3.0.0", 50 | "passport-facebook-token": "^4.0.0", 51 | "passport-google-oauth20": "^2.0.0", 52 | "passport-jwt": "^4.0.0", 53 | "passport-local": "^1.0.0", 54 | "pg": "^8.0.2", 55 | "reflect-metadata": "^0.1.13", 56 | "rimraf": "^3.0.2", 57 | "rxjs": "^6.5.4", 58 | "swagger-ui-express": "^4.1.4", 59 | "typeorm": "^0.2.24", 60 | "uuid": "^8.2.0", 61 | "web-push": "^3.4.4" 62 | }, 63 | "devDependencies": { 64 | "@nestjs/cli": "^7.0.0", 65 | "@nestjs/schematics": "^7.0.0", 66 | "@nestjs/testing": "^7.0.0", 67 | "@types/express": "^4.17.3", 68 | "@types/jest": "25.1.4", 69 | "@types/node": "^13.9.1", 70 | "@types/passport-facebook-token": "^0.4.34", 71 | "@types/socket.io": "^2.1.8", 72 | "@types/supertest": "^2.0.8", 73 | "@typescript-eslint/eslint-plugin": "^2.23.0", 74 | "@typescript-eslint/parser": "^2.23.0", 75 | "eslint": "^6.8.0", 76 | "eslint-config-prettier": "^6.10.0", 77 | "eslint-plugin-import": "^2.20.1", 78 | "jest": "^25.1.0", 79 | "prettier": "^1.19.1", 80 | "start-server-webpack-plugin": "^2.2.5", 81 | "supertest": "^4.0.2", 82 | "ts-jest": "25.2.1", 83 | "ts-loader": "^6.2.1", 84 | "ts-node": "^8.6.2", 85 | "tsconfig-paths": "^3.9.0", 86 | "tslint": "^6.1.2", 87 | "typescript": "^3.7.4", 88 | "webpack-node-externals": "^1.7.2" 89 | }, 90 | "jest": { 91 | "moduleFileExtensions": [ 92 | "js", 93 | "json", 94 | "ts" 95 | ], 96 | "rootDir": "src", 97 | "testRegex": ".spec.ts$", 98 | "transform": { 99 | "^.+\\.(t|j)s$": "ts-jest" 100 | }, 101 | "coverageDirectory": "../coverage", 102 | "testEnvironment": "node" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/modules/musician/musician.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, ParseIntPipe, 7 | Post, 8 | Put, 9 | Query, 10 | UploadedFile, 11 | UseGuards, 12 | UseInterceptors, 13 | } from '@nestjs/common'; 14 | import { ArtistType } from '../../commons/enums/artist-type.enum'; 15 | import { Gender } from '../../commons/enums/gender.enum'; 16 | import { CreateAlbumDto } from '../../shared/dto/create-album.dto'; 17 | import { MusicianService } from './musician.service'; 18 | import { FileInterceptor } from '@nestjs/platform-express'; 19 | import { AuthGuard } from '@nestjs/passport'; 20 | import { AdminAuthGuard } from '../../commons/guards/admin-auth.guard'; 21 | import { Roles } from '../../commons/decorators/roles.decorator'; 22 | import { Role } from '../../commons/enums/role.enum'; 23 | 24 | 25 | @Controller('musicians') 26 | export class MusicianController { 27 | constructor(private musicianService: MusicianService) { 28 | 29 | } 30 | 31 | //localhost:3000/musicians 32 | @Get() 33 | getAllMusicians() { 34 | return this.musicianService.getAllMusicians(); 35 | } 36 | 37 | @Get('filtered') 38 | getFilteredMusicians(@Query('limit') limit: number, 39 | @Query('type') type: ArtistType, 40 | @Query('nationality') nationality: string, 41 | @Query('gender') gender: Gender) { 42 | return this.musicianService.getFilteredMusicians(limit, nationality, type, gender); 43 | } 44 | 45 | @Get('limited') 46 | getLimitedMusicians(@Query('limit') limit: number) { 47 | return this.musicianService.getLimitedMusicians(limit); 48 | } 49 | 50 | 51 | //localhost:3000/musicians 52 | @Post() 53 | @UseGuards(AuthGuard(), AdminAuthGuard) 54 | @Roles([Role.ADMIN]) 55 | @UseInterceptors(FileInterceptor('image')) 56 | createNewMusician(@Body('name') name: string, 57 | @Body('info') info: string, 58 | @Body('gender') gender: Gender, 59 | @Body('nationality') nationality: string, 60 | @Body('type') type: ArtistType, 61 | @UploadedFile() image: any) { 62 | return this.musicianService.createNewMusician(name, info, gender, type, nationality, image); 63 | } 64 | 65 | //localhost:3000/musicians/:id 66 | @Get(':id') 67 | getMusicianById(@Param('id', ParseIntPipe) id: number) { 68 | return this.musicianService.getMusicianById(id); 69 | } 70 | 71 | @Post(':id/new-album') 72 | @UseGuards(AuthGuard(), AdminAuthGuard) 73 | @Roles([Role.ADMIN]) 74 | createNewAlbum(@Param('id', ParseIntPipe) id: number, 75 | @Body() createAlbumDto: CreateAlbumDto) { 76 | 77 | return this.musicianService.createNewAlbum(id, createAlbumDto); 78 | } 79 | 80 | @Put(':id/update-musician') 81 | @UseGuards(AuthGuard(), AdminAuthGuard) 82 | @Roles([Role.ADMIN]) 83 | @UseInterceptors(FileInterceptor('image')) 84 | updateMusician(@Param('id', ParseIntPipe) id: number, 85 | @Body('name') name: string, 86 | @Body('info') info: string, 87 | @Body('gender') gender: Gender, 88 | @Body('nationality') nationality: string, 89 | @Body('type') type: ArtistType, 90 | @UploadedFile() image: any) { 91 | return this.musicianService.updateMusician(id, name, info, gender, nationality, type, image); 92 | } 93 | 94 | @Delete(':id/delete-musician') 95 | @UseGuards(AuthGuard(), AdminAuthGuard) 96 | @Roles([Role.ADMIN]) 97 | deleteMusician(@Param('id', ParseIntPipe) id: number) { 98 | return this.musicianService.deleteMusician(id); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/modules/music/music.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { MusicRepository } from './music.repository'; 4 | import { Music } from './music.entity'; 5 | import { MusicType } from '../../commons/enums/music-type.enum'; 6 | import { DeleteResult } from 'typeorm'; 7 | import { AwsService } from '../../shared/modules/aws/aws.service'; 8 | import { FavoriteService } from '../favorite/favorite.service'; 9 | import { Track } from '../track/track.entity'; 10 | import { PlaylistService } from '../playlist/playlist.service'; 11 | import { TrackService } from '../track/track.service'; 12 | 13 | @Injectable() 14 | export class MusicService { 15 | constructor(@InjectRepository(MusicRepository) private musicRepository: MusicRepository, 16 | private awsService: AwsService, 17 | private favService: FavoriteService, 18 | private playlistService: PlaylistService, 19 | private trackService: TrackService) { 20 | } 21 | 22 | async getAllMusics(): Promise { 23 | return await this.musicRepository.find(); 24 | } 25 | 26 | async getMusicById(id: number): Promise { 27 | const music = await this.musicRepository.findOne({ 28 | where: { 29 | id, 30 | }, 31 | }); 32 | if (!music) { 33 | throw new NotFoundException(`Music with id ${id} does not found`); 34 | } 35 | return music; 36 | } 37 | 38 | async getFilteredMusics(limit: number, 39 | type: MusicType, rate: number): Promise { 40 | return await this.musicRepository.getFilteredMusics(limit, type, rate); 41 | } 42 | 43 | 44 | async getLimitedMusics(limit: number): Promise { 45 | return await this.musicRepository.getLimitedMusics(limit); 46 | } 47 | 48 | async updateMusic(id: number, name: string, description: string, 49 | artist: string, type: MusicType, source: any): Promise { 50 | const music = await this.getMusicById(id); 51 | if (name) { 52 | music.name = name; 53 | } 54 | if (description) { 55 | music.description = description; 56 | } 57 | if (artist) { 58 | music.artist = artist; 59 | } 60 | if (type) { 61 | music.type = type; 62 | } 63 | 64 | if (source) { 65 | await this.awsService.fileDelete(music.source); 66 | music.source = await this.awsService.fileUpload(source, 'musics'); 67 | } 68 | const updatedMusic = await music.save(); 69 | return updatedMusic; 70 | } 71 | 72 | async deleteMusic(id: number): Promise { 73 | const music = await this.getMusicById(id); 74 | for (let i = 0; i < music.tracks.length; i++) { 75 | await this.trackService.deleteTrack(music.tracks[i].id); 76 | } 77 | if (music.source) { 78 | await this.awsService.fileDelete(music.source); 79 | } 80 | const result = await this.musicRepository.delete(id); 81 | if (result.affected === 0) { 82 | throw new NotFoundException(`Music with id ${id} does not found`); 83 | } 84 | return result; 85 | } 86 | 87 | async pushToFavoriteList(musicId: number, favoriteListId: number): Promise { 88 | const music = await this.getMusicById(musicId); 89 | const track = await this.favService.createFavoriteTrack(null, music, favoriteListId); 90 | return track; 91 | } 92 | 93 | 94 | async pushToPlaylist(musicId: number, playlistId: number): Promise { 95 | const music = await this.getMusicById(musicId); 96 | const track = await this.playlistService.createPlaylistTrack(null, music, playlistId); 97 | return track; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/modules/song/song.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { SongRepository } from './song.repository'; 4 | import { Song } from './song.entity'; 5 | import { SongType } from '../../commons/enums/song-type.enum'; 6 | import { SongLanguage } from '../../commons/enums/song-language.enum'; 7 | import { AwsService } from '../../shared/modules/aws/aws.service'; 8 | import { DeleteResult } from 'typeorm'; 9 | import { FavoriteService } from '../favorite/favorite.service'; 10 | import { Track } from '../track/track.entity'; 11 | import { PlaylistService } from '../playlist/playlist.service'; 12 | import { TrackService } from '../track/track.service'; 13 | 14 | @Injectable() 15 | export class SongService { 16 | constructor(@InjectRepository(SongRepository) private songRepository: SongRepository, 17 | private awsService: AwsService, 18 | private favService: FavoriteService, 19 | private playlistService: PlaylistService, 20 | private trackService: TrackService) { 21 | } 22 | 23 | async getAllSongs(): Promise { 24 | return await this.songRepository.find(); 25 | } 26 | 27 | async getSongById(id: number): Promise { 28 | const song = await this.songRepository.findOne({ 29 | where: { 30 | id, 31 | }, 32 | }); 33 | if (!song) { 34 | throw new NotFoundException(`Song with id ${id} does not found`); 35 | } 36 | return song; 37 | } 38 | 39 | async getFilteredSong(limit: number, 40 | type: SongType, language: SongLanguage, rate: number): Promise { 41 | return await this.songRepository.getFilteredSongs(limit, type, language, rate); 42 | } 43 | 44 | async getLimitedSongs(limit: number): Promise { 45 | return await this.songRepository.getLimitedSongs(limit); 46 | } 47 | 48 | async updateSong(id: number, name: string, description: string, 49 | artist: string, type: SongType, language: SongLanguage, source: any): Promise { 50 | const song = await this.getSongById(id); 51 | if (name) { 52 | song.name = name; 53 | } 54 | if (description) { 55 | song.description = description; 56 | } 57 | if (artist) { 58 | song.artist = artist; 59 | } 60 | if (type) { 61 | song.type = type; 62 | } 63 | if (language) { 64 | song.language = language; 65 | } 66 | if (source) { 67 | await this.awsService.fileDelete(song.source); 68 | song.source = await this.awsService.fileUpload(source, 'songs'); 69 | } 70 | const updatedSong = await song.save(); 71 | return updatedSong; 72 | } 73 | 74 | async deleteSong(id: number): Promise { 75 | const song = await this.getSongById(id); 76 | for (let i = 0; i < song.tracks.length; i++) { 77 | await this.trackService.deleteTrack(song.tracks[i].id); 78 | } 79 | if (song.source) { 80 | await this.awsService.fileDelete(song.source); 81 | } 82 | const result = await this.songRepository.delete(id); 83 | if (result.affected === 0) { 84 | throw new NotFoundException(`Song with id ${id} does not found`); 85 | } 86 | return result; 87 | } 88 | 89 | async pushToFavoriteList(songId: number, favoriteListId: number): Promise { 90 | const song = await this.getSongById(songId); 91 | const track = await this.favService.createFavoriteTrack(song, null, favoriteListId); 92 | return track; 93 | } 94 | 95 | async pushToPlaylist(songId: number, playlistId: number): Promise { 96 | const song = await this.getSongById(songId); 97 | const track = await this.playlistService.createPlaylistTrack(song, null, playlistId); 98 | return track; 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/modules/singer/singer.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { Singer } from './singer.entity'; 3 | import { InjectRepository } from '@nestjs/typeorm'; 4 | import { SingerRepository } from './singer.repository'; 5 | import { ArtistType } from '../../commons/enums/artist-type.enum'; 6 | import { Gender } from '../../commons/enums/gender.enum'; 7 | import { CreateAlbumDto } from '../../shared/dto/create-album.dto'; 8 | import { SingerAlbum } from '../singer-album/singer-album.entity'; 9 | import { DeleteResult } from 'typeorm'; 10 | import { AwsService } from '../../shared/modules/aws/aws.service'; 11 | import { SingerAlbumService } from '../singer-album/singer-album.service'; 12 | 13 | @Injectable() 14 | export class SingerService { 15 | 16 | constructor(@InjectRepository(SingerRepository) private singerRepository: SingerRepository, 17 | private awsService: AwsService, 18 | private singerAlbumService: SingerAlbumService) { 19 | } 20 | 21 | async getAllSingers(): Promise { 22 | return await this.singerRepository.find(); 23 | } 24 | 25 | async getLimitedSingers(limit: number): Promise { 26 | return await this.singerRepository.getLimitedSingers(limit); 27 | } 28 | 29 | async getFilteredSingers(limit: number, nationality: string, type: ArtistType, 30 | gender: Gender): Promise { 31 | return await this.singerRepository.getFilteredSingers(limit, nationality, type, gender); 32 | } 33 | 34 | async getSingerById(id: number): Promise { 35 | const singer = await this.singerRepository.findOne({ 36 | where: { id }, 37 | }); 38 | if (!singer) { 39 | throw new NotFoundException(`Singer with id ${id} does not found`); 40 | } 41 | return singer; 42 | } 43 | 44 | async createNewSinger(name: string, info: string, gender: Gender, type: ArtistType, 45 | nationality: string, 46 | image: any): Promise { 47 | const singer = new Singer(); 48 | singer.name = name; 49 | singer.info = info; 50 | singer.gender = gender; 51 | singer.nationality = nationality; 52 | singer.type = type; 53 | singer.image = await this.awsService.fileUpload(image, 'singer-images'); 54 | singer.singerAlbums = []; 55 | const savedSinger = await singer.save(); 56 | return savedSinger; 57 | } 58 | 59 | async updateSinger(id: number, name: string, info: string, gender: Gender, nationality: string, 60 | type: ArtistType, image: any): Promise { 61 | const singer = await this.getSingerById(id); 62 | if (name) { 63 | singer.name = name; 64 | } 65 | if (info) { 66 | singer.info = info; 67 | } 68 | if (gender) { 69 | singer.gender = gender; 70 | } 71 | if (nationality) { 72 | singer.nationality = nationality; 73 | } 74 | if (type) { 75 | singer.type = type; 76 | } 77 | if (image) { 78 | await this.awsService.fileDelete(singer.image); 79 | singer.image = await this.awsService.fileUpload(image, 'singer-images'); 80 | } 81 | const savedSinger = await singer.save(); 82 | return singer; 83 | } 84 | 85 | async deleteSinger(singerId: number): Promise { 86 | const singer = await this.getSingerById(singerId); 87 | for (let i = 0; i < singer.singerAlbums.length; i++) { 88 | await this.singerAlbumService.deleteSingerAlbum(singer.singerAlbums[i].id); 89 | } 90 | if(singer.image){ 91 | await this.awsService.fileDelete(singer.image); 92 | } 93 | const result = await this.singerRepository.delete(singerId); 94 | if (result.affected === 0) { 95 | throw new NotFoundException(`Singer with id ${singerId} does not found`); 96 | } 97 | return result; 98 | } 99 | 100 | async createNewAlbum(singerId: number, createAlbumDto: CreateAlbumDto): Promise { 101 | const singer = await this.getSingerById(singerId); 102 | const singerAlbum = new SingerAlbum(); 103 | const { name } = createAlbumDto; 104 | singerAlbum.name = name; 105 | singerAlbum.singer = singer;// this will create a foreign key 106 | singerAlbum.image = singer.image; 107 | const savedSingerAlbum = await singerAlbum.save(); 108 | return savedSingerAlbum; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/modules/musician/musician.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { AwsService } from '../../shared/modules/aws/aws.service'; 4 | import { ArtistType } from '../../commons/enums/artist-type.enum'; 5 | import { Gender } from '../../commons/enums/gender.enum'; 6 | import { DeleteResult } from 'typeorm'; 7 | import { CreateAlbumDto } from '../../shared/dto/create-album.dto'; 8 | import { MusicianRepository } from './musician.repository'; 9 | import { Musician } from './musician.entity'; 10 | import { MusicianAlbum } from '../musician-album/musician-album.entity'; 11 | import { MusicianAlbumService } from '../musician-album/musician-album.service'; 12 | 13 | @Injectable() 14 | export class MusicianService { 15 | constructor(@InjectRepository(MusicianRepository) private musicianRepository: MusicianRepository, 16 | private awsService: AwsService, 17 | private musicianAlbumService: MusicianAlbumService) { 18 | } 19 | 20 | async getAllMusicians(): Promise { 21 | return await this.musicianRepository.find(); 22 | } 23 | 24 | async getLimitedMusicians(limit: number): Promise { 25 | return await this.musicianRepository.getLimitedMusicians(limit); 26 | } 27 | 28 | async getFilteredMusicians(limit: number, nationality: string, type: ArtistType, 29 | gender: Gender): Promise { 30 | return await this.musicianRepository.getFilteredMusicians(limit, nationality, type, gender); 31 | } 32 | 33 | async getMusicianById(id: number): Promise { 34 | const musician = await this.musicianRepository.findOne({ 35 | where: { id }, 36 | }); 37 | if (!musician) { 38 | throw new NotFoundException(`Musician with id ${id} does not found`); 39 | } 40 | return musician; 41 | } 42 | 43 | async createNewMusician(name: string, info: string, gender: Gender, type: ArtistType, 44 | nationality: string, 45 | image: any): Promise { 46 | const musician = new Musician(); 47 | musician.name = name; 48 | musician.info = info; 49 | musician.gender = gender; 50 | musician.nationality = nationality; 51 | musician.type = type; 52 | musician.image = await this.awsService.fileUpload(image, 'musician-images'); 53 | musician.musicianAlbums = []; 54 | const savedMusician = await musician.save(); 55 | return savedMusician; 56 | } 57 | 58 | async updateMusician(id: number, name: string, info: string, gender: Gender, nationality: string, 59 | type: ArtistType, image: any): Promise { 60 | const musician = await this.getMusicianById(id); 61 | if (name) { 62 | musician.name = name; 63 | } 64 | if (info) { 65 | musician.info = info; 66 | } 67 | if (gender) { 68 | musician.gender = gender; 69 | } 70 | if (nationality) { 71 | musician.nationality = nationality; 72 | } 73 | if (type) { 74 | musician.type = type; 75 | } 76 | if (image) { 77 | await this.awsService.fileDelete(musician.image); 78 | musician.image = await this.awsService.fileUpload(image, 'musician-images'); 79 | } 80 | const savedMusician = await musician.save(); 81 | return savedMusician; 82 | } 83 | 84 | async deleteMusician(musicianId: number): Promise { 85 | const musician = await this.getMusicianById(musicianId); 86 | if(musician.image){ 87 | await this.awsService.fileDelete(musician.image); 88 | } 89 | for (let i = 0; i < musician.musicianAlbums.length; i++) { 90 | await this.musicianAlbumService.deleteMusicianAlbum(musician.musicianAlbums[i].id) 91 | } 92 | const result = await this.musicianRepository.delete(musicianId); 93 | if (result.affected === 0) { 94 | throw new NotFoundException(`Musician with id ${musicianId} does not found`); 95 | } 96 | return result; 97 | } 98 | 99 | // implementation later 100 | async createNewAlbum(musicianId: number, createAlbumDto: CreateAlbumDto): Promise { 101 | const musician = await this.getMusicianById(musicianId); 102 | const musicianAlbum = new MusicianAlbum(); 103 | const { name } = createAlbumDto; 104 | musicianAlbum.name = name; 105 | musicianAlbum.musician = musician;// this will create a foreign key 106 | musicianAlbum.image = musician.image; 107 | 108 | musicianAlbum.musics = []; // updated 109 | 110 | const savedSingerAlbum = await musicianAlbum.save(); 111 | return savedSingerAlbum; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/shared/modules/chat/chat.gateway.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OnGatewayConnection, 3 | OnGatewayDisconnect, 4 | SubscribeMessage, 5 | WebSocketGateway, 6 | WebSocketServer, 7 | } from '@nestjs/websockets'; 8 | import { InjectRepository } from '@nestjs/typeorm'; 9 | import { Room } from './entities/room.entity'; 10 | import { Repository } from 'typeorm'; 11 | import { Message } from './entities/message.entity'; 12 | import { UserJoinedRoom } from './entities/user-joined-room.entity'; 13 | import { forwardRef, Inject, NotFoundException } from '@nestjs/common'; 14 | import { AuthService } from '../../../modules/auth/auth.service'; 15 | import { Socket } from 'socket.io'; 16 | import { User } from '../../../modules/auth/entities/user.entity'; 17 | import { ChatService } from './chat.service'; 18 | 19 | // this is a provider 20 | @WebSocketGateway() 21 | export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect { 22 | 23 | constructor(private chatService: ChatService, 24 | @Inject(forwardRef(() => AuthService)) private authService: AuthService, 25 | @InjectRepository(Message) private readonly messageRepository: Repository, 26 | @InjectRepository(Room) private readonly roomRepository: Repository) { 27 | } 28 | 29 | 30 | /* 31 | * this server is public server ,that used to detect the number of available users 32 | * on the chat 33 | * 34 | * */ 35 | @WebSocketServer() server; 36 | 37 | 38 | private users = 0; 39 | 40 | async handleConnection() { 41 | 42 | // A client has connected 43 | this.users++; 44 | 45 | // Notify connected clients of current users 46 | this.server.emit('connected-users', this.users); 47 | 48 | } 49 | 50 | async handleDisconnect(client: Socket) { 51 | 52 | // A client has disconnected 53 | this.users--; 54 | 55 | // Notify connected clients of current users 56 | this.server.emit('connected-users', this.users); 57 | 58 | // Notify when the user disconnected and has leaved 59 | if (client && client.id) { 60 | const user = await this.authService.findUser(null, null, client.id); 61 | if(user){ 62 | client.server.emit('users-changed', { user: user.username, event: 'left' }); 63 | } 64 | } 65 | 66 | } 67 | 68 | 69 | @SubscribeMessage('enter-chat-room') 70 | async enterChatRoom(client: Socket, data: { nickname: string, roomId: number }) { 71 | const { nickname, roomId } = data; 72 | const user: User = await this.authService.findUser(null, nickname); 73 | const room = await this.chatService.getRoomById(roomId); 74 | 75 | // this object will not be created only if the user has not join the room 76 | let userJoinedRoom: UserJoinedRoom; 77 | 78 | // this function is used to determine if the user has joined this room or not 79 | let isUserJoined = () => 80 | user.userJoinedRooms.some(userJoinedRoom => userJoinedRoom.roomId === roomId); 81 | 82 | 83 | // if the user does not join the room, we will create the user joined room object 84 | if (!isUserJoined()) { 85 | userJoinedRoom = new UserJoinedRoom(); 86 | userJoinedRoom.user = user; 87 | userJoinedRoom.room = room; 88 | userJoinedRoom.joinedUsername = user.username; 89 | await userJoinedRoom.save(); 90 | } 91 | 92 | // if the user does not have a clientId we will give him a one to use it in connection and 93 | // disconnection cases 94 | if (!user.clientId) { 95 | user.clientId = client.id; 96 | await user.save(); 97 | } 98 | client.join(roomId.toString()).broadcast.to(roomId.toString(), 99 | ).emit('users-changed', { user: user.username, event: 'joined' }); 100 | } 101 | 102 | @SubscribeMessage('leave-room') 103 | async leaveRoom(client: Socket, data: { nickname: string, roomId: number }) { 104 | const { nickname, roomId } = data; 105 | const user: User = await this.authService.findUser(null, nickname); 106 | 107 | // Notify the users in the room that this client has leaved 108 | client.broadcast.to(roomId.toString()).emit('users-changed', { 109 | user: user.username, event: 'left', 110 | }); 111 | client.leave(roomId.toString()); 112 | } 113 | 114 | @SubscribeMessage('add-message') 115 | async addMessage(client: Socket, data: { text: string, roomId: number, userId: number }) { 116 | const { text, roomId, userId } = data; 117 | const user = await this.authService.findUser(userId); 118 | const room = await this.chatService.getRoomById(roomId); 119 | const message = await this.createMessage(user, room, text); 120 | 121 | // pushing the message in the room chat immediately after the user sent the message 122 | client.server.emit('message', message); 123 | } 124 | 125 | async createMessage(user: User, room: Room, text: string): Promise { 126 | const msg = new Message(); 127 | msg.text = text; 128 | msg.user = user; 129 | msg.sender = user.username; 130 | msg.room = room; 131 | msg.roomName = room.name; 132 | msg.created = new Date(); 133 | return await msg.save(); 134 | } 135 | 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/modules/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, 7 | Post, 8 | Put, 9 | Req, 10 | ParseIntPipe, 11 | Res, 12 | UseGuards, 13 | } from '@nestjs/common'; 14 | import { AuthCredentialsDto } from './dto/auth-credentials.dto'; 15 | import { CreateProfileDto } from './dto/create-profile.dto'; 16 | import { AuthService } from './auth.service'; 17 | import { EmailLoginDto } from './dto/email-login.dto'; 18 | import { AuthGuard } from '@nestjs/passport'; 19 | import { Roles } from '../../commons/decorators/roles.decorator'; 20 | import { Role } from '../../commons/enums/role.enum'; 21 | import { AcceptedAuthGuard } from '../../commons/guards/accepted-auth.guard'; 22 | import { ResetPasswordDto } from './dto/reset-password.dto'; 23 | import { GetAuthenticatedUser } from '../../commons/decorators/get-authenticated-user.decorator'; 24 | import { User } from './entities/user.entity'; 25 | import { AdminAuthGuard } from '../../commons/guards/admin-auth.guard'; 26 | import { UserAuthGuard } from '../../commons/guards/user-auth.guard'; 27 | 28 | 29 | @Controller('auth') 30 | export class AuthController { 31 | 32 | constructor(private authService: AuthService) { 33 | } 34 | 35 | @Post('register') 36 | signUp(@Body('authCredentialsDto') authCredentialsDto: AuthCredentialsDto, 37 | @Body('createProfileDto') createProfileDto: CreateProfileDto) { 38 | return this.authService.signUp(authCredentialsDto, createProfileDto); 39 | } 40 | 41 | @Get('email/send-email-verification/:email') 42 | async sendEmailVerification(@Param('email') email: string) { 43 | await this.authService.createEmailToken(email); 44 | return this.authService.sendEmailVerification(email); 45 | } 46 | 47 | @Get('email/verify/:token') 48 | verifyEmail(@Param('token') token: string) { 49 | return this.authService.verifyEmail(token); 50 | } 51 | 52 | 53 | /* Social Endpoints */ 54 | 55 | @Get('google') 56 | @UseGuards(AuthGuard('google')) 57 | googleLogin() { 58 | 59 | } 60 | 61 | // related to callback --> redirection to frontend 62 | @Get('google/callback') 63 | @UseGuards(AuthGuard(['google'])) 64 | googleLoginCallback(@Req() req, @Res() res) { 65 | const jwt: string = req.user.jwt; 66 | const { id } = req.user.user; 67 | if (jwt) { 68 | res 69 | .redirect(`http://localhost:4200/auth/google-success/userId:${id}/accessToken:${jwt}`); 70 | } else { 71 | res.redirect('http://localhost:4200/auth/google-failure'); 72 | } 73 | } 74 | 75 | @Get('facebook') 76 | @UseGuards(AuthGuard('facebook')) 77 | facebookLogin() { 78 | 79 | } 80 | 81 | // related to callback --> redirection to frontend 82 | @Get('facebook/callback') 83 | @UseGuards(AuthGuard('facebook')) 84 | facebookLoginCallback(@Req() req, @Res() res) { 85 | const jwt: string = req.user.jwt; 86 | const { id } = req.user.user; 87 | if (jwt) { 88 | res 89 | .redirect(`http://localhost:4200/auth/facebook-success/userId:${id}/accessToken:${jwt}`); 90 | } else { 91 | res.redirect('http://localhost:4200/auth/facebook-failure'); 92 | } 93 | } 94 | 95 | 96 | @Post('login/user') 97 | signInUser(@Body() emailLoginDto: EmailLoginDto) { 98 | return this.authService.signInUser(emailLoginDto); 99 | } 100 | 101 | 102 | @Get('email/forgot-password/:email') 103 | sendEmailForgotPassword(@Param('email') email: string) { 104 | return this.authService.sendEmailForgottenPassword(email); 105 | } 106 | 107 | @Post('email/reset-password') 108 | setNewPassword(@Body() resetPasswordDto: ResetPasswordDto) { 109 | return this.authService.setNewPassword(resetPasswordDto); 110 | } 111 | 112 | @Get('user-main-data') 113 | @UseGuards(AuthGuard('jwt'), AcceptedAuthGuard) 114 | @Roles([Role.USER, Role.ADMIN]) 115 | getUserData(@GetAuthenticatedUser() user: User) { 116 | return this.authService.getUserMainData(user); 117 | } 118 | 119 | @Delete('delete-user-account') 120 | @UseGuards(AuthGuard(), UserAuthGuard) 121 | @Roles([Role.USER]) 122 | deleteUserAccount(@GetAuthenticatedUser() user: User) { 123 | return this.authService.deleteUserAccount(user); 124 | } 125 | 126 | @Get('check-username/:username') 127 | isValidUsername(@Param('username') username: string) { 128 | return this.authService.isValidUsername(username); 129 | } 130 | 131 | 132 | @Post('login/admin') 133 | signInAdmin(@Body() emailLoginDto: EmailLoginDto) { 134 | return this.authService.signInAdmin(emailLoginDto); 135 | } 136 | 137 | @Get('system-users') 138 | @UseGuards(AuthGuard(), AdminAuthGuard) 139 | @Roles([Role.ADMIN]) 140 | getSystemUsers() { 141 | return this.authService.getSystemUsers(); 142 | } 143 | 144 | @Get('users/:id') 145 | getUserById(@Param('id', ParseIntPipe) id: number) { 146 | return this.authService.getUserById(id); 147 | } 148 | 149 | @Put('edit-user-roles/:userId') 150 | @UseGuards(AuthGuard(), AdminAuthGuard) 151 | @Roles([Role.ADMIN]) 152 | editUserRoles(@Param('userId', ParseIntPipe) userId: number, @Body() roles: Role[]) { 153 | return this.authService.editUserRoles(userId, roles); 154 | } 155 | 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/modules/notification/notification.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import * as webPush from 'web-push'; 3 | import { InjectRepository } from '@nestjs/typeorm'; 4 | import { Subscriber } from './entities/subscriber.entity'; 5 | import { Repository } from 'typeorm'; 6 | import { SubscribersNotifications } from './entities/subscribers-notifications.entity'; 7 | import { User } from '../auth/entities/user.entity'; 8 | import { NotificationPayloadDto } from './notification-payload.dto'; 9 | import { NotificationPayload } from './classes/notification-payload'; 10 | import { Notification } from './classes/notification'; 11 | import { NotificationData } from './classes/notification-data'; 12 | import { NotificationEntity } from './entities/notification.entity'; 13 | import { v4 as uuidv4 } from 'uuid'; 14 | 15 | @Injectable() 16 | export class NotificationService { 17 | constructor(@InjectRepository(Subscriber) private subscriberRepository: Repository, 18 | @InjectRepository(SubscribersNotifications) 19 | private subscribersNotificationsRepository: Repository) { 20 | } 21 | 22 | async getAllSubscribers(): Promise { 23 | return await this.subscriberRepository.find(); 24 | } 25 | 26 | async getSubscriberById(id: number): Promise { 27 | const subscriber = await this.subscriberRepository.findOne({ 28 | where: { 29 | id, 30 | }, 31 | }); 32 | if (!subscriber) { 33 | throw new NotFoundException(`Subscriber with Id ${id} does not found`); 34 | } 35 | return subscriber; 36 | } 37 | 38 | async getSubscriberNotifications(id: number): Promise { 39 | const subscriber = await this.getSubscriberById(id); 40 | return subscriber.subscribersNotifications; 41 | } 42 | 43 | async deleteSubscriber(id: number): Promise { 44 | const subscriber = await this.getSubscriberById(id); 45 | for (let i = 0; i < subscriber.subscribersNotifications.length; i++) { 46 | await this.subscribersNotificationsRepository 47 | .delete(subscriber.subscribersNotifications[i].id); 48 | } 49 | const result = await this.subscriberRepository.delete(id); 50 | if (result.affected === 0) { 51 | throw new NotFoundException(`Subscriber with Id ${id} does not found`); 52 | } 53 | } 54 | 55 | async newSubscriber(user: User, sub: any): Promise { 56 | const { endpoint, expirationTime, keys } = sub; 57 | const subscriber = new Subscriber(); 58 | subscriber.user = user; 59 | subscriber.keys = keys; 60 | subscriber.endpoint = endpoint; 61 | subscriber.expirationTime = expirationTime; 62 | subscriber.subscribersNotifications = []; 63 | return await subscriber.save(); 64 | } 65 | 66 | 67 | async sendNewNotification(notificationPayloadDto: NotificationPayloadDto): Promise { 68 | const { title, body } = notificationPayloadDto; 69 | const notificationPayload = new NotificationPayload(); 70 | notificationPayload.notification = new Notification(); 71 | notificationPayload.notification.title = title; 72 | notificationPayload.notification.body = body; 73 | notificationPayload.notification.actions = [ 74 | { 75 | action: 'explore', 76 | title: 'explore my new website', 77 | }, 78 | { 79 | action: 'explore', 80 | title: 'Close Popups', 81 | }, 82 | ]; 83 | notificationPayload.notification.data = new NotificationData(); 84 | notificationPayload.notification.data.dateOfArrival = new Date(Date.now()); 85 | notificationPayload.notification.data.primaryKey = uuidv4(); 86 | notificationPayload.notification.icon = 87 | 'https://songs-static.s3.us-east-2.amazonaws.com/main-page-logo-small-hat.png'; 88 | notificationPayload.notification.vibrate = [100, 50, 100]; 89 | const subscribers = await this.getAllSubscribers(); 90 | const notification = await this.createNotification(title, body); 91 | for (let i = 0; i < subscribers.length; i++) { 92 | await this.createSubscriberNotification( 93 | notificationPayload, notification, subscribers[i], 94 | ); 95 | await webPush.sendNotification(subscribers[i], JSON.stringify(notificationPayload)); 96 | } 97 | 98 | } 99 | 100 | async createSubscriberNotification(notificationPayload: NotificationPayload, 101 | notification: NotificationEntity, 102 | subscriber: Subscriber): Promise { 103 | const subscribeNotification = new SubscribersNotifications(); 104 | subscribeNotification.title = notificationPayload.notification.title; 105 | subscribeNotification.body = notificationPayload.notification.body; 106 | subscribeNotification.data = notificationPayload.notification.data; 107 | subscribeNotification.actions = notificationPayload.notification.actions; 108 | subscribeNotification.vibrate = notificationPayload.notification.vibrate; 109 | 110 | //creation of foreign keys 111 | subscribeNotification.subscriber = subscriber; 112 | subscribeNotification.notification = notification; 113 | await subscribeNotification.save(); 114 | 115 | } 116 | 117 | 118 | async createNotification(title: string, body: string) { 119 | const notification = new NotificationEntity(); 120 | notification.title = title; 121 | notification.body = body; 122 | notification.subscribersNotifications = []; 123 | return await notification.save(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/routes_endpoint.txt: -------------------------------------------------------------------------------- 1 | - Endpoints of music land system: 2 | - We will use Restful API 3 | ** Auth Endpoints ** 4 | (Auth Controller): 5 | - localhost:3000/auth --> Root Route (Main-Endpoint - Controller Name) 6 | 7 | - localhost:3000/auth/email/register (POST Method) 8 | - localhost:3000/auth/email/verify/:token (GET Method) 9 | - localhost:3000/auth/email/send-verification-request/:email (GET Method) 10 | - localhost:3000/auth/email/forgot-password/:email (GET Method) 11 | - localhost:3000/auth/email/reset-password (POST Method) 12 | - localhost:3000/auth/check-username (GET Method) 13 | - localhost:3000/auth/login/user (POST Method) 14 | - localhost:3000/auth/login/admin (POST Method) 15 | - localhost:3000/auth/delete-user (DELETE Method) 16 | - localhost:3000/auth/system-users (GET Method) 17 | - localhost:3000/auth/current-user (GET Method) 18 | - localhost:3000/auth/user-main-data (GET Method) 19 | ---------------------------------------------------------- 20 | (Email Verification Controller): 21 | - localhost:3000/verified-emails --> Root Route (GET Method - Main-Endpoint - Controller Name) 22 | 23 | - localhost:3000/verified-emails/:email --> (GET Method) 24 | ---------------------------------------------------------- 25 | 26 | (Forgotten Password Controller): 27 | 28 | - localhost:3000/forgotten-passwords --> Root Route (GET Method - Main-Endpoint - Controller Name) 29 | - localhost:3000/forgotten-passwords/:email --> (GET Method) 30 | 31 | 32 | ////////////////////////////////////////////////////////////////// 33 | 34 | ** Profile Endpoints ** 35 | 36 | (Profile Controller): 37 | - localhost:3000/profiles --> Root Route (Main-Endpoint - Controller Name) 38 | 39 | - localhost:3000/profiles/user-profile --> (GET Method) 40 | - localhost:3000/profiles/user-profile/edit --> (PUT Method) 41 | - localhost:3000/profiles/user-profile/set-profile-image --> (POST Method) 42 | - localhost:3000/profiles/user-profile/delete-profile-image --> (DELETE Method) 43 | - localhost:3000/profiles/user-profile/change-profile-image --> (PATCH Method) 44 | 45 | ////////////////////////////////////////////////////////////////// 46 | 47 | ** Singer Endpoints ** 48 | 49 | (Singer Controller): 50 | - localhost:3000/singers --> Root Route (GET Method - Main-Endpoint - Controller Name) 51 | - localhost:3000/singers/new-singer --> Root Route (POST Method - Main-Endpoint - Controller Name) 52 | 53 | - localhost:3000/singers/limited --> Root Route (GET Method) 54 | - localhost:3000/singers/filtered --> Root Route (GET Method) 55 | - localhost:3000/singers/:id --> (GET Method) 56 | - localhost:3000/singers/:id/new-album --> (POST Method) 57 | - localhost:3000/singers/:id/update-singer --> (PUT Method) 58 | - localhost:3000/singers/:id/delete-singer --> (DELETE Method) 59 | 60 | ////////////////////////////////////////////////////////////////// 61 | 62 | ** Musician Endpoints ** 63 | 64 | (Musician Controller): 65 | - localhost:3000/musicians --> Root Route (GET Method - Main-Endpoint - Controller Name) 66 | - localhost:3000/musicians/new-musician --> Root Route (POST Method - Main-Endpoint - Controller Name) 67 | 68 | - localhost:3000/musicians/limited --> Root Route (GET Method) 69 | - localhost:3000/musicians/filtered --> Root Route (GET Method) 70 | - localhost:3000/musicians/:id --> (GET Method) 71 | - localhost:3000/musicians/:id/new-album --> (POST Method) 72 | - localhost:3000/musicians/:id/update-musician --> (PUT Method) 73 | - localhost:3000/musicians/:id/delete-musician --> (DELETE Method) 74 | 75 | ////////////////////////////////////////////////////////////////// 76 | 77 | ** Singer-Album Endpoints ** 78 | 79 | (Singer-Album Controller): 80 | - localhost:3000/singer-albums --> Root Route (GET Method - Main-Endpoint - Controller Name) 81 | - localhost:3000/singer-albums/:id --> (GET Method) 82 | - localhost:3000/singer-albums/:id/new-song --> (POST Method) 83 | - localhost:3000/singer-albums/:id/update-album --> (PUT Method) 84 | - localhost:3000/singer-albums/:id/delete-album --> (DELETE Method) 85 | - localhost:3000/singer-albums/:id/clear-album --> (DELETE Method) 86 | 87 | 88 | ////////////////////////////////////////////////////////////////// 89 | 90 | 91 | ** Musician-Album Endpoints ** 92 | 93 | (Musician-Album Controller): 94 | - localhost:3000/musician-albums --> Root Route (GET Method - Main-Endpoint - Controller Name) 95 | - localhost:3000/musician-albums/:id --> (GET Method) 96 | - localhost:3000/musician-albums/:id/new-music --> (POST Method) 97 | - localhost:3000/musician-albums/:id/update-album --> (PUT Method) 98 | - localhost:3000/musician-albums/:id/delete-album --> (DELETE Method) 99 | - localhost:3000/musician-albums/:id/clear-album --> (DELETE Method) 100 | 101 | 102 | ////////////////////////////////////////////////////////////////// 103 | 104 | 105 | ////////////////////////////////////////////////////////////////// 106 | 107 | 108 | ** Favorite-List Endpoints ** 109 | 110 | (Favorite-List Controller): 111 | - localhost:3000/favorite-lists --> Root Route (GET Method - Main-Endpoint - Controller Name) 112 | - localhost:3000/favorite-lists/:id --> (GET Method) 113 | - localhost:3000/favorite-lists/:id/clear-favorite-list --> (DELETE Method) 114 | - localhost:3000/favorite-lists/:favoriteId/remove-track-from-favorite-list/:trackId --> (DELETE Method) 115 | 116 | 117 | ////////////////////////////////////////////////////////////////// 118 | 119 | ** Playlist Endpoints ** 120 | 121 | (Playlist Controller): 122 | - localhost:3000/playlists --> Root Route (GET Method - Main-Endpoint - Controller Name) 123 | - localhost:3000/playlists/:id --> (GET Method) 124 | - localhost:3000/playlists/user-play-lists --> (GET Method) 125 | - localhost:3000/playlists/new-user-playlist --> (POST Method) 126 | - localhost:3000/playlists/:id/update-playlist--> (PUT Method) 127 | - localhost:3000/playlists/:id/delete-playlist--> (DELETE Method) 128 | - localhost:3000/playlists/:id/clear-playlist-content--> (DELETE Method) 129 | - localhost:3000/playlists/:playlistId/remove-track-from-playlist/:trackId --> (DELETE Method) 130 | 131 | 132 | ////////////////////////////////////////////////////////////////// 133 | 134 | 135 | 136 | 137 | 138 | 139 | ** Music Endpoints ** 140 | 141 | (Music Controller): 142 | 143 | - localhost:3000/musics --> Root Route (GET Method - Main-Endpoint - Controller Name) 144 | - localhost:3000/musics/limited --> Root Route (GET Method) 145 | - localhost:3000/musics/filtered --> Root Route (GET Method) 146 | - localhost:3000/musics/:id --> (GET Method) 147 | - localhost:3000/musics/:musicId/add-to-playlist/:playlistId --> (POST Method) 148 | - localhost:3000/musics/:musicId/save-to-favorite-list/:favoriteId --> (POST Method) 149 | - localhost:3000/musics/:id/update-music --> (PUT Method) 150 | - localhost:3000/musics/:id/delete-music --> Root Route (DELETE Method) 151 | 152 | ////////////////////////////////////////////////////////////////// 153 | 154 | ** Song Endpoints ** 155 | 156 | (Song Controller): 157 | 158 | - localhost:3000/songs --> Root Route (GET Method - Main-Endpoint - Controller Name) 159 | - localhost:3000/songs/limited --> Root Route (GET Method) 160 | - localhost:3000/songs/filtered --> Root Route (GET Method) 161 | - localhost:3000/songs/:id --> (GET Method) 162 | - localhost:3000/songs/:songId/add-to-playlist/:playlistId --> (POST Method) 163 | - localhost:3000/songs/:songId/save-to-favorite-list/:favoriteId --> (POST Method) 164 | - localhost:3000/songs/:id/update-song --> (PUT Method) 165 | - localhost:3000/songs/:id/delete-song --> Root Route (DELETE Method) 166 | -------------------------------------------------------------------------------- /src/modules/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, 3 | ConflictException, forwardRef, 4 | HttpException, 5 | HttpStatus, Inject, 6 | Injectable, 7 | NotFoundException, 8 | } from '@nestjs/common'; 9 | import { AuthCredentialsDto } from './dto/auth-credentials.dto'; 10 | import { CreateProfileDto } from './dto/create-profile.dto'; 11 | import { User } from './entities/user.entity'; 12 | import * as bcrypt from 'bcryptjs'; 13 | import { InjectRepository } from '@nestjs/typeorm'; 14 | import { UserRepository } from './repositories/user.repository'; 15 | import { Profile } from '../profile/profile.entity'; 16 | import { Favorite } from '../favorite/favorite.entity'; 17 | import { Role } from '../../commons/enums/role.enum'; 18 | import { EmailVerification } from './entities/email-verification.entity'; 19 | import { Repository } from 'typeorm'; 20 | import { Nodemailer, NodemailerDrivers } from '@crowdlinker/nestjs-mailer'; 21 | import { config } from '../../config'; 22 | import { EmailLoginDto } from './dto/email-login.dto'; 23 | import { JwtPayload } from '../../commons/interfaces/jwt-payload.interface'; 24 | import { JwtService } from '@nestjs/jwt'; 25 | import { ForgottenPassword } from './entities/forgotten-password.entity'; 26 | import { ResetPasswordDto } from './dto/reset-password.dto'; 27 | import { ProfileService } from '../profile/profile.service'; 28 | import { FavoriteService } from '../favorite/favorite.service'; 29 | import { PlaylistService } from '../playlist/playlist.service'; 30 | import { ChatService } from '../../shared/modules/chat/chat.service'; 31 | import { NotificationService } from '../notification/notification.service'; 32 | 33 | @Injectable() 34 | export class AuthService { 35 | constructor(@InjectRepository(UserRepository) private userRepository: UserRepository, 36 | @InjectRepository(EmailVerification) private emailVerificationRepo: Repository, 37 | @InjectRepository(ForgottenPassword) private forgottenPasswordRepo: Repository, 38 | private nodeMailerService: Nodemailer, 39 | private jwtService: JwtService, 40 | private profileService: ProfileService, 41 | private favoriteService: FavoriteService, 42 | private playlistService: PlaylistService, 43 | private notificationService: NotificationService, 44 | @Inject(forwardRef(() => ChatService)) private chatService: ChatService) { 45 | } 46 | 47 | async signUp(authCredentialsDto: AuthCredentialsDto, 48 | createProfileDto: CreateProfileDto): Promise { 49 | const { username, password, email } = authCredentialsDto; 50 | if (!this.isValidEmail(email)) { 51 | throw new BadRequestException('You have entered invalid email'); 52 | } 53 | const user = new User(); 54 | user.salt = await bcrypt.genSalt(); 55 | 56 | if ((await this.isValidUsername(username))) { 57 | throw new ConflictException(`Username ${username} is not available, please try another one`); 58 | } else { 59 | user.username = username; 60 | } 61 | 62 | if ((await this.checkIfEmailExist(email))) { 63 | throw new ConflictException(`Email ${email} is not available, please try another one`); 64 | } else { 65 | user.email = email; 66 | } 67 | 68 | user.roles = [Role.USER]; 69 | user.password = await this.userRepository.hashPassword(password, user.salt); 70 | user.profile = await this.createProfile(user, createProfileDto); 71 | user.playlists = []; 72 | // sending emails verification 73 | await this.createEmailToken(email); 74 | await this.sendEmailVerification(email); 75 | 76 | await user.save(); 77 | } 78 | 79 | 80 | /* Social Methods */ 81 | async SignInGoogle(profile: any, googleId: string): Promise<{ user: User, jwt: string }> { 82 | const { emails } = profile; 83 | let googleUser = new User(); 84 | googleUser.googleId = googleId; 85 | googleUser = await this.setUserInfo(googleUser, profile); 86 | const jwt = this.generateJwtToken(emails[0].value); 87 | const user = await googleUser.save(); 88 | return { user, jwt }; 89 | } 90 | 91 | async SingInFacebook(profile: any, facebookId: string): Promise<{ user: User, jwt: string }> { 92 | const { emails } = profile; 93 | let facebookUser = new User(); 94 | facebookUser.facebookId = facebookId; 95 | facebookUser = await this.setUserInfo(facebookUser, profile); 96 | const jwt = this.generateJwtToken(emails[0].value); 97 | const user = await facebookUser.save(); 98 | return { user, jwt }; 99 | } 100 | 101 | 102 | async setUserInfo(user: User, profile: any) { 103 | const { name, displayName, emails, photos } = profile; 104 | 105 | // check if email and username is available 106 | if ((await this.isValidUsername(displayName))) { 107 | throw new ConflictException(`Username ${displayName} is not available, please try another one`); 108 | } 109 | if ((await this.checkIfEmailExist(emails[0].value))) { 110 | throw new ConflictException(`Email ${emails[0].value} is not available, please try another one`); 111 | } 112 | user.username = displayName; 113 | user.email = emails[0].value; 114 | user.roles = [Role.USER]; 115 | const newProfile = new Profile(); 116 | newProfile.user = user; 117 | newProfile.firstName = name.givenName; 118 | newProfile.lastName = name.familyName; 119 | newProfile.image = photos[0].value; 120 | newProfile.favorite = await this.createFavoriteList(newProfile); 121 | user.profile = await newProfile.save(); 122 | user.isEmailVerified = true; 123 | return user; 124 | } 125 | 126 | 127 | async getUserMainData(user: User): Promise<{ user: User, profile: Profile }> { 128 | const profile = await this.profileService.getProfileData(user); 129 | return { 130 | user, 131 | profile, 132 | }; 133 | } 134 | 135 | async signInUser(emailLoginDto: EmailLoginDto): Promise<{token: string}> { 136 | if (!(await this.isValidEmail(emailLoginDto.email))) { 137 | throw new BadRequestException('Invalid Email Signature'); 138 | } 139 | const { email, user } = await this.userRepository.validateUserPassword(emailLoginDto); 140 | const token = this.generateJwtToken(email); 141 | return {token}; 142 | } 143 | 144 | async checkIfEmailExist(email: string): Promise { 145 | const query = this.userRepository.createQueryBuilder('user'); 146 | const isEmailExist = query.select('email') 147 | .where('user.email LIKE :email', { email }); 148 | const count = await isEmailExist.getCount(); 149 | return count >= 1; 150 | } 151 | 152 | 153 | // this method well be used in different methods 154 | generateJwtToken(email: string) { 155 | const payload: JwtPayload = { email }; 156 | const jwt = this.jwtService.sign(payload); 157 | return jwt; 158 | } 159 | 160 | async createProfile(user: User, createProfileDto: CreateProfileDto): Promise { 161 | const { 162 | firstName, 163 | lastName, 164 | age, 165 | phone, 166 | gender, 167 | country, 168 | city, 169 | address, 170 | } 171 | = createProfileDto; 172 | const profile = new Profile(); 173 | profile.firstName = firstName; 174 | profile.lastName = lastName; 175 | profile.phone = phone; 176 | profile.gender = gender; 177 | profile.age = age; 178 | profile.country = country; 179 | profile.city = city; 180 | profile.address = address; 181 | profile.user = user; 182 | profile.favorite = await this.createFavoriteList(profile); // create a foreign key 183 | return await profile.save(); 184 | } 185 | 186 | async createFavoriteList(profile: Profile): Promise { 187 | const favorite = new Favorite(); 188 | favorite.profile = profile; 189 | favorite.tracks = []; 190 | return await favorite.save(); 191 | } 192 | 193 | 194 | async createEmailToken(email: string) { 195 | const verifiedEmail = await this.emailVerificationRepo.findOne({ email }); 196 | if (verifiedEmail && ((new Date().getTime() - verifiedEmail.timestamp.getTime()) / 60000) < 15) { 197 | throw new HttpException('LOGIN_EMAIL_SENT_RECENTLY', HttpStatus.INTERNAL_SERVER_ERROR); 198 | } else { 199 | const newEmailVerification = new EmailVerification(); 200 | newEmailVerification.email = email; 201 | newEmailVerification.emailToken = (Math.floor(Math.random() * (900000)) + 100000).toString(); 202 | newEmailVerification.timestamp = new Date(); 203 | await newEmailVerification.save(); 204 | return true; 205 | } 206 | } 207 | 208 | async sendEmailVerification(email: string): Promise { 209 | const verifiedEmail = await this.emailVerificationRepo.findOne({ email }); 210 | if (verifiedEmail && verifiedEmail.emailToken) { 211 | const url = `Click Here to confirm your email`; 213 | await this.nodeMailerService.sendMail({ 214 | from: '"Company" <' + config.nodeMailerOptions.transport.auth.username + '>', 215 | to: config.nodeMailerOptions.transport.auth.username, 216 | subject: 'Verify Email', 217 | text: 'Verify Email', 218 | html: `

Hi User



Thanks for your registration

219 |

Please Verify Your Email by clicking the following link



220 | ${url}`, 221 | }).then(info => { 222 | console.log('Message sent: %s', info.messageId); 223 | }).catch(err => { 224 | console.log('Message sent: %s', err); 225 | }); 226 | } else { 227 | throw new HttpException('REGISTER.USER_NOT_REGISTERED', HttpStatus.FORBIDDEN); 228 | } 229 | } 230 | 231 | async verifyEmail(token: string): Promise<{ isFullyVerified: boolean, user: User }> { 232 | const verifiedEmail = await this.emailVerificationRepo.findOne({ emailToken: token }); 233 | if (verifiedEmail && verifiedEmail.email) { 234 | const user = await this.userRepository.findOne({ email: verifiedEmail.email }); 235 | if (user) { 236 | user.isEmailVerified = true; 237 | const updatedUser = await user.save(); 238 | await verifiedEmail.remove(); 239 | return { isFullyVerified: true, user: updatedUser }; 240 | } 241 | } else { 242 | throw new HttpException('LOGIN_EMAIL_CODE_NOT_VALID', HttpStatus.FORBIDDEN); 243 | } 244 | } 245 | 246 | 247 | isValidEmail(email: string) { 248 | if (email) { 249 | const pattern = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 250 | return pattern.test(email); 251 | } else 252 | return false; 253 | } 254 | 255 | 256 | async sendEmailForgottenPassword(email: string): Promise { 257 | const user = await this.userRepository.findOne({ email }); 258 | if (!user) { 259 | throw new HttpException('LOGIN_USER_NOT_FOUND', HttpStatus.NOT_FOUND); 260 | } 261 | const tokenModel = await this.createForgottenPasswordToken(email); 262 | if (tokenModel && tokenModel.newPasswordToken) { 263 | const url = `Click here to reset your password`; 265 | return await this.nodeMailerService.sendMail({ 266 | from: '"Company" <' + config.nodeMailerOptions.transport.auth.username + '>', 267 | to: email, 268 | subject: 'Reset Your Password', 269 | text: 'Reset Your Password', 270 | html: `

Hi User



You have requested to reset your password , please click the following link to change your password

271 |

Please click the following link



272 | ${url}`, 273 | }).then(info => { 274 | console.log('Message sent: %s', info.messageId); 275 | }).catch(err => { 276 | console.log('Message sent: %s', err); 277 | }); 278 | } 279 | } 280 | 281 | async createForgottenPasswordToken(email: string) { 282 | let forgottenPassword = await this.forgottenPasswordRepo.findOne({ email }); 283 | if (forgottenPassword && ((new Date().getTime() - forgottenPassword.timestamp.getTime()) / 60000) < 15) { 284 | throw new HttpException('RESET_PASSWORD_EMAIL_SENT_RECENTLY', HttpStatus.INTERNAL_SERVER_ERROR); 285 | } else { 286 | forgottenPassword = new ForgottenPassword(); 287 | forgottenPassword.email = email; 288 | forgottenPassword.timestamp = new Date(); 289 | forgottenPassword.newPasswordToken = (Math.floor(Math.random() * (900000)) + 100000).toString(); 290 | return await forgottenPassword.save(); 291 | } 292 | } 293 | 294 | async checkPassword(email: string, password: string) { 295 | const user = await this.userRepository.findOne({ email }); 296 | if (!user) { 297 | throw new HttpException('User Does not Found', HttpStatus.NOT_FOUND); 298 | } 299 | return await bcrypt.compare(password, user.password); 300 | } 301 | 302 | async setNewPassword(resetPasswordDto: ResetPasswordDto) { 303 | let isNewPasswordChanged = false; 304 | const { email, newPasswordToken, currentPassword, newPassword } = resetPasswordDto; 305 | if (email && currentPassword) { 306 | const isValidPassword = await this.checkPassword(email, currentPassword); 307 | if (isValidPassword) { 308 | isNewPasswordChanged = await this.setPassword(email, newPassword); 309 | } else { 310 | throw new HttpException('RESET_PASSWORD_WRONG_CURRENT_PASSWORD', HttpStatus.CONFLICT); 311 | } 312 | } else if (newPasswordToken) { 313 | const forgottenPassword = await this.forgottenPasswordRepo.findOne({ newPasswordToken }); 314 | isNewPasswordChanged = await this.setPassword(forgottenPassword.email, newPassword); 315 | if (isNewPasswordChanged) { 316 | await this.forgottenPasswordRepo.delete(forgottenPassword.id); 317 | } 318 | } else { 319 | return new HttpException('RESET_PASSWORD_CHANGE_PASSWORD_ERROR', HttpStatus.INTERNAL_SERVER_ERROR); 320 | } 321 | return isNewPasswordChanged; 322 | } 323 | 324 | async setPassword(email: string, newPassword: string) { 325 | const user = await this.userRepository.findOne({ email }); 326 | if (!user) { 327 | throw new HttpException('LOGIN_USER_NOT_FOUND', HttpStatus.NOT_FOUND); 328 | } 329 | user.password = await this.userRepository.hashPassword(newPassword, user.salt); 330 | await user.save(); 331 | return true; 332 | } 333 | 334 | async signInAdmin(emailLoginDto: EmailLoginDto): Promise<{ accessToken: string, user: User }> { 335 | if (!(await this.isValidEmail(emailLoginDto.email))) { 336 | throw new BadRequestException('Invalid Email Signature'); 337 | } 338 | const { email, user } = await this.userRepository.validateAdminPassword(emailLoginDto); 339 | const payload: JwtPayload = { email }; 340 | const accessToken = this.jwtService.sign(payload); 341 | return { accessToken, user }; 342 | } 343 | 344 | async getSystemUsers(): Promise { 345 | return await this.userRepository.find(); 346 | } 347 | 348 | async getUserById(id: number) { 349 | const user = await this.userRepository.findOne({ 350 | where: { 351 | id, 352 | }, 353 | }); 354 | if (!user) { 355 | throw new NotFoundException(`User with Id ${id} Does not found`); 356 | } 357 | return user; 358 | } 359 | 360 | async editUserRoles(id: number, roles: Role[]): Promise { 361 | const user = await this.getUserById(id); 362 | if (roles) { 363 | user.roles = roles; 364 | } 365 | return await user.save(); 366 | } 367 | 368 | async deleteUserAccount(user: User) { 369 | const profile = await this.profileService.getProfileData(user); 370 | const favoriteId = profile.favoriteId; 371 | const subscriber = await this.notificationService.getSubscriberById(user.subscriberId); 372 | // procedure-1: delete-user-playlists/ messages/ and related rooms 373 | for (let i = 0; i < user.playlists.length; i++) { 374 | await this.playlistService.deletePlaylist(user.playlists[i].id); 375 | } 376 | 377 | await this.chatService.deleteUserMessages(user); 378 | await this.chatService.deleteUserJoinedRooms(user); 379 | 380 | 381 | // procedure-2: delete-user 382 | await this.userRepository.delete(user.id); 383 | 384 | // procedure-3: delete-user-profile 385 | await this.profileService.deleteProfile(profile.id); 386 | 387 | // procedure-4: delete user subscriber 388 | await this.notificationService.deleteSubscriber(subscriber.id); 389 | // procedure-5: delete-user-favorite-list 390 | 391 | await this.favoriteService.deleteFavoriteList(favoriteId); 392 | 393 | return true; 394 | 395 | } 396 | 397 | async isValidUsername(username: string): Promise { 398 | const query = this.userRepository.createQueryBuilder('user').select('username'); 399 | query.where('user.username LIKE :username', { username }); 400 | const count = await query.getCount(); 401 | return count >= 1; 402 | } 403 | 404 | 405 | // creating this method is just response to chat gateway 406 | async findUser(id: number, nickname?: string, clientId?: string): Promise { 407 | let user = null; 408 | if (id) { 409 | user = await this.getUserById(id); 410 | } else if (nickname) { 411 | user = await this.userRepository.findOne({ username: nickname }); 412 | } else if (clientId) { 413 | user = await this.userRepository.findOne({ clientId }); 414 | } else { 415 | throw new NotFoundException(`User with Id ${id} Does not found`); 416 | } 417 | return user; 418 | } 419 | 420 | } 421 | --------------------------------------------------------------------------------