Skip to content

Commit e5d11ef

Browse files
Merge pull request #5 from AmplicaLabs/setup_repo
[Chore] setup repository with relevant code
2 parents be5b87f + a4752d7 commit e5d11ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+30
-4051
lines changed

services/content-watcher/.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ env:
2020
NEW_RELEASE_TAG_FROM_UI: ${{github.event.inputs.release-version}}
2121
TEST_RUN: ${{startsWith(github.event.inputs.release-version || github.ref_name, 'v0.0.1')}}
2222
DOCKER_HUB_PROFILE: amplicalabs
23-
IMAGE_NAME: content-publishing-service-api
23+
IMAGE_NAME: content-watcher-service
2424

2525
jobs:
2626
build-and-publish-container-image:
@@ -68,7 +68,7 @@ jobs:
6868
with:
6969
username: ${{secrets.DOCKERHUB_USERNAME_FC}}
7070
password: ${{secrets.DOCKERHUB_TOKEN_FC}}
71-
- name: Build and Push Content-Publishing-Service Image
71+
- name: Build and Push content-watcher-service Image
7272
uses: docker/build-push-action@v4
7373
with:
7474
context: .

services/content-watcher/INSTALLING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ The application requires a Redis server that is configured with `Append-only fil
99

1010
### Standalone (complete) image
1111

12-
The standalone container image is meant to be a complete solution for a provider. It contains a single instance of the main application, plus a pre-configured Redis server. Simply download the latest [container image](https://hub.docker.com/r/amplicalabs/content-publishing-service/) and deploy using your favorite container management system.
12+
The standalone container image is meant to be a complete solution for a provider. It contains a single instance of the main application, plus a pre-configured Redis server. Simply download the latest [container image](https://hub.docker.com/r/amplicalabs/content-watcher-service/) and deploy using your favorite container management system.
1313
```
14-
docker pull amplicalabs/content-publishing-service:standalone-latest
14+
docker pull amplicalabs/content-watcher-service:standalone-latest
1515
```
1616

1717
The internal Redis server included in the complete image is already configured for persistence; it is simply necessary to configure your container pod to map the directory `/var/lib/redis` to a persistent storage volume.
@@ -22,9 +22,9 @@ Follow the instructions below for [configuration](#configuration), with the exce
2222

2323
### App-only image
2424

25-
The app-only image is meant to be used for providers who would rather utilize a Redis instance in their own (or their cloud infrastructure provider's) external Redis instance or service. To download the latest [container image](https://hub.docker.com/r/amplicalabs/content-publishing-service/), simply:
25+
The app-only image is meant to be used for providers who would rather utilize a Redis instance in their own (or their cloud infrastructure provider's) external Redis instance or service. To download the latest [container image](https://hub.docker.com/r/amplicalabs/content-watcher-service/), simply:
2626
```
27-
docker pull amplicalabs/content-publishing-service:apponly-latest
27+
docker pull amplicalabs/content-watcher-service:apponly-latest
2828
```
2929
In this case, you need to ensure that the following settings are configured in your Redis instance:
3030
```

services/content-watcher/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Follow these steps to set up and run Content Publisher:
2525
1. Clone the Content Publisher repository to your local machine:
2626

2727
```bash
28-
git clone https://github.com/amplicalabls/content-publishing-service.git
28+
git clone https://github.com/amplicalabls/content-watcher-service.git
2929
```
3030

3131
2. Modify any environment variables in the `.env` file as needed. For docker compose env `.env.docker.dev` file is used.

services/content-watcher/apps/api/src/api.controller.ts

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -33,73 +33,4 @@ export class ApiController {
3333
status: HttpStatus.OK,
3434
};
3535
}
36-
37-
@Put('asset/upload')
38-
@UseInterceptors(FilesInterceptor('files'))
39-
@HttpCode(202)
40-
@ApiConsumes('multipart/form-data')
41-
@ApiBody({
42-
description: 'Asset files',
43-
type: FilesUploadDto,
44-
})
45-
async assetUpload(
46-
@UploadedFiles(
47-
new ParseFilePipeBuilder()
48-
.addFileTypeValidator({
49-
fileType: DSNP_VALID_MIME_TYPES,
50-
})
51-
// TODO: add a validator to check overall uploaded size
52-
.addMaxSizeValidator({
53-
// this is in bytes (2 GB)
54-
maxSize: parseInt(process.env.FILE_UPLOAD_MAX_SIZE_IN_BYTES!, 10),
55-
})
56-
.build({
57-
errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
58-
}),
59-
)
60-
files: // eslint-disable-next-line no-undef
61-
Array<Express.Multer.File>,
62-
): Promise<UploadResponseDto> {
63-
return this.apiService.addAssets(files);
64-
}
65-
66-
@Post('content/:userDsnpId/broadcast')
67-
@HttpCode(202)
68-
async broadcast(@Param() userDsnpId: DsnpUserIdParam, @Body() broadcastDto: BroadcastDto): Promise<AnnouncementResponseDto> {
69-
const metadata = await this.apiService.validateAssetsAndFetchMetadata(broadcastDto as AssetIncludedRequestDto);
70-
return this.apiService.enqueueRequest(AnnouncementTypeDto.BROADCAST, userDsnpId.userDsnpId, broadcastDto, metadata);
71-
}
72-
73-
@Post('content/:userDsnpId/reply')
74-
@HttpCode(202)
75-
async reply(@Param() userDsnpId: DsnpUserIdParam, @Body() replyDto: ReplyDto): Promise<AnnouncementResponseDto> {
76-
const metadata = await this.apiService.validateAssetsAndFetchMetadata(replyDto as AssetIncludedRequestDto);
77-
return this.apiService.enqueueRequest(AnnouncementTypeDto.REPLY, userDsnpId.userDsnpId, replyDto, metadata);
78-
}
79-
80-
@Post('content/:userDsnpId/reaction')
81-
@HttpCode(202)
82-
async reaction(@Param() userDsnpId: DsnpUserIdParam, @Body() reactionDto: ReactionDto): Promise<AnnouncementResponseDto> {
83-
return this.apiService.enqueueRequest(AnnouncementTypeDto.REACTION, userDsnpId.userDsnpId, reactionDto);
84-
}
85-
86-
@Put('content/:userDsnpId')
87-
@HttpCode(202)
88-
async update(@Param() userDsnpId: DsnpUserIdParam, @Body() updateDto: UpdateDto): Promise<AnnouncementResponseDto> {
89-
const metadata = await this.apiService.validateAssetsAndFetchMetadata(updateDto as AssetIncludedRequestDto);
90-
return this.apiService.enqueueRequest(AnnouncementTypeDto.UPDATE, userDsnpId.userDsnpId, updateDto, metadata);
91-
}
92-
93-
@Delete('content/:userDsnpId')
94-
@HttpCode(202)
95-
async delete(@Param() userDsnpId: DsnpUserIdParam, @Body() tombstoneDto: TombstoneDto): Promise<AnnouncementResponseDto> {
96-
return this.apiService.enqueueRequest(AnnouncementTypeDto.TOMBSTONE, userDsnpId.userDsnpId, tombstoneDto);
97-
}
98-
99-
@Put('profile/:userDsnpId')
100-
@HttpCode(202)
101-
async profile(@Param() userDsnpId: DsnpUserIdParam, @Body() profileDto: ProfileDto): Promise<AnnouncementResponseDto> {
102-
const metadata = await this.apiService.validateAssetsAndFetchMetadata(profileDto as AssetIncludedRequestDto);
103-
return this.apiService.enqueueRequest(AnnouncementTypeDto.PROFILE, userDsnpId.userDsnpId, profileDto, metadata);
104-
}
10536
}

services/content-watcher/apps/api/src/api.module.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { BullBoardModule } from '@bull-board/nestjs';
77
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
88
import { ExpressAdapter } from '@bull-board/express';
99
import { ApiController } from './api.controller';
10-
import { DevelopmentController } from './development.controller';
1110
import { QueueConstants } from '../../../libs/common/src';
1211
import { ApiService } from './api.service';
1312
import { IpfsService } from '../../../libs/common/src/utils/ipfs.client';
@@ -163,7 +162,7 @@ import { ConfigService } from '../../../libs/common/src/config/config.service';
163162
ScheduleModule.forRoot(),
164163
],
165164
providers: [ConfigService, ApiService, IpfsService],
166-
controllers: process.env?.ENVIRONMENT === 'dev' ? [DevelopmentController, ApiController] : [ApiController],
165+
controllers: [ApiController],
167166
exports: [],
168167
})
169168
export class ApiModule {}

services/content-watcher/apps/api/src/api.service.ts

Lines changed: 0 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ import {
1919
import { calculateIpfsCID } from '../../../libs/common/src/utils/ipfs';
2020
import { IAssetJob, IAssetMetadata } from '../../../libs/common/src/interfaces/asset-job.interface';
2121
import { RedisUtils } from '../../../libs/common/src/utils/redis';
22-
import getAssetDataKey = RedisUtils.getAssetDataKey;
23-
import getAssetMetadataKey = RedisUtils.getAssetMetadataKey;
2422

2523
@Injectable()
2624
export class ApiService {
@@ -34,134 +32,6 @@ export class ApiService {
3432
this.logger = new Logger(this.constructor.name);
3533
}
3634

37-
async enqueueRequest(
38-
announcementType: AnnouncementTypeDto,
39-
dsnpUserId: string,
40-
content: RequestTypeDto,
41-
assetToMimeType?: Map<string, string>,
42-
): Promise<AnnouncementResponseDto> {
43-
const data = {
44-
content,
45-
id: '',
46-
announcementType,
47-
dsnpUserId,
48-
dependencyAttempt: 0,
49-
} as IRequestJob;
50-
data.id = this.calculateJobId(data);
51-
if (assetToMimeType) {
52-
// not used in id calculation since the order in map might not be deterministic
53-
data.assetToMimeType = assetToMimeType;
54-
}
55-
const job = await this.requestQueue.add(`Request Job - ${data.id}`, data, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); // TODO: should come from queue configs
56-
this.logger.debug(job);
57-
return {
58-
referenceId: data.id,
59-
};
60-
}
61-
62-
async validateAssetsAndFetchMetadata(content: AssetIncludedRequestDto): Promise<Map<string, string> | undefined> {
63-
const checkingList: Array<{ onlyImage: boolean; referenceId: string }> = [];
64-
if (content.profile) {
65-
content.profile.icon?.forEach((reference) => checkingList.push({ onlyImage: true, referenceId: reference.referenceId }));
66-
} else if (content.content) {
67-
content.content.assets?.forEach(
68-
(asset) =>
69-
asset.references?.forEach((reference) =>
70-
checkingList.push({
71-
onlyImage: false,
72-
referenceId: reference.referenceId,
73-
}),
74-
),
75-
);
76-
}
77-
78-
const redisResults = await Promise.all(checkingList.map((obj) => this.redis.get(getAssetMetadataKey(obj.referenceId))));
79-
const errors: string[] = [];
80-
const map = new Map();
81-
redisResults.forEach((res, index) => {
82-
if (res === null) {
83-
errors.push(`${content.profile ? 'profile.icon' : 'content.assets'}.referenceId ${checkingList[index].referenceId} does not exist!`);
84-
} else {
85-
const metadata: IAssetMetadata = JSON.parse(res);
86-
map[checkingList[index].referenceId] = metadata.mimeType;
87-
88-
// checks if attached asset is an image
89-
if (checkingList[index].onlyImage && !isImage(metadata.mimeType)) {
90-
errors.push(`profile.icon.referenceId ${checkingList[index].referenceId} is not an image!`);
91-
}
92-
}
93-
});
94-
if (errors.length > 0) {
95-
throw new HttpErrorByCode[400](errors);
96-
}
97-
return map;
98-
}
99-
100-
// eslint-disable-next-line no-undef
101-
async addAssets(files: Array<Express.Multer.File>): Promise<UploadResponseDto> {
102-
// calculate ipfs cid references
103-
const referencePromises: Promise<string>[] = files.map((file) => calculateIpfsCID(file.buffer));
104-
const references = await Promise.all(referencePromises);
105-
106-
let dataTransaction = this.redis.multi();
107-
let metadataTransaction = this.redis.multi();
108-
const jobs: any[] = [];
109-
files.forEach((f, index) => {
110-
// adding data and metadata to the transaction
111-
dataTransaction = dataTransaction.setex(getAssetDataKey(references[index]), RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, f.buffer);
112-
metadataTransaction = metadataTransaction.setex(
113-
getAssetMetadataKey(references[index]),
114-
RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS,
115-
JSON.stringify({
116-
ipfsCid: references[index],
117-
mimeType: f.mimetype,
118-
createdOn: Date.now(),
119-
} as IAssetMetadata),
120-
);
121-
// adding asset job to the jobs
122-
jobs.push({
123-
name: `Asset Job - ${references[index]}`,
124-
data: {
125-
ipfsCid: references[index],
126-
contentLocation: getAssetDataKey(references[index]),
127-
metadataLocation: getAssetMetadataKey(references[index]),
128-
mimeType: f.mimetype,
129-
} as IAssetJob,
130-
opts: {
131-
jobId: references[index],
132-
removeOnFail: false,
133-
removeOnComplete: true,
134-
attempts: 3,
135-
backoff: {
136-
type: 'exponential',
137-
delay: 10000,
138-
},
139-
} as BulkJobOptions,
140-
});
141-
});
142-
143-
// currently we are applying 3 different transactions on redis
144-
// 1: Storing the content data
145-
// 2: Adding asset jobs
146-
// 3: Storing the content metadata
147-
// even though all these transactions are applied separately, the overall behavior will clean up any partial failures eventually
148-
// partial failure scenarios:
149-
// 1: adding jobs failure: at this point we already successfully stored the data content in redis, but since all
150-
// of this stored data has expire-time, it would eventually get cleaned up
151-
// 2: metadata transaction failure: at this point we already stored the data content and jobs and those two are
152-
// enough to process the asset on the worker side, the worker will clean up both of them after processing
153-
const dataOps = await dataTransaction.exec();
154-
this.checkTransactionResult(dataOps);
155-
const queuedJobs = await this.assetQueue.addBulk(jobs);
156-
this.logger.debug(queuedJobs);
157-
const metaDataOps = await metadataTransaction.exec();
158-
this.checkTransactionResult(metaDataOps);
159-
160-
return {
161-
assetIds: references,
162-
};
163-
}
164-
16535
// eslint-disable-next-line class-methods-use-this
16636
private calculateJobId(jobWithoutId: IRequestJob): string {
16737
const stringVal = JSON.stringify(jobWithoutId);

services/content-watcher/apps/api/src/development.controller.ts

Lines changed: 0 additions & 101 deletions
This file was deleted.

0 commit comments

Comments
 (0)