Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
14 changes: 12 additions & 2 deletions src/services/action/repositories/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { aggregateExpressionNames, buildAggregateExpression } from '../utils/act
*/
export const ActionRepository = AppDataSource.getRepository(Action).extend({
/**
* Create given action and return it.
* @param action Action to create
* TODO: remove if it not used.
* Create given actions.
* @param action Actions to create
*/
async postMany(actions: Pick<Action, 'member' | 'type'>[]): Promise<void> {
// save action
Expand All @@ -20,6 +21,15 @@ export const ActionRepository = AppDataSource.getRepository(Action).extend({
}
},

/**
* Create given action.
* @param action Action to create
*/
async post(action: Pick<Action, 'member' | 'type'>): Promise<void> {
// save action
await this.insert(action);
},

/**
* Delete actions matching the given `memberId`. Return actions, or `null`, if delete has no effect.
* @param memberId ID of the member whose actions are deleted
Expand Down
37 changes: 37 additions & 0 deletions src/services/action/services/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class ActionService {
this.memberService = memberService;
}

// TODO: remove if it not used
async postMany(
member: Actor,
repositories: Repositories,
Expand Down Expand Up @@ -59,4 +60,40 @@ export class ActionService {

await repositories.actionRepository.postMany(completeActions);
}

async post(
member: Actor,
repositories: Repositories,
request: FastifyRequest,
action: Partial<Action> & Pick<Action, 'type'>,
): Promise<void> {
const { headers } = request;

// prevent saving if member disabled
const enableMemberSaving = member?.extra?.enableSaveActions ?? true;
if (!enableMemberSaving) {
return;
}

// prevent saving if item disabled analytics
if (action.item?.settings?.enableSaveActions) {
return;
}

const view = getView(headers);
// warning: addresses might contained spoofed ips
const addresses = forwarded(request.raw);
const ip = addresses.pop();

const geolocation = ip ? getGeolocationIp(ip) : null;
const completeAction = {
member,
geolocation: geolocation ?? undefined,
view,
extra: {},
...action,
};

await repositories.actionRepository.post(completeAction);
}
}
71 changes: 44 additions & 27 deletions src/services/item/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { StatusCodes } from 'http-status-codes';

import { FastifyPluginAsync } from 'fastify';

import { IdParam, IdsParams, ParentIdParam, PermissionLevel } from '@graasp/sdk';
import { IdParam, IdsParams, ParentIdParam, PermissionLevel, getParentFromPath } from '@graasp/sdk';

import { PaginationParams } from '../../types';
import { UnauthorizedMember } from '../../utils/errors';
Expand All @@ -24,8 +24,9 @@ import {
updateMany,
} from './fluent-schema';
import { ItemGeolocation } from './plugins/geolocation/ItemGeolocation';
import { ItemChildrenParams, ItemSearchParams } from './types';
import { ItemOpFeedbackEvent, memberItemsTopic } from './ws/events';
import { ItemChildrenParams, ItemSearchParams, PromiseRunner } from './types';
import { ItemOpFeedbackEvent, ResultOfFactory, memberItemsTopic } from './ws/events';
import { ItemWebsocketsService } from './ws/services';

const plugin: FastifyPluginAsync = async (fastify) => {
const { db, items, websockets } = fastify;
Expand Down Expand Up @@ -211,7 +212,7 @@ const plugin: FastifyPluginAsync = async (fastify) => {
websockets.publish(
memberItemsTopic,
member.id,
ItemOpFeedbackEvent('update', ids, { error: e }),
ItemOpFeedbackEvent('update', ids, ResultOfFactory.withError(e)),
);
}
});
Expand Down Expand Up @@ -252,7 +253,7 @@ const plugin: FastifyPluginAsync = async (fastify) => {
websockets.publish(
memberItemsTopic,
member.id,
ItemOpFeedbackEvent('delete', ids, { error: e }),
ItemOpFeedbackEvent('delete', ids, ResultOfFactory.withError(e)),
);
}
});
Expand All @@ -271,30 +272,46 @@ const plugin: FastifyPluginAsync = async (fastify) => {
body: { parentId },
log,
} = request;
db.transaction(async (manager) => {
const repositories = buildRepositories(manager);
const items = await itemService.moveMany(member, repositories, ids, parentId);
await actionItemService.postManyMoveAction(request, reply, repositories, items);
if (member) {
websockets.publish(
memberItemsTopic,
member.id,
ItemOpFeedbackEvent('move', ids, {
data: Object.fromEntries(items.map((i) => [i.id, i])),
errors: [],
}),
);
}
}).catch((e) => {
log.error(e);
const itemWsService = new ItemWebsocketsService(websockets);

PromiseRunner.inSeries<Item>(
ids.map((itemId) => {
return async () => {
return db
.transaction(async (manager) => {
const repositories = buildRepositories(manager);
const { source, destination } = await itemService.move(
member,
repositories,
itemId,
parentId,
);

await actionItemService.postMoveAction(request, repositories, destination);

return { source, destination };
})
.then(({ source, destination }) => {
itemWsService.publishTopicsForMove({
source,
destination,
sourceParentId: getParentFromPath(source.path),
});
return destination;
});
};
}),
).then((results) => {
if (member) {
websockets.publish(
memberItemsTopic,
member.id,
ItemOpFeedbackEvent('move', ids, { error: e }),
);
itemWsService.publishFeedbacksForMove({
log,
results,
itemIds: ids,
memberId: member.id,
});
}
});

reply.status(StatusCodes.ACCEPTED);
return ids;
},
Expand Down Expand Up @@ -335,7 +352,7 @@ const plugin: FastifyPluginAsync = async (fastify) => {
websockets.publish(
memberItemsTopic,
member.id,
ItemOpFeedbackEvent('copy', ids, { error: e }),
ItemOpFeedbackEvent('copy', ids, ResultOfFactory.withError(e)),
);
}
});
Expand Down
4 changes: 2 additions & 2 deletions src/services/item/plugins/action/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
LocalFileConfiguration,
S3FileConfiguration,
} from '../../../file/interfaces/configuration';
import { ItemOpFeedbackEvent, memberItemsTopic } from '../../ws/events';
import { ItemOpFeedbackEvent, ResultOfFactory, memberItemsTopic } from '../../ws/events';
import { CannotPostAction } from './errors';
import { ActionRequestExportService } from './requestExport/service';
import { exportAction, getAggregateActions, getItemActions, postAction } from './schemas';
Expand Down Expand Up @@ -175,7 +175,7 @@ const plugin: FastifyPluginAsync<GraaspActionsOptions> = async (fastify) => {
websockets.publish(
memberItemsTopic,
member.id,
ItemOpFeedbackEvent('export', [itemId], { error: e }),
ItemOpFeedbackEvent('export', [itemId], ResultOfFactory.withError(e)),
);
}
});
Expand Down
13 changes: 13 additions & 0 deletions src/services/item/plugins/action/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ export class ActionItemService {
await this.actionService.postMany(member, repositories, request, actions);
}

// TODO: remove it if it is never used
async postManyMoveAction(
request: FastifyRequest,
reply: FastifyReply,
Expand All @@ -297,6 +298,18 @@ export class ActionItemService {
await this.actionService.postMany(member, repositories, request, actions);
}

async postMoveAction(request: FastifyRequest, repositories: Repositories, item: Item) {
const { member } = request;
const action = {
item,
type: ItemActionType.Move,
// TODO: remove any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
extra: { itemId: item.id, body: request.body as any },
};
await this.actionService.post(member, repositories, request, action);
}

async postManyCopyAction(
request: FastifyRequest,
reply: FastifyReply,
Expand Down
8 changes: 6 additions & 2 deletions src/services/item/plugins/action/test/ws.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { clearDatabase } from '../../../../../../test/app';
import { saveItemAndMembership } from '../../../../itemMembership/test/fixtures/memberships';
import { TestWsClient } from '../../../../websockets/test/test-websocket-client';
import { setupWsApp } from '../../../../websockets/test/ws-app';
import { ItemOpFeedbackEvent, memberItemsTopic } from '../../../ws/events';
import { ItemOpFeedbackEvent, ResultOfFactory, memberItemsTopic } from '../../../ws/events';
import { ActionRequestExportRepository } from '../requestExport/repository';

// mock datasource
Expand Down Expand Up @@ -97,7 +97,11 @@ describe('asynchronous feedback', () => {
await waitForExpect(() => {
const [feedbackUpdate] = memberUpdates;
expect(feedbackUpdate).toMatchObject(
ItemOpFeedbackEvent('export', [item.id], { error: new Error('mock error') }),
ItemOpFeedbackEvent(
'export',
[item.id],
ResultOfFactory.withError(new Error('mock error')),
),
);
});
});
Expand Down
6 changes: 3 additions & 3 deletions src/services/item/plugins/recycled/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FastifyPluginAsync } from 'fastify';
import { IdParam, IdsParams, MAX_TARGETS_FOR_READ_REQUEST } from '@graasp/sdk';

import { buildRepositories } from '../../../../utils/repositories';
import { ItemOpFeedbackEvent, memberItemsTopic } from '../../ws/events';
import { ItemOpFeedbackEvent, ResultOfFactory, memberItemsTopic } from '../../ws/events';
import schemas, { getRecycledItemDatas, recycleMany, restoreMany } from './schemas';
import { RecycledBinService } from './service';
import { recycleWsHooks } from './ws/hooks';
Expand Down Expand Up @@ -88,7 +88,7 @@ const plugin: FastifyPluginAsync<RecycledItemDataOptions> = async (fastify, opti
websockets.publish(
memberItemsTopic,
member.id,
ItemOpFeedbackEvent('recycle', ids, { error: e }),
ItemOpFeedbackEvent('recycle', ids, ResultOfFactory.withError(e)),
);
}
});
Expand Down Expand Up @@ -138,7 +138,7 @@ const plugin: FastifyPluginAsync<RecycledItemDataOptions> = async (fastify, opti
websockets.publish(
memberItemsTopic,
member.id,
ItemOpFeedbackEvent('restore', ids, { error: e }),
ItemOpFeedbackEvent('restore', ids, ResultOfFactory.withError(e)),
);
}
});
Expand Down
17 changes: 11 additions & 6 deletions src/services/item/plugins/recycled/test/ws.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
ItemEvent,
ItemOpFeedbackEvent,
OwnItemsEvent,
ResultOfFactory,
SelfItemEvent,
SharedItemsEvent,
itemTopic,
Expand Down Expand Up @@ -710,9 +711,11 @@ describe('Recycle websocket hooks', () => {
await waitForExpect(() => {
const [feedbackUpdate] = memberUpdates;
expect(feedbackUpdate).toMatchObject(
ItemOpFeedbackEvent('recycle', [item.id], {
error: new Error('mock error'),
}),
ItemOpFeedbackEvent(
'recycle',
[item.id],
ResultOfFactory.withError(new Error('mock error')),
),
);
});
});
Expand Down Expand Up @@ -771,9 +774,11 @@ describe('Recycle websocket hooks', () => {
await waitForExpect(() => {
const [feedbackUpdate] = memberUpdates;
expect(feedbackUpdate).toMatchObject(
ItemOpFeedbackEvent('restore', [item.id], {
error: new Error('mock error'),
}),
ItemOpFeedbackEvent(
'restore',
[item.id],
ResultOfFactory.withError(new Error('mock error')),
),
);
});
});
Expand Down
4 changes: 2 additions & 2 deletions src/services/item/plugins/validation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FastifyPluginAsync } from 'fastify';

import { UnauthorizedMember } from '../../../../utils/errors';
import { buildRepositories } from '../../../../utils/repositories';
import { ItemOpFeedbackEvent, memberItemsTopic } from '../../ws/events';
import { ItemOpFeedbackEvent, ResultOfFactory, memberItemsTopic } from '../../ws/events';
import { itemValidation, itemValidationGroup } from './schemas';
import { ItemValidationService } from './service';

Expand Down Expand Up @@ -98,7 +98,7 @@ const plugin: FastifyPluginAsync<GraaspPluginValidationOptions> = async (fastify
websockets.publish(
memberItemsTopic,
member.id,
ItemOpFeedbackEvent('validate', [itemId], { error: e }),
ItemOpFeedbackEvent('validate', [itemId], ResultOfFactory.withError(e)),
);
}
});
Expand Down
15 changes: 11 additions & 4 deletions src/services/item/plugins/validation/test/ws.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import { ITEMS_ROUTE_PREFIX } from '../../../../../utils/config';
import { saveItemAndMembership } from '../../../../itemMembership/test/fixtures/memberships';
import { TestWsClient } from '../../../../websockets/test/test-websocket-client';
import { setupWsApp } from '../../../../websockets/test/ws-app';
import { ItemEvent, ItemOpFeedbackEvent, memberItemsTopic } from '../../../ws/events';
import {
ItemEvent,
ItemOpFeedbackEvent,
ResultOfFactory,
memberItemsTopic,
} from '../../../ws/events';
import { ItemValidationGroupRepository } from '../repositories/ItemValidationGroup';
import { saveItemValidation } from './utils';

Expand Down Expand Up @@ -82,9 +87,11 @@ describe('asynchronous feedback', () => {
await waitForExpect(() => {
const [feedbackUpdate] = memberUpdates;
expect(feedbackUpdate).toMatchObject(
ItemOpFeedbackEvent('validate', [item.id], {
error: new Error('mock error'),
}),
ItemOpFeedbackEvent(
'validate',
[item.id],
ResultOfFactory.withError(new Error('mock error')),
),
);
});
});
Expand Down
Loading