Skip to content

Commit

Permalink
N21-1764 Fix env variable for systems v3 endpoint (#4774)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinOehlerkingCap authored Feb 21, 2024
1 parent 882007c commit eb1b674
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ export class SystemController {
@ApiOperation({ summary: 'Deletes a system.' })
@HttpCode(HttpStatus.NO_CONTENT)
async deleteSystem(@CurrentUser() currentUser: ICurrentUser, @Param() params: SystemIdParams): Promise<void> {
await this.systemUc.delete(currentUser.userId, params.systemId);
await this.systemUc.delete(currentUser.userId, currentUser.schoolId, params.systemId);
}
}
1 change: 1 addition & 0 deletions apps/server/src/modules/system/domain/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { System, SystemProps } from './system.do';
export { LdapConfig } from './ldap-config';
export { OauthConfig } from './oauth-config';
export { SystemType } from './system-type.enum';
13 changes: 13 additions & 0 deletions apps/server/src/modules/system/domain/system-type.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export enum SystemType {
OAUTH = 'oauth',
LDAP = 'ldap',
OIDC = 'oidc',
TSP_BASE = 'tsp-base',
TSP_SCHOOL = 'tsp-school',
// Legacy
LOCAL = 'local',
ISERV = 'iserv',
LERNSAX = 'lernsax',
ITSLEARNING = 'itslearning',
MOODLE = 'moodle',
}
5 changes: 5 additions & 0 deletions apps/server/src/modules/system/domain/system.do.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object';
import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy';
import { LdapConfig } from './ldap-config';
import { OauthConfig } from './oauth-config';
import { SystemType } from './system-type.enum';

export interface SystemProps extends AuthorizableObject {
type: string;
Expand All @@ -22,6 +23,10 @@ export interface SystemProps extends AuthorizableObject {
}

export class System extends DomainObject<SystemProps> {
get type(): SystemType | string {
return this.props.type;
}

get ldapConfig(): LdapConfig | undefined {
return this.props.ldapConfig;
}
Expand Down
3 changes: 2 additions & 1 deletion apps/server/src/modules/system/system-api.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { AuthorizationModule } from '@modules/authorization';
import { LegacySchoolModule } from '@modules/legacy-school';
import { SystemController } from '@modules/system/controller/system.controller';
import { SystemUc } from '@modules/system/uc/system.uc';
import { Module } from '@nestjs/common';
import { SystemModule } from './system.module';

@Module({
imports: [SystemModule, AuthorizationModule],
imports: [SystemModule, AuthorizationModule, LegacySchoolModule],
providers: [SystemUc],
controllers: [SystemController],
})
Expand Down
87 changes: 77 additions & 10 deletions apps/server/src/modules/system/uc/system.uc.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { ObjectId } from '@mikro-orm/mongodb';
import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization';
import { LegacySchoolService } from '@modules/legacy-school';
import { SystemDto } from '@modules/system/service/dto/system.dto';
import { SystemUc } from '@modules/system/uc/system.uc';
import { Test, TestingModule } from '@nestjs/testing';
import { EntityNotFoundError } from '@shared/common';
import { NotFoundLoggableException } from '@shared/common/loggable-exception';
import { LegacySchoolDo } from '@shared/domain/domainobject';
import { SystemEntity } from '@shared/domain/entity';
import { Permission } from '@shared/domain/interface';
import { EntityId, SystemTypeEnum } from '@shared/domain/types';
import { setupEntities, systemEntityFactory, systemFactory, userFactory } from '@shared/testing';
import { AuthorizationContextBuilder, AuthorizationService } from '../../authorization';
import { legacySchoolDoFactory, setupEntities, systemEntityFactory, systemFactory, userFactory } from '@shared/testing';
import { SystemType } from '../domain';
import { SystemMapper } from '../mapper';
import { LegacySystemService, SystemService } from '../service';

Expand All @@ -25,6 +28,7 @@ describe('SystemUc', () => {
let legacySystemService: DeepMocked<LegacySystemService>;
let systemService: DeepMocked<SystemService>;
let authorizationService: DeepMocked<AuthorizationService>;
let schoolService: DeepMocked<LegacySchoolService>;

beforeAll(async () => {
await setupEntities();
Expand All @@ -44,13 +48,18 @@ describe('SystemUc', () => {
provide: AuthorizationService,
useValue: createMock<AuthorizationService>(),
},
{
provide: LegacySchoolService,
useValue: createMock<LegacySchoolService>(),
},
],
}).compile();

systemUc = module.get(SystemUc);
legacySystemService = module.get(LegacySystemService);
systemService = module.get(SystemService);
authorizationService = module.get(AuthorizationService);
schoolService = module.get(LegacySchoolService);
});

afterAll(async () => {
Expand Down Expand Up @@ -161,20 +170,29 @@ describe('SystemUc', () => {
const setup = () => {
const user = userFactory.buildWithId();
const system = systemFactory.build();
const otherSystemId = new ObjectId().toHexString();
const school = legacySchoolDoFactory.build({
systems: [system.id, otherSystemId],
ldapLastSync: new Date().toString(),
externalId: 'test',
});

systemService.findById.mockResolvedValueOnce(system);
authorizationService.getUserWithPermissions.mockResolvedValueOnce(user);
schoolService.getSchoolById.mockResolvedValueOnce(school);

return {
user,
system,
school,
otherSystemId,
};
};

it('should check the permission', async () => {
const { user, system } = setup();

await systemUc.delete(user.id, system.id);
await systemUc.delete(user.id, user.school.id, system.id);

expect(authorizationService.checkPermission).toHaveBeenCalledWith(
user,
Expand All @@ -186,10 +204,57 @@ describe('SystemUc', () => {
it('should delete the system', async () => {
const { user, system } = setup();

await systemUc.delete(user.id, system.id);
await systemUc.delete(user.id, user.school.id, system.id);

expect(systemService.delete).toHaveBeenCalledWith(system);
});

it('should remove the system from the school', async () => {
const { user, system, school, otherSystemId } = setup();

await systemUc.delete(user.id, user.school.id, system.id);

expect(schoolService.save).toHaveBeenCalledWith(
expect.objectContaining<Partial<LegacySchoolDo>>({
systems: [otherSystemId],
ldapLastSync: undefined,
externalId: school.externalId,
})
);
});
});

describe('when the system is the last ldap system at the school', () => {
const setup = () => {
const user = userFactory.buildWithId();
const system = systemFactory.build({ type: SystemType.LDAP });
const school = legacySchoolDoFactory.build({
systems: [system.id],
ldapLastSync: new Date().toString(),
externalId: 'test',
});

systemService.findById.mockResolvedValueOnce(system);
authorizationService.getUserWithPermissions.mockResolvedValueOnce(user);
schoolService.getSchoolById.mockResolvedValueOnce(school);

return {
user,
system,
};
};

it('should remove the external id of the school', async () => {
const { user, system } = setup();

await systemUc.delete(user.id, user.school.id, system.id);

expect(schoolService.save).toHaveBeenCalledWith(
expect.objectContaining<Partial<LegacySchoolDo>>({
externalId: undefined,
})
);
});
});

describe('when the system does not exist', () => {
Expand All @@ -200,15 +265,17 @@ describe('SystemUc', () => {
it('should throw a not found exception', async () => {
setup();

await expect(systemUc.delete(new ObjectId().toHexString(), new ObjectId().toHexString())).rejects.toThrow(
NotFoundLoggableException
);
await expect(
systemUc.delete(new ObjectId().toHexString(), new ObjectId().toHexString(), new ObjectId().toHexString())
).rejects.toThrow(NotFoundLoggableException);
});

it('should not delete any system', async () => {
setup();

await expect(systemUc.delete(new ObjectId().toHexString(), new ObjectId().toHexString())).rejects.toThrow();
await expect(
systemUc.delete(new ObjectId().toHexString(), new ObjectId().toHexString(), new ObjectId().toHexString())
).rejects.toThrow();

expect(systemService.delete).not.toHaveBeenCalled();
});
Expand Down Expand Up @@ -236,13 +303,13 @@ describe('SystemUc', () => {
it('should throw an error', async () => {
const { user, system, error } = setup();

await expect(systemUc.delete(user.id, system.id)).rejects.toThrow(error);
await expect(systemUc.delete(user.id, user.school.id, system.id)).rejects.toThrow(error);
});

it('should not delete any system', async () => {
const { user, system } = setup();

await expect(systemUc.delete(user.id, system.id)).rejects.toThrow();
await expect(systemUc.delete(user.id, user.school.id, system.id)).rejects.toThrow();

expect(systemService.delete).not.toHaveBeenCalled();
});
Expand Down
28 changes: 23 additions & 5 deletions apps/server/src/modules/system/uc/system.uc.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization';
import { LegacySchoolService } from '@modules/legacy-school';
import { Injectable } from '@nestjs/common';
import { EntityNotFoundError } from '@shared/common';
import { NotFoundLoggableException } from '@shared/common/loggable-exception';
import { LegacySchoolDo } from '@shared/domain/domainobject';
import { SystemEntity, User } from '@shared/domain/entity';
import { Permission } from '@shared/domain/interface';
import { EntityId, SystemType, SystemTypeEnum } from '@shared/domain/types';
import { System } from '../domain';
import { EntityId, SystemTypeEnum } from '@shared/domain/types';
import { System, SystemType } from '../domain';
import { LegacySystemService, SystemDto, SystemService } from '../service';

@Injectable()
export class SystemUc {
constructor(
private readonly legacySystemService: LegacySystemService,
private readonly systemService: SystemService,
private readonly authorizationService: AuthorizationService
private readonly authorizationService: AuthorizationService,
private readonly schoolService: LegacySchoolService
) {}

async findByFilter(type?: SystemType, onlyOauth = false): Promise<SystemDto[]> {
async findByFilter(type?: SystemTypeEnum, onlyOauth = false): Promise<SystemDto[]> {
let systems: SystemDto[];

if (onlyOauth) {
Expand All @@ -40,7 +43,7 @@ export class SystemUc {
return system;
}

async delete(userId: EntityId, systemId: EntityId): Promise<void> {
async delete(userId: EntityId, schoolId: EntityId, systemId: EntityId): Promise<void> {
const system: System | null = await this.systemService.findById(systemId);

if (!system) {
Expand All @@ -55,5 +58,20 @@ export class SystemUc {
);

await this.systemService.delete(system);

await this.removeSystemFromSchool(schoolId, system);
}

private async removeSystemFromSchool(schoolId: string, system: System) {
const school: LegacySchoolDo = await this.schoolService.getSchoolById(schoolId);

school.systems = school.systems?.filter((schoolSystemId: string) => schoolSystemId !== system.id);
school.ldapLastSync = undefined;

if (system.type === SystemType.LDAP && school.systems?.length === 0) {
school.externalId = undefined;
}

await this.schoolService.save(school);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export class LegacySchoolDo extends BaseDO {
// TODO: N21-990 Refactoring: Create domain objects for schoolYear and federalState
federalState: FederalStateEntity;

ldapLastSync?: string;

constructor(params: LegacySchoolDo) {
super();
this.id = params.id;
Expand All @@ -44,5 +46,6 @@ export class LegacySchoolDo extends BaseDO {
this.systems = params.systems;
this.userLoginMigrationId = params.userLoginMigrationId;
this.federalState = params.federalState;
this.ldapLastSync = params.ldapLastSync;
}
}
5 changes: 5 additions & 0 deletions apps/server/src/shared/domain/entity/school.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface SchoolProperties {
fileStorageType?: FileStorageType;
language?: string;
timezone?: string;
ldapLastSync?: string;
}

@Embeddable()
Expand Down Expand Up @@ -77,6 +78,9 @@ export class SchoolEntity extends BaseEntityWithTimestamps {
@Property({ nullable: true, fieldName: 'ldapSchoolIdentifier' })
externalId?: string;

@Property({ nullable: true })
ldapLastSync?: string;

@Property({ nullable: true })
previousExternalId?: string;

Expand Down Expand Up @@ -159,5 +163,6 @@ export class SchoolEntity extends BaseEntityWithTimestamps {
this.fileStorageType = props.fileStorageType;
this.language = props.language;
this.timezone = props.timezone;
this.ldapLastSync = props.ldapLastSync;
}
}
2 changes: 0 additions & 2 deletions apps/server/src/shared/domain/types/system.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,3 @@ export enum SystemTypeEnum {
OAUTH = 'oauth', // systems for direct authentication via OAuth,
OIDC = 'oidc', // systems for direct authentication via OpenID Connect,
}

export type SystemType = SystemTypeEnum;
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
userLoginMigrationFactory,
} from '@shared/testing';
import { LegacyLogger } from '@src/core/logger';
import { LegacySchoolRepo } from '..';
import { LegacySchoolRepo } from './legacy-school.repo';

describe('LegacySchoolRepo', () => {
let module: TestingModule;
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/shared/repo/school/legacy-school.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class LegacySchoolRepo extends BaseDORepo<LegacySchoolDo, SchoolEntity> {
systems: entity.systems.isInitialized() ? entity.systems.getItems().map((system: SystemEntity) => system.id) : [],
userLoginMigrationId: entity.userLoginMigration?.id,
federalState: entity.federalState,
ldapLastSync: entity.ldapLastSync,
});
}

Expand All @@ -71,6 +72,7 @@ export class LegacySchoolRepo extends BaseDORepo<LegacySchoolDo, SchoolEntity> {
? this._em.getReference(UserLoginMigrationEntity, entityDO.userLoginMigrationId)
: undefined,
federalState: entityDO.federalState,
ldapLastSync: entityDO.ldapLastSync,
};
}
}
5 changes: 5 additions & 0 deletions config/default.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,11 @@
"default": false,
"description": "Enables groups of type class in courses"
},
"FEATURE_NEST_SYSTEMS_API_ENABLED": {
"type": "boolean",
"default": true,
"description": "Uses the v3 api over the v1 api for systems"
},
"FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED": {
"type": "boolean",
"default": false,
Expand Down
1 change: 1 addition & 0 deletions src/services/config/publicAppConfigService.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const exposedVars = [
'TLDRAW__ASSETS_ENABLED',
'TLDRAW__ASSETS_MAX_SIZE',
'TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST',
'FEATURE_NEST_SYSTEMS_API_ENABLED',
];

/**
Expand Down

0 comments on commit eb1b674

Please sign in to comment.