Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reef admin relationships #128

Merged
merged 15 commits into from
Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/api/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@
}
]
}
},
{
"files": ["src/**/*.entity.{ts,js}"],
"rules": {
"import/no-cycle": "off"
}
}
]
}
76 changes: 76 additions & 0 deletions packages/api/migration/1599586648784-ReefAdminManyToMany.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class ReefAdminManyToMany1599586648784 implements MigrationInterface {
name = 'ReefAdminManyToMany1599586648784';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "reef" DROP CONSTRAINT "FK_dc56bfd6bfcd1f221ec83885294"`,
);
await queryRunner.query(
`
CREATE TABLE "users_administered_reefs_reef" (
"reef_id" integer NOT NULL,
"users_id" integer NOT NULL,
CONSTRAINT "PK_21f162e26e837a19d1e1accd1cd" PRIMARY KEY ("reef_id", "users_id")
)
`,
);
await queryRunner.query(
`CREATE INDEX "IDX_088a629ef23eb9eba6ac857ed6" ON "users_administered_reefs_reef" ("reef_id") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_da52b9542bf7df43f4840ae439" ON "users_administered_reefs_reef" ("users_id") `,
);
await queryRunner.query(`ALTER TABLE "reef" DROP COLUMN "admin_id"`);
await queryRunner.query(
`
ALTER TABLE "users_administered_reefs_reef" ADD CONSTRAINT "FK_088a629ef23eb9eba6ac857ed62"
FOREIGN KEY ("reef_id") REFERENCES "reef"("id") ON DELETE CASCADE ON UPDATE NO ACTION
`,
);
await queryRunner.query(
`
ALTER TABLE "users_administered_reefs_reef" ADD CONSTRAINT "FK_da52b9542bf7df43f4840ae4394"
FOREIGN KEY ("users_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION
`,
);
await queryRunner.query(
`ALTER TABLE "reef_application" DROP CONSTRAINT "FK_77d33d9b9602120cd1529312e77"`,
);
await queryRunner.query(
`ALTER TABLE "reef_application" DROP CONSTRAINT "UQ_77d33d9b9602120cd1529312e77"`,
);
await queryRunner.query(
`ALTER TABLE "reef_application" ADD CONSTRAINT "FK_77d33d9b9602120cd1529312e77" FOREIGN KEY ("reef_id") REFERENCES "reef"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "users_administered_reefs_reef" DROP CONSTRAINT "FK_da52b9542bf7df43f4840ae4394"`,
);
await queryRunner.query(
`ALTER TABLE "users_administered_reefs_reef" DROP CONSTRAINT "FK_088a629ef23eb9eba6ac857ed62"`,
);
await queryRunner.query(`ALTER TABLE "reef" ADD "admin_id" integer`);
await queryRunner.query(`DROP INDEX "IDX_da52b9542bf7df43f4840ae439"`);
await queryRunner.query(`DROP INDEX "IDX_088a629ef23eb9eba6ac857ed6"`);
await queryRunner.query(`DROP TABLE "users_administered_reefs_reef"`);
await queryRunner.query(
`
ALTER TABLE "reef" ADD CONSTRAINT "FK_dc56bfd6bfcd1f221ec83885294"
FOREIGN KEY ("admin_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION
`,
);
await queryRunner.query(
`ALTER TABLE "reef_application" DROP CONSTRAINT "FK_77d33d9b9602120cd1529312e77"`,
);
await queryRunner.query(
`ALTER TABLE "reef_application" ADD CONSTRAINT "UQ_77d33d9b9602120cd1529312e77" UNIQUE ("reef_id")`,
);
await queryRunner.query(
`ALTER TABLE "reef_application" ADD CONSTRAINT "FK_77d33d9b9602120cd1529312e77" FOREIGN KEY ("reef_id") REFERENCES "reef"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
}
3 changes: 0 additions & 3 deletions packages/api/src/auth/auth.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import { FirebaseAuthGuard } from './firebase-auth.guard';
import { LevelsGuard } from './levels.guard';

export const Auth = (...levels: AdminLevel[]) => {
if (!levels) {
return UseGuards(FirebaseAuthGuard);
}
return applyDecorators(
SetMetadata('levels', levels),
UseGuards(FirebaseAuthGuard, LevelsGuard),
Expand Down
23 changes: 21 additions & 2 deletions packages/api/src/auth/firebase-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
import { Injectable } from '@nestjs/common';
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { of } from 'rxjs';

@Injectable()
export class FirebaseAuthGuard extends AuthGuard('custom') {}
export class FirebaseAuthGuard extends AuthGuard('custom') {
constructor(private reflector: Reflector) {
super();
}

canActivate(context: ExecutionContext) {
const isPublic = this.reflector.get<boolean>(
'isPublic',
context.getHandler(),
);

if (isPublic) {
return of(true);
}

return super.canActivate(context);
}
}
48 changes: 48 additions & 0 deletions packages/api/src/auth/is-reef-admin.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Reflector } from '@nestjs/core';
import { Repository } from 'typeorm';
import { User, AdminLevel } from '../users/users.entity';
import { Reef } from '../reefs/reefs.entity';

export class IsReefAdminGuard implements CanActivate {
constructor(
@InjectRepository(Reef)
private reefRepository: Repository<Reef>,

private reflector: Reflector,
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.get<boolean>(
'isPublic',
context.getHandler(),
);

if (isPublic) {
return true;
}

const request = context.switchToHttp().getRequest();
const { user }: { user: User } = request;
const reefId = parseInt(request.params.reef_id, 10);

if (user.adminLevel === AdminLevel.SuperAdmin) {
return true;
}

if (!Number.isNaN(reefId) && user.adminLevel === AdminLevel.ReefManager) {
const isReefAdmin = await this.reefRepository
.createQueryBuilder('reef')
.innerJoin('reef.admins', 'admins', 'admins.id = :userId', {
userId: user.id,
})
.andWhere('reef.id = :reefId', { reefId })
.getOne();

return !!isReefAdmin;
}

return false;
}
}
10 changes: 9 additions & 1 deletion packages/api/src/auth/levels.guard.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AdminLevel } from '../users/users.entity';
import { AuthRequest } from './auth.types';

@Injectable()
export class LevelsGuard implements CanActivate {
constructor(private reflector: Reflector) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.get<boolean>(
'isPublic',
context.getHandler(),
);
if (isPublic) {
return true;
}
const levels = this.reflector.get<AdminLevel[]>(
'levels',
context.getHandler(),
);
if (!levels || !levels.length) {
return true;
}
const request = context.switchToHttp().getRequest();
const request = context.switchToHttp().getRequest<AuthRequest>();
const { user } = request;
const hasAccess = levels.findIndex((l) => l === user.adminLevel) !== -1;
return hasAccess;
Expand Down
6 changes: 6 additions & 0 deletions packages/api/src/auth/override-level-access.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { applyDecorators, SetMetadata } from '@nestjs/common';
import { AdminLevel } from '../users/users.entity';

export const OverrideLevelAccess = (...levels: AdminLevel[]) => {
return applyDecorators(SetMetadata('levels', levels));
};
5 changes: 5 additions & 0 deletions packages/api/src/auth/public.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { applyDecorators, SetMetadata } from '@nestjs/common';

export const Public = () => {
return applyDecorators(SetMetadata('isPublic', true));
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
ManyToOne,
CreateDateColumn,
UpdateDateColumn,
JoinColumn,
} from 'typeorm';
import { Exclude, Expose } from 'class-transformer';
import { Reef } from '../reefs/reefs.entity';
Expand Down Expand Up @@ -41,8 +39,7 @@ export class ReefApplication {
@UpdateDateColumn()
updatedAt: Date;

@OneToOne(() => Reef, { onDelete: 'CASCADE' })
@JoinColumn()
@ManyToOne(() => Reef, { onDelete: 'CASCADE' })
reef: Reef;

@ManyToOne(() => User, { onDelete: 'CASCADE' })
Expand Down
7 changes: 4 additions & 3 deletions packages/api/src/reef-pois/reef-pois.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,34 @@ import { FilterReefPoiDto } from './dto/filter-reef-poi.dto';
import { UpdateReefPoiDto } from './dto/update-reef-poi.dto';
import { AdminLevel } from '../users/users.entity';
import { Auth } from '../auth/auth.decorator';
import { Public } from '../auth/public.decorator';

@Auth(AdminLevel.ReefManager, AdminLevel.SuperAdmin)
@Controller('pois')
export class ReefPoisController {
constructor(private poisService: ReefPoisService) {}

@Auth(AdminLevel.ReefManager, AdminLevel.SuperAdmin)
@Post()
create(
@Body() createReefPoiDto: CreateReefPoiDto,
): Promise<ReefPointOfInterest> {
return this.poisService.create(createReefPoiDto);
}

@Public()
@Get()
find(
@Query() filterReefPoiDto: FilterReefPoiDto,
): Promise<ReefPointOfInterest[]> {
return this.poisService.find(filterReefPoiDto);
}

@Public()
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number): Promise<ReefPointOfInterest> {
return this.poisService.findOne(id);
}

@Auth(AdminLevel.ReefManager, AdminLevel.SuperAdmin)
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
Expand All @@ -50,7 +52,6 @@ export class ReefPoisController {
return this.poisService.update(id, updateReefPoiDto);
}

@Auth(AdminLevel.ReefManager, AdminLevel.SuperAdmin)
@Delete(':id')
delete(@Param('id', ParseIntPipe) id: number): Promise<void> {
return this.poisService.delete(id);
Expand Down
2 changes: 0 additions & 2 deletions packages/api/src/reef-pois/reef-pois.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import {
UpdateDateColumn,
OneToMany,
} from 'typeorm';
// eslint-disable-next-line import/no-cycle
import { Reef } from '../reefs/reefs.entity';
// eslint-disable-next-line import/no-cycle
import { SurveyMedia } from '../surveys/survey-media.entity';

@Entity()
Expand Down
1 change: 0 additions & 1 deletion packages/api/src/reefs/daily-data.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
// eslint-disable-next-line import/no-cycle
import { Reef } from './reefs.entity';

@Entity()
Expand Down
20 changes: 5 additions & 15 deletions packages/api/src/reefs/reefs.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,39 @@ import { Reef } from './reefs.entity';
import { CreateReefDto } from './dto/create-reef.dto';
import { FilterReefDto } from './dto/filter-reef.dto';
import { UpdateReefDto } from './dto/update-reef.dto';
import surveys from '../../mock_response/survey_data.json';
import { AdminLevel } from '../users/users.entity';
import { Auth } from '../auth/auth.decorator';
import { Public } from '../auth/public.decorator';

@Auth(AdminLevel.ReefManager, AdminLevel.SuperAdmin)
@Controller('reefs')
export class ReefsController {
constructor(private reefsService: ReefsService) {}

@Auth(AdminLevel.ReefManager, AdminLevel.SuperAdmin)
@Post()
create(@Body() createReefDto: CreateReefDto): Promise<Reef> {
return this.reefsService.create(createReefDto);
}

@Public()
@Get()
find(@Query() filterReefDto: FilterReefDto): Promise<Reef[]> {
return this.reefsService.find(filterReefDto);
}

@Public()
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number): Promise<Reef> {
return this.reefsService.findOne(id);
}

@Public()
@Get(':id/daily_data')
// eslint-disable-next-line no-unused-vars
findDailyData(@Param('id') id: number) {
return this.reefsService.findDailyData(id);
}

@Get(':id/surveys/:poi')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this not needed anymore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I see it, this endpoint was using a mock response which is already implemented in the survey details endpoint (GET /reefs/:reef_id/surveys/:id). The only difference was that I fetch all POIs of a survey and this was designed to fetch only one POI of a survey. I don't see any use for it anymore.
Also it was conflicting with the new endpoints (surveys endpoints with the updated prefix)

findSurveys(@Param('id') id: string, @Param('poi') poi: string) {
return surveys.map((survey) => ({
...survey,
images: survey.images.filter(
(image) => image.poi_label_id === parseInt(poi, 10),
),
}));
// return this.reefRepository.findOneReef(id);
}

@Auth(AdminLevel.ReefManager, AdminLevel.SuperAdmin)
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
Expand All @@ -64,7 +55,6 @@ export class ReefsController {
return this.reefsService.update(id, updateReefDto);
}

@Auth(AdminLevel.ReefManager, AdminLevel.SuperAdmin)
@Delete(':id')
delete(@Param('id', ParseIntPipe) id: number): Promise<void> {
return this.reefsService.delete(id);
Expand Down
Loading