Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Commit

Permalink
✨ Support creating groups, memberships
Browse files Browse the repository at this point in the history
  • Loading branch information
AnandChowdhary committed Oct 24, 2020
1 parent 26c0c0e commit a642b7e
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 17 deletions.
24 changes: 12 additions & 12 deletions src/modules/auth/auth.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,55 +24,55 @@ export class RegisterDto {

@IsBoolean()
@IsOptional()
checkLocationOnLogin: boolean;
checkLocationOnLogin?: boolean;

@IsString()
@Length(2, 2)
@IsOptional()
countryCode: string;
countryCode?: string;

@IsString()
@IsIn(['MALE', 'FEMALE', 'NONBINARY', 'UNKNOWN'])
@IsOptional()
gender: 'MALE' | 'FEMALE' | 'NONBINARY' | 'UNKNOWN';
gender?: 'MALE' | 'FEMALE' | 'NONBINARY' | 'UNKNOWN';

@IsIn(['ACCOUNT', 'UPDATES', 'PROMOTIONS'])
@IsOptional()
notificationEmails: 'ACCOUNT' | 'UPDATES' | 'PROMOTIONS';
notificationEmails?: 'ACCOUNT' | 'UPDATES' | 'PROMOTIONS';

@IsString()
@IsOptional()
password: string | null;
password?: string | null;

@IsLocale()
@IsOptional()
prefersLanguage: string;
prefersLanguage?: string;

@IsString()
@IsIn(['NO_PREFERENCE', 'LIGHT', 'DARK'])
@IsOptional()
prefersColorScheme: 'NO_PREFERENCE' | 'LIGHT' | 'DARK';
prefersColorScheme?: 'NO_PREFERENCE' | 'LIGHT' | 'DARK';

@IsString()
@IsIn(['NO_PREFERENCE', 'REDUCE'])
@IsOptional()
prefersReducedMotion: 'NO_PREFERENCE' | 'REDUCE';
prefersReducedMotion?: 'NO_PREFERENCE' | 'REDUCE';

@IsUrl()
@IsOptional()
profilePictureUrl: string;
profilePictureUrl?: string;

@IsString()
@IsOptional()
timezone: string;
timezone?: string;

@IsBoolean()
@IsOptional()
twoFactorEnabled: boolean;
twoFactorEnabled?: boolean;

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

export class ResendEmailVerificationDto {
Expand Down
9 changes: 8 additions & 1 deletion src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ export class AuthService {
const emailSafe = this.users.getSafeEmail(email);
const user = await this.prisma.users.findFirst({
where: { emails: { some: { emailSafe } } },
select: { id: true, password: true },
select: { id: true, password: true, emails: true },
});
if (!user) throw new HttpException('User not found', HttpStatus.NOT_FOUND);
if (!user.emails.find(i => i.emailSafe === emailSafe)?.isVerified)
throw new UnauthorizedException();
if (!password || !user.password)
throw new HttpException(
'Logging in without passwords is not supported',
Expand Down Expand Up @@ -99,6 +101,11 @@ export class AuthService {
'There is no user for this email',
HttpStatus.NOT_FOUND,
);
if (!emailDetails.isVerified)
throw new HttpException(
'This email is already verified',
HttpStatus.BAD_REQUEST,
);
this.email.send({
to: `"${emailDetails.user.name}" <${email}>`,
template: resend
Expand Down
15 changes: 14 additions & 1 deletion src/modules/memberships/memberships-group.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Param,
ParseIntPipe,
Patch,
Post,
Query,
} from '@nestjs/common';
import { memberships } from '@prisma/client';
Expand All @@ -15,13 +16,25 @@ import { OptionalIntPipe } from 'src/pipes/optional-int.pipe';
import { OrderByPipe } from 'src/pipes/order-by.pipe';
import { WherePipe } from 'src/pipes/where.pipe';
import { Scopes } from '../auth/scope.decorator';
import { UpdateMembershipDto } from './memberships.dto';
import {
CreateGroupMembershipDto,
UpdateMembershipDto,
} from './memberships.dto';
import { MembershipsService } from './memberships.service';

@Controller('groups/:groupId/memberships')
export class GroupMembershipController {
constructor(private membershipsService: MembershipsService) {}

@Post()
@Scopes('group-{groupId}:write-membership')
async create(
@Param('groupId', ParseIntPipe) groupId: number,
@Body() data: CreateGroupMembershipDto,
): Promise<Expose<memberships>> {
return this.membershipsService.createGroupMembership(groupId, data);
}

@Get()
@Scopes('group-{groupId}:read-membership')
async getAll(
Expand Down
12 changes: 12 additions & 0 deletions src/modules/memberships/memberships-user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Post,
Query,
} from '@nestjs/common';
import { memberships } from '@prisma/client';
Expand All @@ -13,12 +15,22 @@ import { OptionalIntPipe } from 'src/pipes/optional-int.pipe';
import { OrderByPipe } from 'src/pipes/order-by.pipe';
import { WherePipe } from 'src/pipes/where.pipe';
import { Scopes } from '../auth/scope.decorator';
import { CreateGroupDto } from '../groups/groups.dto';
import { MembershipsService } from './memberships.service';

@Controller('users/:userId/memberships')
export class UserMembershipController {
constructor(private membershipsService: MembershipsService) {}

@Post()
@Scopes('user-{userId}:write-membership')
async create(
@Param('userId', ParseIntPipe) userId: number,
@Body() data: CreateGroupDto,
): Promise<Expose<memberships>> {
return this.membershipsService.createUserMembership(userId, data);
}

@Get()
@Scopes('user-{userId}:read-membership')
async getAll(
Expand Down
23 changes: 22 additions & 1 deletion src/modules/memberships/memberships.dto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import { IsIn, IsOptional, IsString } from 'class-validator';
import {
IsEmail,
IsIn,
IsNotEmpty,
IsOptional,
IsString,
} from 'class-validator';

export class UpdateMembershipDto {
@IsString()
@IsIn(['OWNER', 'ADMIN', 'MEMBER'])
@IsOptional()
role?: 'OWNER' | 'ADMIN' | 'MEMBER';
}

export class CreateGroupMembershipDto {
@IsString()
@IsNotEmpty()
name: string;

@IsEmail()
@IsNotEmpty()
email: string;

@IsString()
@IsIn(['OWNER', 'ADMIN', 'MEMBER'])
@IsOptional()
role?: 'OWNER' | 'ADMIN' | 'MEMBER';
}
7 changes: 7 additions & 0 deletions src/modules/memberships/memberships.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { MembershipRole } from '@prisma/client';

export interface CreateMembershipInput {
name: string;
email: string;
role?: MembershipRole;
}
39 changes: 37 additions & 2 deletions src/modules/memberships/memberships.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,26 @@ import {
UnauthorizedException,
} from '@nestjs/common';
import {
groupsCreateInput,
memberships,
membershipsOrderByInput,
membershipsUpdateInput,
membershipsWhereInput,
membershipsWhereUniqueInput,
} from '@prisma/client';
import { Expose } from 'src/modules/prisma/prisma.interface';
import { AuthService } from '../auth/auth.service';
import { PrismaService } from '../prisma/prisma.service';
import { UsersService } from '../user/user.service';
import { CreateMembershipInput } from './memberships.interface';

@Injectable()
export class MembershipsService {
constructor(private prisma: PrismaService) {}
constructor(
private prisma: PrismaService,
private users: UsersService,
private auth: AuthService,
) {}
async getMemberships(params: {
skip?: number;
take?: number;
Expand Down Expand Up @@ -116,8 +124,35 @@ export class MembershipsService {
return this.prisma.expose<memberships>(membership);
}

async createUserMembership(userId: number, data: groupsCreateInput) {
return this.prisma.memberships.create({
data: {
role: 'OWNER',
user: { connect: { id: userId } },
group: { create: data },
},
});
}

async createGroupMembership(groupId: number, data: CreateMembershipInput) {
const emailSafe = this.users.getSafeEmail(data.email);
let user = this.prisma.expose(
await this.prisma.users.findFirst({
where: { emails: { some: { emailSafe } } },
}),
);
if (!user) user = await this.auth.register(data);
return this.prisma.memberships.create({
data: {
role: data.role,
group: { connect: { id: groupId } },
user: { connect: { id: user.id } },
},
});
}

/** Verify whether a group membership can be deleted */
async verifyDeleteMembership(
private async verifyDeleteMembership(
groupId: number,
membershipId: number,
): Promise<void> {
Expand Down

0 comments on commit a642b7e

Please sign in to comment.