Skip to content

Commit

Permalink
added docs
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdip-b committed Sep 9, 2024
1 parent d290cd5 commit cabc5f1
Show file tree
Hide file tree
Showing 14 changed files with 925 additions and 27 deletions.
51 changes: 51 additions & 0 deletions apps/api/src/api-key/service/api-key.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ export class ApiKeyService {
updatedAt: true
}

/**
* Creates a new API key for the given user.
*
* @throws `ConflictException` if the API key already exists.
* @param user The user to create the API key for.
* @param dto The data to create the API key with.
* @returns The created API key.
*/
async createApiKey(user: User, dto: CreateApiKey) {
await this.isApiKeyUnique(user, dto.name)

Expand Down Expand Up @@ -60,6 +68,16 @@ export class ApiKeyService {
}
}

/**
* Updates an existing API key of the given user.
*
* @throws `ConflictException` if the API key name already exists.
* @throws `NotFoundException` if the API key with the given slug does not exist.
* @param user The user to update the API key for.
* @param apiKeySlug The slug of the API key to update.
* @param dto The data to update the API key with.
* @returns The updated API key.
*/
async updateApiKey(
user: User,
apiKeySlug: ApiKey['slug'],
Expand Down Expand Up @@ -103,6 +121,13 @@ export class ApiKeyService {
return updatedApiKey
}

/**
* Deletes an API key of the given user.
*
* @throws `NotFoundException` if the API key with the given slug does not exist.
* @param user The user to delete the API key for.
* @param apiKeySlug The slug of the API key to delete.
*/
async deleteApiKey(user: User, apiKeySlug: ApiKey['slug']) {
try {
await this.prisma.apiKey.delete({
Expand All @@ -118,6 +143,14 @@ export class ApiKeyService {
this.logger.log(`User ${user.id} deleted API key ${apiKeySlug}`)
}

/**
* Retrieves an API key of the given user by slug.
*
* @throws `NotFoundException` if the API key with the given slug does not exist.
* @param user The user to retrieve the API key for.
* @param apiKeySlug The slug of the API key to retrieve.
* @returns The API key with the given slug.
*/
async getApiKeyBySlug(user: User, apiKeySlug: ApiKey['slug']) {
const apiKey = await this.prisma.apiKey.findUnique({
where: {
Expand All @@ -134,6 +167,17 @@ export class ApiKeyService {
return apiKey
}

/**
* Retrieves all API keys of the given user.
*
* @param user The user to retrieve the API keys for.
* @param page The page number to retrieve.
* @param limit The maximum number of items to retrieve per page.
* @param sort The column to sort by.
* @param order The order to sort by.
* @param search The search string to filter the API keys by.
* @returns The API keys of the given user, filtered by the search string.
*/
async getAllApiKeysOfUser(
user: User,
page: number,
Expand All @@ -158,6 +202,13 @@ export class ApiKeyService {
})
}

/**
* Checks if an API key with the given name already exists for the given user.
*
* @throws `ConflictException` if the API key already exists.
* @param user The user to check for.
* @param apiKeyName The name of the API key to check.
*/
private async isApiKeyUnique(user: User, apiKeyName: string) {
let apiKey: ApiKey | null = null

Expand Down
8 changes: 8 additions & 0 deletions apps/api/src/auth/guard/admin/admin.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { Observable } from 'rxjs'

@Injectable()
export class AdminGuard implements CanActivate {
/**
* This guard will check if the request's user is an admin.
* If the user is an admin, then the canActivate function will return true.
* If the user is not an admin, then the canActivate function will return false.
*
* @param context The ExecutionContext for the request.
* @returns A boolean indicating whether or not the request's user is an admin.
*/
canActivate(
context: ExecutionContext
): boolean | Promise<boolean> | Observable<boolean> {
Expand Down
19 changes: 19 additions & 0 deletions apps/api/src/auth/guard/api-key/api-key.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ import { IS_PUBLIC_KEY } from '@/decorators/public.decorator'
export class ApiKeyGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}

/**
* This method will check if the user is authenticated via an API key,
* and if the API key has the required authorities for the route.
*
* If the user is not authenticated via an API key, or if the API key does not have the required authorities,
* then the canActivate method will return true.
*
* If the user is authenticated via an API key, and the API key has the required authorities,
* then the canActivate method will return true.
*
* If the user is authenticated via an API key, but the API key does not have the required authorities,
* then the canActivate method will throw an UnauthorizedException.
*
* If the user is authenticated via an API key, but the API key is forbidden for the route,
* then the canActivate method will throw an UnauthorizedException.
*
* @param context The ExecutionContext for the request.
* @returns A boolean indicating whether or not the user is authenticated via an API key and has the required authorities for the route.
*/
canActivate(
context: ExecutionContext
): boolean | Promise<boolean> | Observable<boolean> {
Expand Down
9 changes: 9 additions & 0 deletions apps/api/src/auth/guard/auth/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ export class AuthGuard implements CanActivate {
private readonly cache: CacheService
) {}

/**
* This method is called by NestJS every time an HTTP request is made to an endpoint
* that is protected by this guard. It checks if the request is authenticated and if
* the user is active. If the user is not active, it throws an UnauthorizedException.
* If the onboarding is not finished, it throws an UnauthorizedException.
* @param context The ExecutionContext object that contains information about the
* request.
* @returns A boolean indicating if the request is authenticated and the user is active.
*/
async canActivate(context: ExecutionContext): Promise<boolean> {
// Get the kind of route. Routes marked with the @Public() decorator are public.
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
Expand Down
36 changes: 36 additions & 0 deletions apps/api/src/auth/service/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export class AuthService {
this.logger = new Logger(AuthService.name)
}

/**
* Sends a login code to the given email address
* @throws {BadRequestException} If the email address is invalid
* @param email The email address to send the login code to
*/
async sendOtp(email: string): Promise<void> {
if (!email || !email.includes('@')) {
this.logger.error(`Invalid email address: ${email}`)
Expand All @@ -45,6 +50,14 @@ export class AuthService {
}

/* istanbul ignore next */
/**
* Validates a login code sent to the given email address
* @throws {NotFoundException} If the user is not found
* @throws {UnauthorizedException} If the login code is invalid
* @param email The email address the login code was sent to
* @param otp The login code to validate
* @returns An object containing the user and a JWT token
*/
async validateOtp(
email: string,
otp: string
Expand Down Expand Up @@ -93,6 +106,14 @@ export class AuthService {
}

/* istanbul ignore next */
/**
* Handles a login with an OAuth provider
* @param email The email of the user
* @param name The name of the user
* @param profilePictureUrl The profile picture URL of the user
* @param oauthProvider The OAuth provider used
* @returns An object containing the user and a JWT token
*/
async handleOAuthLogin(
email: string,
name: string,
Expand All @@ -116,6 +137,10 @@ export class AuthService {
}

/* istanbul ignore next */
/**
* Cleans up expired OTPs every hour
* @throws {PrismaError} If there is an error deleting expired OTPs
*/
@Cron(CronExpression.EVERY_HOUR)
async cleanUpExpiredOtps() {
try {
Expand All @@ -133,6 +158,17 @@ export class AuthService {
}
}

/**
* Creates a user if it doesn't exist yet. If the user has signed up with a
* different authentication provider, it throws an UnauthorizedException.
* @param email The email address of the user
* @param authProvider The AuthProvider used
* @param name The name of the user
* @param profilePictureUrl The profile picture URL of the user
* @returns The user
* @throws {UnauthorizedException} If the user has signed up with a different
* authentication provider
*/
private async createUserIfNotExists(
email: string,
authProvider: AuthProvider,
Expand Down
115 changes: 115 additions & 0 deletions apps/api/src/environment/service/environment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,31 @@ export class EnvironmentService {
private readonly authorityCheckerService: AuthorityCheckerService
) {}

/**
* Creates a new environment in the given project.
*
* This endpoint requires the following authorities:
* - `CREATE_ENVIRONMENT` on the project
* - `READ_ENVIRONMENT` on the project
* - `READ_PROJECT` on the project
*
* If the user does not have the required authorities, a `ForbiddenException` is thrown.
*
* If an environment with the same name already exists in the project, a `ConflictException` is thrown.
*
* The created environment is returned, with the slug generated using the `name` and `ENVIRONMENT` as the entity type.
*
* An event of type `ENVIRONMENT_ADDED` is created, with the following metadata:
* - `environmentId`: The ID of the created environment
* - `name`: The name of the created environment
* - `projectId`: The ID of the project in which the environment was created
* - `projectName`: The name of the project in which the environment was created
*
* @param user The user that is creating the environment
* @param dto The data for the new environment
* @param projectSlug The slug of the project in which to create the environment
* @returns The created environment
*/
async createEnvironment(
user: User,
dto: CreateEnvironment,
Expand Down Expand Up @@ -96,6 +121,31 @@ export class EnvironmentService {
return environment
}

/**
* Updates an environment in the given project.
*
* This endpoint requires the following authorities:
* - `UPDATE_ENVIRONMENT` on the environment
* - `READ_ENVIRONMENT` on the environment
* - `READ_PROJECT` on the project
*
* If the user does not have the required authorities, a `ForbiddenException` is thrown.
*
* If an environment with the same name already exists in the project, a `ConflictException` is thrown.
*
* The updated environment is returned, with the slug generated using the `name` and `ENVIRONMENT` as the entity type.
*
* An event of type `ENVIRONMENT_UPDATED` is created, with the following metadata:
* - `environmentId`: The ID of the updated environment
* - `name`: The name of the updated environment
* - `projectId`: The ID of the project in which the environment was updated
* - `projectName`: The name of the project in which the environment was updated
*
* @param user The user that is updating the environment
* @param dto The data for the updated environment
* @param environmentSlug The slug of the environment to update
* @returns The updated environment
*/
async updateEnvironment(
user: User,
dto: UpdateEnvironment,
Expand Down Expand Up @@ -157,6 +207,19 @@ export class EnvironmentService {
return updatedEnvironment
}

/**
* Gets an environment by its slug.
*
* This endpoint requires the `READ_ENVIRONMENT` authority on the environment.
*
* If the user does not have the required authority, a `ForbiddenException` is thrown.
*
* The returned environment object does not include the project property.
*
* @param user The user that is requesting the environment
* @param environmentSlug The slug of the environment to get
* @returns The environment
*/
async getEnvironment(user: User, environmentSlug: Environment['slug']) {
const environment =
await this.authorityCheckerService.checkAuthorityOverEnvironment({
Expand All @@ -171,6 +234,36 @@ export class EnvironmentService {
return environment
}

/**
* Gets a list of all environments in the given project.
*
* This endpoint requires the `READ_ENVIRONMENT` authority on the project.
*
* If the user does not have the required authority, a `ForbiddenException` is thrown.
*
* The returned list of environments is paginated and sorted according to the provided parameters.
*
* The metadata object contains the following properties:
* - `href`: The URL to the current page
* - `next`: The URL to the next page (if it exists)
* - `prev`: The URL to the previous page (if it exists)
* - `totalPages`: The total number of pages
* - `totalItems`: The total number of items
* - `limit`: The maximum number of items per page
* - `page`: The current page number
* - `sort`: The sort field
* - `order`: The sort order
* - `search`: The search query
*
* @param user The user that is requesting the environments
* @param projectSlug The slug of the project in which to get the environments
* @param page The page number
* @param limit The maximum number of items per page
* @param sort The sort field
* @param order The sort order
* @param search The search query
* @returns An object with a list of environments and metadata
*/
async getEnvironmentsOfProject(
user: User,
projectSlug: Project['slug'],
Expand Down Expand Up @@ -239,6 +332,23 @@ export class EnvironmentService {
return { items, metadata }
}

/**
* Deletes an environment in a project.
*
* This endpoint requires the `DELETE_ENVIRONMENT` authority on the environment.
*
* If the user does not have the required authority, a `ForbiddenException` is thrown.
*
* If this is the only existing environment in the project, a `BadRequestException` is thrown.
*
* An event of type `ENVIRONMENT_DELETED` is created, with the following metadata:
* - `environmentId`: The ID of the deleted environment
* - `name`: The name of the deleted environment
* - `projectId`: The ID of the project in which the environment was deleted
*
* @param user The user that is deleting the environment
* @param environmentSlug The slug of the environment to delete
*/
async deleteEnvironment(user: User, environmentSlug: Environment['slug']) {
const environment =
await this.authorityCheckerService.checkAuthorityOverEnvironment({
Expand Down Expand Up @@ -289,6 +399,11 @@ export class EnvironmentService {
)
}

/**
* Checks if an environment with the given name already exists in the given project.
* @throws ConflictException if an environment with the given name already exists
* @private
*/
private async environmentExists(name: Environment['name'], project: Project) {
const { id: projectId, slug } = project

Expand Down
5 changes: 5 additions & 0 deletions apps/api/src/feedback/service/feedback.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ export class FeedbackService {
@Inject(MAIL_SERVICE) private readonly mailService: IMailService
) {}

/**
* Registers a feedback to be sent to the admin's email.
* @param feedback The feedback to be sent.
* @throws {BadRequestException} If the feedback is null or empty.
*/
async registerFeedback(feedback: string): Promise<void> {
if (!feedback || feedback.trim().length === 0) {
throw new BadRequestException('Feedback cannot be null or empty')
Expand Down
Loading

0 comments on commit cabc5f1

Please sign in to comment.