Skip to content

Commit

Permalink
N21-2009 Media source as object (#5061)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinOehlerkingCap authored Jun 14, 2024
1 parent e50d2b4 commit 76bdf79
Show file tree
Hide file tree
Showing 53 changed files with 1,044 additions and 636 deletions.
2 changes: 1 addition & 1 deletion apps/server/src/infra/schulconnex-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ export {
SchulconnexPersonenkontextResponse,
SchulconnexSonstigeGruppenzugehoerigeResponse,
} from './response';
export { schulconnexResponseFactory } from './testing/schulconnex-response-factory';
export { schulconnexResponseFactory, schulconnexLizenzInfoResponseFactory } from './testing';
export { SchulconnexClientConfig } from './schulconnex-client-config';
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ describe(SchulconnexRestClient.name, () => {
describe('when requesting lizenz-info', () => {
const setup = () => {
const accessToken = 'accessToken';
const response: SchulconnexLizenzInfoResponse[] = schulconnexLizenzInfoResponseFactory.build();
const response: SchulconnexLizenzInfoResponse[] = schulconnexLizenzInfoResponseFactory.buildList(1);

httpService.get.mockReturnValueOnce(of(axiosResponseFactory.build({ data: response })));

Expand Down Expand Up @@ -235,7 +235,7 @@ describe(SchulconnexRestClient.name, () => {
const setup = () => {
const accessToken = 'accessToken';
const customUrl = 'https://override.url/lizenz-info';
const response: SchulconnexLizenzInfoResponse[] = schulconnexLizenzInfoResponseFactory.build();
const response: SchulconnexLizenzInfoResponse[] = schulconnexLizenzInfoResponseFactory.buildList(1);

httpService.get.mockReturnValueOnce(of(axiosResponseFactory.build({ data: response })));

Expand Down
1 change: 1 addition & 0 deletions apps/server/src/infra/schulconnex-client/testing/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { schulconnexResponseFactory } from './schulconnex-response-factory';
export { schulconnexLizenzInfoResponseFactory } from './schulconnex-lizenz-info-response-factory';
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Factory } from 'fishery';
import { SchulconnexLizenzInfoActionType, SchulconnexLizenzInfoResponse } from '../response';

export const schulconnexLizenzInfoResponseFactory = Factory.define<SchulconnexLizenzInfoResponse[]>(() => [
{
export const schulconnexLizenzInfoResponseFactory = Factory.define<SchulconnexLizenzInfoResponse>(() => {
return {
target: {
uid: 'bildungscloud',
partOf: '',
Expand All @@ -12,5 +12,5 @@ export const schulconnexLizenzInfoResponseFactory = Factory.define<SchulconnexLi
action: [SchulconnexLizenzInfoActionType.EXECUTE],
},
],
},
]);
};
});
79 changes: 79 additions & 0 deletions apps/server/src/migrations/mikro-orm/Migration20240611081033.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Migration } from '@mikro-orm/migrations-mongodb';
import { ObjectId } from '@mikro-orm/mongodb';

type MediaSourceEntityProps = {
name?: string;

sourceId: string;
};

export class Migration20240611081033 extends Migration {
async up(): Promise<void> {
const uniqueSourceIds: unknown[] = await this.getCollection('user-licenses').distinct('mediaSourceId');

const validSourceIds: string[] = uniqueSourceIds.filter(
(sourceId): sourceId is string => !!sourceId && typeof sourceId === 'string'
);

if (!validSourceIds.length) {
console.info(`no media sources for migration found`);
return;
}

const sourceObjects: MediaSourceEntityProps[] = validSourceIds.map((sourceId: string): MediaSourceEntityProps => {
return {
sourceId,
};
});

const mediaSourcesInsertResult = await this.driver.nativeInsertMany('media-sources', sourceObjects);

console.info(`${mediaSourcesInsertResult.affectedRows} media sources were added`);

const mediaSources = await this.driver.find<{ _id: ObjectId } & MediaSourceEntityProps>('media-sources', {});

let total = 0;
for await (const mediaSource of mediaSources) {
const userLicenses = await this.driver.nativeUpdate(
'user-licenses',
{
mediaSourceId: mediaSource.sourceId,
},
{
mediaSourceId: mediaSource._id,
}
);

total += userLicenses.affectedRows;
console.info(
`${userLicenses.affectedRows} user-licenses are now referenced to ${mediaSource.sourceId ?? 'unknown'}`
);
}
console.info(`${total} user-licenses are now referenced to media-sources`);

const userLicensesNameChange = await this.driver.nativeUpdate(
'user-licenses',
{
mediaSourceId: { $exists: true },
},
{
$rename: { mediaSourceId: 'mediaSource' },
}
);
console.info(`mediaSourceId was renamed to mediaSource in ${userLicensesNameChange.affectedRows} user licenses`);
}

async down(): Promise<void> {
await this.driver.aggregate('user-licenses', [
{ $match: { mediaSource: { $ne: null } } },
{ $lookup: { from: 'media-sources', localField: 'mediaSource', foreignField: '_id', as: 'mediaSourceId' } }, // Joins media-sources as array in mediaSourceId
{ $unwind: { path: '$mediaSourceId', preserveNullAndEmptyArrays: true } }, // Transforms mediaSourceId array to individual documents with the object
{ $set: { mediaSourceId: '$mediaSourceId.sourceId' } }, // Replaces the mediaSourceId object with the contained mediaSourceId
{ $unset: 'mediaSource' },
{ $merge: { into: 'user-licenses', on: '_id', whenMatched: 'replace', whenNotMatched: 'fail' } },
]);

await this.getCollection('media-sources').drop();
console.info(`media-sources was dropped`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { contextExternalToolEntityFactory } from '@modules/tool/context-external
import { externalToolEntityFactory } from '@modules/tool/external-tool/testing';
import { schoolExternalToolEntityFactory } from '@modules/tool/school-external-tool/testing';
import { MediaUserLicenseEntity } from '@modules/user-license/entity';
import { mediaUserLicenseEntityFactory } from '@modules/user-license/testing';
import { mediaSourceEntityFactory, mediaUserLicenseEntityFactory } from '@modules/user-license/testing';
import { HttpStatus, INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { TestApiClient, UserAndAccountTestFactory, type DatesToStrings } from '@shared/testing';
Expand Down Expand Up @@ -498,8 +498,14 @@ describe('Media Board (API)', () => {

const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent();

const mediaSource = mediaSourceEntityFactory.build();
const externalTool = externalToolEntityFactory.build();
const licensedUnusedExternalTool = externalToolEntityFactory.withMedium().build();
const licensedUnusedExternalTool = externalToolEntityFactory
.withMedium({
mediumId: 'mediumId',
mediaSourceId: mediaSource.sourceId,
})
.build();
const unusedExternalTool = externalToolEntityFactory.build({ medium: { mediumId: 'notLicensedByUser' } });
const schoolExternalTool = schoolExternalToolEntityFactory.build({
tool: externalTool,
Expand Down Expand Up @@ -531,7 +537,7 @@ describe('Media Board (API)', () => {
const userLicense: MediaUserLicenseEntity = mediaUserLicenseEntityFactory.build({
user: studentUser,
mediumId: 'mediumId',
mediaSourceId: 'mediaSourceId',
mediaSource,
});

await em.persistAndFlush([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { Action, AuthorizationService } from '@modules/authorization';
import { ExternalTool } from '@modules/tool/external-tool/domain';
import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain';
import { schoolExternalToolFactory } from '@modules/tool/school-external-tool/testing';
import { MediaUserLicense, mediaUserLicenseFactory, UserLicenseService } from '@modules/user-license';
import { MediaUserLicenseService } from '@modules/user-license/service';
import { MediaUserLicense, mediaUserLicenseFactory, MediaUserLicenseService } from '@modules/user-license';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { FeatureDisabledLoggableException } from '@shared/common/loggable-exception';
Expand Down Expand Up @@ -45,7 +44,6 @@ describe(MediaAvailableLineUc.name, () => {
let mediaAvailableLineService: DeepMocked<MediaAvailableLineService>;
let configService: DeepMocked<ConfigService<MediaBoardConfig, true>>;
let mediaBoardService: DeepMocked<MediaBoardService>;
let userLicenseService: DeepMocked<UserLicenseService>;
let mediaUserLicenseService: DeepMocked<MediaUserLicenseService>;

beforeAll(async () => {
Expand Down Expand Up @@ -78,10 +76,6 @@ describe(MediaAvailableLineUc.name, () => {
provide: MediaBoardService,
useValue: createMock<MediaBoardService>(),
},
{
provide: UserLicenseService,
useValue: createMock<UserLicenseService>(),
},
{
provide: MediaUserLicenseService,
useValue: createMock<MediaUserLicenseService>(),
Expand All @@ -96,7 +90,6 @@ describe(MediaAvailableLineUc.name, () => {
mediaAvailableLineService = module.get(MediaAvailableLineService);
configService = module.get(ConfigService);
mediaBoardService = module.get(MediaBoardService);
userLicenseService = module.get(UserLicenseService);
mediaUserLicenseService = module.get(MediaUserLicenseService);
});

Expand Down Expand Up @@ -256,7 +249,7 @@ describe(MediaAvailableLineUc.name, () => {
const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.build({ toolId: externalTool1.id });
const schoolExternalTool2: SchoolExternalTool = schoolExternalToolFactory.build({ toolId: externalTool2.id });

userLicenseService.getMediaUserLicensesForUser.mockResolvedValue([]);
mediaUserLicenseService.getMediaUserLicensesForUser.mockResolvedValue([]);

boardNodeService.findByClassAndId.mockResolvedValueOnce(mediaBoard);
mediaAvailableLineService.getUnusedAvailableSchoolExternalTools.mockResolvedValueOnce([
Expand Down Expand Up @@ -326,7 +319,7 @@ describe(MediaAvailableLineUc.name, () => {
const mediaUserlicense: MediaUserLicense = mediaUserLicenseFactory.build();
mediaUserlicense.mediumId = 'mediumId';

userLicenseService.getMediaUserLicensesForUser.mockResolvedValue([mediaUserlicense]);
mediaUserLicenseService.getMediaUserLicensesForUser.mockResolvedValue([mediaUserlicense]);
mediaUserLicenseService.hasLicenseForExternalTool.mockReturnValue(true);

boardNodeService.findByClassAndId.mockResolvedValueOnce(mediaBoard);
Expand Down Expand Up @@ -392,7 +385,7 @@ describe(MediaAvailableLineUc.name, () => {

const mediaUserlicense: MediaUserLicense = mediaUserLicenseFactory.build();

userLicenseService.getMediaUserLicensesForUser.mockResolvedValue([mediaUserlicense]);
mediaUserLicenseService.getMediaUserLicensesForUser.mockResolvedValue([mediaUserlicense]);
mediaUserLicenseService.hasLicenseForExternalTool.mockReturnValue(false);

boardNodeService.findByClassAndId.mockResolvedValueOnce(mediaBoard);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { Action, AuthorizationService } from '@modules/authorization';
import { ExternalTool } from '@modules/tool/external-tool/domain';
import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain';
import { MediaUserLicense, UserLicenseService } from '@modules/user-license';
import { MediaUserLicenseService } from '@modules/user-license/service';
import { MediaUserLicense, MediaUserLicenseService } from '@modules/user-license';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { FeatureDisabledLoggableException } from '@shared/common/loggable-exception';
import { EntityId } from '@shared/domain/types';
import { MediaAvailableLine, MediaBoard } from '../../domain';
import { MediaBoardColors } from '../../domain/media-board/types';
import type { MediaBoardConfig } from '../../media-board.config';
import {
BoardNodePermissionService,
BoardNodeService,
MediaAvailableLineService,
MediaBoardService,
} from '../../service';
import { MediaBoardColors } from '../../domain/media-board/types';

@Injectable()
export class MediaAvailableLineUc {
Expand All @@ -26,7 +25,6 @@ export class MediaAvailableLineUc {
private readonly mediaAvailableLineService: MediaAvailableLineService,
private readonly mediaBoardService: MediaBoardService,
private readonly configService: ConfigService<MediaBoardConfig, true>,
private readonly userLicenseService: UserLicenseService,
private readonly mediaUserLicenseService: MediaUserLicenseService
) {}

Expand Down Expand Up @@ -95,7 +93,9 @@ export class MediaAvailableLineUc {
userId: EntityId,
matchedTools: [ExternalTool, SchoolExternalTool][]
): Promise<[ExternalTool, SchoolExternalTool][]> {
const mediaUserLicenses: MediaUserLicense[] = await this.userLicenseService.getMediaUserLicensesForUser(userId);
const mediaUserLicenses: MediaUserLicense[] = await this.mediaUserLicenseService.getMediaUserLicensesForUser(
userId
);

matchedTools = matchedTools.filter((tool: [ExternalTool, SchoolExternalTool]): boolean => {
const externalToolMedium = tool[0]?.medium;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('SchoolExternalToolCreatedLoggable', () => {
userId: license.userId,
schoolId: schoolExternalTool.schoolId,
mediumId: license.mediumId,
mediaSourceId: license.mediaSourceId,
mediaSourceId: license.mediaSource?.sourceId,
schoolExternalToolId: schoolExternalTool.id,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class SchoolExternalToolCreatedLoggable implements Loggable {
userId: this.license.userId,
schoolId: this.schoolExternalTool.schoolId,
mediumId: this.license.mediumId,
mediaSourceId: this.license.mediaSourceId,
mediaSourceId: this.license.mediaSource?.sourceId,
schoolExternalToolId: this.schoolExternalTool.id,
},
};
Expand Down
Loading

0 comments on commit 76bdf79

Please sign in to comment.