Skip to content

Commit

Permalink
feat: redis, encore clients. first attempt at a working application
Browse files Browse the repository at this point in the history
Signed-off-by: Fredrik Lundkvist <[email protected]>
  • Loading branch information
Lunkers committed Jan 20, 2025
1 parent ead0720 commit 17e0081
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 267 deletions.
4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
testEnvironment: 'node'
};
52 changes: 41 additions & 11 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import logger from './util/logger';
import { EncoreClient } from './encore/encoreclient';
import { MinioClient } from './minio/minio';


const HelloWorld = Type.String({
description: 'The magical words!'
});
Expand All @@ -21,7 +20,11 @@ export interface HealthcheckOptions {
title: string;
}

const healthcheck: FastifyPluginCallback<HealthcheckOptions> = (fastify, opts, next) => {
const healthcheck: FastifyPluginCallback<HealthcheckOptions> = (
fastify,
opts,
next
) => {
fastify.get<{ Reply: Static<typeof HelloWorld> }>(
'/',
{
Expand All @@ -44,17 +47,38 @@ export interface ApiOptions {
}

export default (opts: ApiOptions) => {
logger.info("starting server")
logger.info('starting server');
const config = getConfiguration();

if (!config.serviceAccessToken) {
logger.info(
"No service access token provided. If you're running the app outside of OSC, you won't be able to access the API."
);
} else {
logger.info(
'Service access token provided. You should be able to access the API.'
);
}

const redisclient = new RedisClient(config.redisUrl);

redisclient.connect();

const saveToRedis = (key: string, value: string) => { redisclient.set(key, value) };

const encoreClient = new EncoreClient(config.encoreUrl, config.callbackListenerUrl);
const saveToRedis = (key: string, value: string) => {
redisclient.set(key, value);
};
logger.info(config.callbackListenerUrl);
const encoreClient = new EncoreClient(
config.encoreUrl,
config.callbackListenerUrl,
config.serviceAccessToken
);

const minioClient = new MinioClient(config.minioUrl, config.minioAccessKey, config.minioSecretKey);
const minioClient = new MinioClient(
config.minioUrl,
config.minioAccessKey,
config.minioSecretKey
);

const api = fastify({
ignoreTrailingSlash: true
Expand All @@ -77,15 +101,21 @@ export default (opts: ApiOptions) => {
routePrefix: '/docs'
});


api.register(healthcheck, { title: opts.title });
// register other API routes here

api.register(vastApi, {
adServerUrl: config.adServerUrl,
lookUpAsset: (mediaFile: string) => redisclient.get(mediaFile),
onMissingAsset: async (asset: ManifestAsset) => encoreClient.createEncoreJob(asset),
setupNotification: (asset: ManifestAsset) => minioClient.listenForNotifications("bucket", asset.creativeId, "index.m3u8", () => saveToRedis(asset.creativeId, asset.masterPlaylistUrl))
onMissingAsset: async (asset: ManifestAsset) =>
encoreClient.createEncoreJob(asset),
setupNotification: (asset: ManifestAsset) =>
minioClient.listenForNotifications(
config.minioBucket,
asset.creativeId,
'index.m3u8',
() => saveToRedis(asset.creativeId, asset.masterPlaylistUrl)
)
});
return api;
}
};
74 changes: 38 additions & 36 deletions src/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@

export interface AdNormalizerConfiguration {
encoreUrl: string;
callbackListenerUrl: string;
minioUrl: string;
minioAccessKey: string;
minioSecretKey: string;
minioBucket: string;
adServerUrl: string;
redisUrl: string;
encoreUrl: string;
callbackListenerUrl: string;
minioUrl: string;
minioAccessKey: string;
minioSecretKey: string;
minioBucket: string;
adServerUrl: string;
redisUrl: string;
serviceAccessToken?: string;
}

let config: AdNormalizerConfiguration | null = null;

let loadConfiguration = (): AdNormalizerConfiguration => {
const encoreUrl = process.env.ENCORE_URL;
const callbackListenerUrl = process.env.CALLBACK_LISTENER_URL;
const minioUrl = process.env.MINIO_URL;
const minioAccessKey = process.env.MINIO_ACCESS_KEY;
const minioSecretKey = process.env.MINIO_SECRET_KEY;
const adServerUrl = process.env.AD_SERVER_URL;
const redisUrl = process.env.REDIS_URL;
const minioBucket = process.env.MINIO_BUCKET;
const configuration = {
encoreUrl: encoreUrl,
callbackListenerUrl: callbackListenerUrl,
minioUrl: minioUrl,
minioAccessKey: minioAccessKey,
minioSecretKey: minioSecretKey,
adServerUrl: adServerUrl,
redisUrl: redisUrl,
minioBucket: minioBucket
} as AdNormalizerConfiguration;
const loadConfiguration = (): AdNormalizerConfiguration => {
const encoreUrl = process.env.ENCORE_URL;
const callbackListenerUrl = process.env.CALLBACK_LISTENER_URL;
const minioUrl = process.env.MINIO_URL;
const minioAccessKey = process.env.MINIO_ACCESS_KEY;
const minioSecretKey = process.env.MINIO_SECRET_KEY;
const adServerUrl = process.env.AD_SERVER_URL;
const redisUrl = process.env.REDIS_URL;
const minioBucket = process.env.MINIO_BUCKET;
const serviceAccessToken = process.env.SERVICE_ACCESS_TOKEN;
const configuration = {
encoreUrl: encoreUrl,
callbackListenerUrl: callbackListenerUrl,
minioUrl: minioUrl,
minioAccessKey: minioAccessKey,
minioSecretKey: minioSecretKey,
adServerUrl: adServerUrl,
redisUrl: redisUrl,
minioBucket: minioBucket,
serviceAccessToken: serviceAccessToken
} as AdNormalizerConfiguration;

return configuration;
}
return configuration;
};

/**
* Gets the application config. Configuration is treated as a singleton.
* If the configuration has not been loaded yet, it will be loaded from environment variables.
* If the configuration has not been loaded yet, it will be loaded from environment variables.
* @returns configuration object
*/
export default function getConfiguration(): AdNormalizerConfiguration {
if (config === null) {
config = loadConfiguration();
}
return config as AdNormalizerConfiguration;
}
if (config === null) {
config = loadConfiguration();
}
return config as AdNormalizerConfiguration;
}
75 changes: 44 additions & 31 deletions src/encore/encoreclient.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,51 @@
import logger from "../util/logger"
import { ManifestAsset } from "../vast/vastApi";
import { EncoreJob, InputType } from "./types"
import logger from '../util/logger';
import { ManifestAsset } from '../vast/vastApi';
import { EncoreJob, InputType } from './types';

export class EncoreClient {
constructor(private url: string, private callbackUrl: string) { }
constructor(
private url: string,
private callbackUrl: string,
private serviceAccessToken?: string
) {}

async submitJob(job: EncoreJob): Promise<Response> {
logger.info('Submitting job to Encore', { job });
return fetch(`${this.url}/encoreJobs`, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Accept": "application/hal+json"
},
body: JSON.stringify(job),
})
async submitJob(job: EncoreJob): Promise<Response> {
logger.info('Submitting job to Encore', { job });
let headersWithToken = undefined;
const headers = {
'Content-Type': 'application/json',
Accept: 'application/hal+json'
};
if (this.serviceAccessToken) {
headersWithToken = {
...headers,
'x-jwt': `Bearer ${this.serviceAccessToken}`
};
const sat = this.serviceAccessToken;
}
return fetch(`${this.url}/encoreJobs`, {
method: 'POST',
headers: headersWithToken ? headersWithToken : headers,
body: JSON.stringify(job)
});
}

async createEncoreJob(creative: ManifestAsset): Promise<Response> {
const job: EncoreJob = {
externalId: creative.creativeId,
profile: 'program',
outputFolder: creative.creativeId,
baseName: creative.creativeId,
progressCallbackUri: this.callbackUrl,
inputs: [{
uri: creative.masterPlaylistUrl,
seekTo: 0,
copyTs: true,
type: InputType.AUDIO_VIDEO
}]
async createEncoreJob(creative: ManifestAsset): Promise<Response> {
const job: EncoreJob = {
externalId: creative.creativeId,
profile: 'program',
outputFolder: '/usercontent/',
baseName: creative.creativeId,
progressCallbackUri: this.callbackUrl,
inputs: [
{
uri: 'https://testcontent.eyevinn.technology/mp4/stswe-tvplus-promo.mp4', // TODO: Use the correct URI from the VAST response
seekTo: 0,
copyTs: true,
type: InputType.AUDIO_VIDEO
}
return this.submitJob(job);
}
]
};
return this.submitJob(job);
}
}


43 changes: 22 additions & 21 deletions src/encore/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { EncoreJob, InputFile, InputType } from "./types";
import { EncoreJob, InputFile, InputType } from './types';

const testJob: EncoreJob = {
externalId: "123",
profile: "ad",
outputFolder: "test",
basename: "ad",
progressCallbackUri: "http://localhost:3000",
inputs: [
{
uri: "test",
seekTo: 0,
copyTs: true,
type: InputType.AUDIO_VIDEO
} as InputFile
]
}
externalId: '123',
profile: 'ad',
outputFolder: 'test',
baseName: 'ad',
progressCallbackUri: 'http://localhost:3000',
inputs: [
{
uri: 'test',
seekTo: 0,
copyTs: true,
type: InputType.AUDIO_VIDEO
} as InputFile
]
};

describe('EncoreJob', () => {
it('should serialize to JSON correctly', () => {
const expected = '{"externalId":"123","profile":"ad","outputFolder":"test","basename":"ad","progressCallbackUri":"http://localhost:3000","inputs":[{"uri":"test","seekTo":0,"copyTs":true,"type":"AudioVideo"}]}';
const serialized = JSON.stringify(testJob);
expect(serialized).toEqual(expected);
});
});
it('should serialize to JSON correctly', () => {
const expected =
'{"externalId":"123","profile":"ad","outputFolder":"test","baseName":"ad","progressCallbackUri":"http://localhost:3000","inputs":[{"uri":"test","seekTo":0,"copyTs":true,"type":"AudioVideo"}]}';
const serialized = JSON.stringify(testJob);
expect(serialized).toEqual(expected);
});
});
31 changes: 15 additions & 16 deletions src/encore/types.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
// Minimal implementation of an encore job
export type EncoreJob = {
externalId: string;
profile: string;
outputFolder: string;
baseName: string;
progressCallbackUri: string;
inputs: InputFile[];
}
externalId: string;
profile: string;
outputFolder: string;
baseName: string;
progressCallbackUri: string;
inputs: InputFile[];
};

export type InputFile = {
uri: string;
seekTo: number;
copyTs: boolean;
type: InputType
}
uri: string;
seekTo: number;
copyTs: boolean;
type: InputType;
};

export enum InputType {
AUDIO ="Audio",
VIDEO="Video",
AUDIO_VIDEO="AudioVideo"
AUDIO = 'Audio',
VIDEO = 'Video',
AUDIO_VIDEO = 'AudioVideo'
}

9 changes: 0 additions & 9 deletions src/minio/minio.test.ts

This file was deleted.

Loading

0 comments on commit 17e0081

Please sign in to comment.