Skip to content

Commit 32f1e66

Browse files
committed
feat: add playlist sticher service setup
1 parent fdbc412 commit 32f1e66

27 files changed

+502
-7
lines changed

apps/api/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"compilerOptions": {
44
"outDir": "./dist",
55
},
6-
"include": ["src/**/*.ts", "../../libs/uuid/src/uuidService.ts"],
6+
"include": ["src/**/*.ts", "tests/**/*.ts"],
77
"references": [
88
{
99
"path": "../../libs/amqp/tsconfig.json"

apps/downloader/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"compilerOptions": {
44
"outDir": "./dist",
55
},
6-
"include": ["src/**/*.ts", "tests/**/*.ts", "src/actions/downloadVideoAction"],
6+
"include": ["src/**/*.ts", "tests/**/*.ts"],
77
"exclude": [],
88
"references": [
99
{

apps/encoder/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"compilerOptions": {
44
"outDir": "./dist",
55
},
6-
"include": ["src/**/*.ts", "tests/**/*.ts", "src/actions/encodeVideoAction"],
6+
"include": ["src/**/*.ts", "tests/**/*.ts"],
77
"exclude": [],
88
"references": [
99
{

apps/encoding-director/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"compilerOptions": {
44
"outDir": "./dist",
55
},
6-
"include": ["src/**/*.ts", "tests/**/*.ts", "src/actions/requestVideoEncodingsAction"],
6+
"include": ["src/**/*.ts", "tests/**/*.ts"],
77
"exclude": [],
88
"references": [
99
{

apps/playlist-sticher/.dockerignore

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# See http://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# Compiled output
4+
/dist
5+
/tmp
6+
/out-tsc
7+
/bazel-out
8+
9+
# Node
10+
/node_modules
11+
npm-debug.log
12+
yarn-error.log
13+
14+
# IDEs and editors
15+
.idea/
16+
.project
17+
.classpath
18+
.c9/
19+
*.launch
20+
.settings/
21+
*.sublime-workspace
22+
23+
# Visual Studio Code
24+
.vscode/*
25+
!.vscode/settings.json
26+
!.vscode/tasks.json
27+
!.vscode/launch.json
28+
!.vscode/extensions.json
29+
.history/*
30+
31+
# Miscellaneous
32+
/.angular/cache
33+
.sass-cache/
34+
/connect.lock
35+
/coverage
36+
/libpeerconnection.log
37+
testem.log
38+
/typings
39+
40+
# System files
41+
.DS_Store
42+
Thumbs.db
43+
44+
Dockerfile
45+
.dockerignore
46+
.git

apps/playlist-sticher/.gitignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.idea
2+
node_modules
3+
4+
dist
5+
*.log
6+
7+
.eslintcache
8+
9+
.DS_Store
10+
11+
.vscode
12+
!.vscode/extensions.json
13+
14+
.tsbuildinfo
15+
16+
.turbo
17+
18+
**/local.json

apps/playlist-sticher/Dockerfile

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
FROM node:20.17.0-alpine AS base
2+
3+
RUN apk update
4+
5+
WORKDIR /app
6+
7+
FROM base AS builder
8+
9+
RUN npm i -g turbo
10+
11+
COPY package*.json ./
12+
COPY turbo.json ./
13+
COPY apps/ ./apps
14+
COPY libs/ ./libs
15+
COPY tsconfig.json ./
16+
17+
RUN turbo prune --scope="@apps/playlist-sticher" --docker
18+
19+
FROM base AS installer
20+
21+
COPY --from=builder /app/out/json/ .
22+
COPY --from=builder /app/out/package-lock.json ./package-lock.json
23+
COPY tsconfig.json ./
24+
25+
RUN npm ci
26+
27+
COPY --from=builder /app/out/full/ .
28+
29+
RUN npm run build --filter="@apps/playlist-sticher"
30+
31+
FROM base AS runner
32+
33+
ENV NODE_CONFIG_DIR=/app/apps/playlist-sticher/config
34+
35+
COPY package*.json ./
36+
COPY --from=installer /app/apps/playlist-sticher/dist ./apps/playlist-sticher
37+
COPY --from=installer /app/apps/playlist-sticher/config ./apps/playlist-sticher/config
38+
COPY --from=installer /app/apps/playlist-sticher/package.json ./apps/playlist-sticher/package.json
39+
40+
COPY --from=installer /app/libs/ ./libs/.temp
41+
42+
RUN for folder in /app/libs/.temp/*; do \
43+
package_name=$(basename $folder); \
44+
mkdir -p /app/libs/$package_name; \
45+
cp -r $folder/dist /app/libs/$package_name/dist; \
46+
cp $folder/package.json /app/libs/$package_name/package.json; \
47+
done
48+
49+
RUN rm -rf ./libs/.temp
50+
51+
RUN npm ci
52+
53+
RUN mkdir -p /shared && chmod -R 777 /shared
54+
55+
USER node
56+
57+
CMD node apps/playlist-sticher/src/index.js

apps/playlist-sticher/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Playlist Sticher
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"logLevel": "LOG_LEVEL",
3+
"aws": {
4+
"accessKeyId": "AWS_ACCESS_KEY_ID",
5+
"secretAccessKey": "AWS_SECRET_ACCESS_KEY",
6+
"s3": {
7+
"encodingArtifactsBucket": "AWS_S3_ENCODING_ARTIFACTS_BUCKET"
8+
}
9+
},
10+
"amqp": {
11+
"url": "AMQP_URL"
12+
}
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"appName": "playlist-sticher",
3+
"logLevel": "debug",
4+
"aws": {
5+
"accessKeyId": null,
6+
"secretAccessKey": null,
7+
"region": "eu-central-1",
8+
"s3": {
9+
"encodingArtifactsBucket": null
10+
}
11+
},
12+
"amqp": {
13+
"url": "amqp://test:test@transcoder-rabbitmq:5672",
14+
"redeliveryDropThreshold": 3,
15+
"messageTtl": 30000
16+
}
17+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{
2+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"logLevel": "info"
3+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"aws": {
3+
"accessKeyId": "test",
4+
"secretAccessKey": "test",
5+
"endpoint": "http://127.0.0.1:4566",
6+
"s3": {
7+
"encodingArtifactsBucket": "encoding-artifacts"
8+
}
9+
},
10+
"amqp": {
11+
"url": "amqp://test:test@localhost:5672"
12+
}
13+
}

apps/playlist-sticher/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@apps/playlist-sticher",
3+
"type": "module",
4+
"scripts": {
5+
"start": "node src/index.js",
6+
"start:dev": "node --watch --inspect --no-warnings=ExperimentalWarning --experimental-specifier-resolution=node --loader ts-node/esm/transpile-only src/index.ts",
7+
"build": "tsc --build tsconfig.prod.json",
8+
"build:dev": "tsc --build tsconfig.json",
9+
"test:unit": "vitest .unit.test.ts --config vitest.unit.config.js",
10+
"test:unit:run": "npm run test:unit -- --run",
11+
"test:integration": "NODE_ENV=test vitest --config vitest.integration.config.js .integration.test.ts",
12+
"test:integration:run": "npm run test:integration -- --run",
13+
"lint": "eslint . -c ../../eslint.config.mjs --max-warnings 0",
14+
"lint:fix": "npm run lint -- --fix"
15+
},
16+
"dependencies": {
17+
"@libs/amqp": "*",
18+
"@libs/contracts": "*",
19+
"@libs/errors": "*",
20+
"@libs/logger": "*",
21+
"@libs/s3": "*"
22+
},
23+
"volta": {
24+
"node": "20.17.0",
25+
"npm": "10.8.3"
26+
},
27+
"engines": {
28+
"node": "20.17.0"
29+
}
30+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { type Logger } from '@libs/logger';
2+
import { type Config } from '../../config.js';
3+
import { type EncodingId } from '@libs/contracts';
4+
import { type S3Service } from '@libs/s3';
5+
6+
export interface CreateMasterPlaylstActionPayload {
7+
readonly videoId: string;
8+
readonly encodingId: EncodingId;
9+
}
10+
11+
export class CreateMasterPlaylistAction {
12+
public constructor(
13+
private readonly s3Service: S3Service,
14+
private readonly logger: Logger,
15+
private readonly config: Config,
16+
) {}
17+
18+
public async execute(payload: CreateMasterPlaylstActionPayload): Promise<void> {
19+
const { videoId, encodingId } = payload;
20+
21+
this.logger.debug({
22+
message: 'Creating master HLS playlist...',
23+
videoId,
24+
encodingId,
25+
});
26+
27+
console.log({ s3: this.s3Service, config: this.config });
28+
29+
this.logger.info({
30+
message: 'HLS master playlist created.',
31+
videoId,
32+
encodingId,
33+
});
34+
}
35+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { type ConsumePayload, type MessageConsumer } from '@libs/amqp';
2+
import { Value } from '@sinclair/typebox/value';
3+
import { videoArtifactsUploadedMessageSchema } from '@libs/contracts';
4+
import { type CreateMasterPlaylistAction } from '../../actions/uploadVideoArtifactsAction/createMasterPlaylistAction.js';
5+
6+
export class VideoArtifactsUploadedMessageConsumer implements MessageConsumer {
7+
public constructor(private readonly createMasterPlaylistAction: CreateMasterPlaylistAction) {}
8+
9+
public async consume(payload: ConsumePayload): Promise<void> {
10+
const { videoId, encodingId } = Value.Decode(videoArtifactsUploadedMessageSchema, payload.message);
11+
12+
await this.createMasterPlaylistAction.execute({
13+
videoId,
14+
encodingId,
15+
});
16+
}
17+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { type AmqpChannel, type AmqpConnection, AmqpProvisioner, MessageConsumerExecutor } from '@libs/amqp';
2+
import { type Logger, LoggerFactory } from '@libs/logger';
3+
4+
import { type Config, ConfigFactory } from './config.js';
5+
import { exchangeName, queueNames, routingKeys } from '@libs/contracts';
6+
import { VideoArtifactsUploadedMessageConsumer } from './api/messageConsumers/videoArtifactsUploadedConsumer.js';
7+
import { S3Service, S3ClientFactory } from '@libs/s3';
8+
import { CreateMasterPlaylistAction } from './actions/uploadVideoArtifactsAction/createMasterPlaylistAction.js';
9+
10+
export class Application {
11+
private readonly config: Config;
12+
private readonly logger: Logger;
13+
private amqpConnection: AmqpConnection | undefined;
14+
private amqpChannel: AmqpChannel | undefined;
15+
private readonly amqpProvisioner: AmqpProvisioner;
16+
17+
public constructor() {
18+
this.config = ConfigFactory.create();
19+
20+
this.logger = LoggerFactory.create({
21+
appName: this.config.appName,
22+
logLevel: this.config.logLevel,
23+
});
24+
25+
this.amqpProvisioner = new AmqpProvisioner(this.logger);
26+
}
27+
28+
public async start(): Promise<void> {
29+
await this.setupAmqp();
30+
31+
const s3Service = new S3Service(S3ClientFactory.create(this.config.aws));
32+
33+
const createMasterPlaylistAction = new CreateMasterPlaylistAction(s3Service, this.logger, this.config);
34+
35+
const messageConsumer = new VideoArtifactsUploadedMessageConsumer(createMasterPlaylistAction);
36+
37+
const messageConsumerExecutor = new MessageConsumerExecutor(
38+
messageConsumer,
39+
this.amqpChannel as AmqpChannel,
40+
this.logger,
41+
queueNames.uploadedArtifacts,
42+
this.config.amqp.redeliveryDropThreshold,
43+
);
44+
45+
await messageConsumerExecutor.startConsuming();
46+
}
47+
48+
public async stop(): Promise<void> {
49+
await this.amqpConnection?.close();
50+
}
51+
52+
private async setupAmqp(): Promise<void> {
53+
this.amqpConnection = await this.amqpProvisioner.createConnection({
54+
url: this.config.amqp.url,
55+
});
56+
57+
this.amqpChannel = await this.amqpProvisioner.createChannel({
58+
connection: this.amqpConnection,
59+
});
60+
61+
await this.amqpProvisioner.createQueue({
62+
channel: this.amqpChannel,
63+
exchangeName,
64+
queueName: queueNames.uploadedArtifacts,
65+
pattern: routingKeys.videoArtifactsUploaded,
66+
dlqMessageTtl: this.config.amqp.messageTtl,
67+
});
68+
}
69+
}

0 commit comments

Comments
 (0)