From b24d0499d8b5a5886810d11741ce6bb7b6761aae Mon Sep 17 00:00:00 2001 From: Tataihono Nikora Date: Thu, 3 Mar 2022 01:15:11 +0000 Subject: [PATCH 01/35] chore: create api-languages --- apps/api-gateway/supergraph.yml | 4 + apps/api-languages/.env | 2 + apps/api-languages/.env.example | 2 + apps/api-languages/.eslintrc.json | 21 +++++ apps/api-languages/Dockerfile | 8 ++ apps/api-languages/db/db.ts | 13 +++ apps/api-languages/db/seed.ts | 26 ++++++ apps/api-languages/index.ts | 1 + apps/api-languages/jest.config.js | 15 ++++ apps/api-languages/project.json | 90 +++++++++++++++++++ apps/api-languages/schema.graphql | 11 +++ apps/api-languages/src/app/.gitkeep | 0 .../src/app/__generated__/graphql.ts | 19 ++++ apps/api-languages/src/app/app.module.ts | 19 ++++ .../src/app/modules/language/language.graphql | 7 ++ .../app/modules/language/language.module.ts | 11 +++ .../app/modules/language/language.resolver.ts | 15 ++++ .../app/modules/language/language.service.ts | 8 ++ apps/api-languages/src/assets/.gitkeep | 0 .../src/environments/environment.prod.ts | 3 + .../src/environments/environment.ts | 3 + apps/api-languages/src/generate-typings.ts | 20 +++++ apps/api-languages/src/main.ts | 15 ++++ apps/api-languages/tsconfig.app.json | 12 +++ apps/api-languages/tsconfig.json | 13 +++ apps/api-languages/tsconfig.spec.json | 9 ++ workspace.json | 1 + 27 files changed, 348 insertions(+) create mode 100644 apps/api-languages/.env create mode 100644 apps/api-languages/.env.example create mode 100644 apps/api-languages/.eslintrc.json create mode 100644 apps/api-languages/Dockerfile create mode 100644 apps/api-languages/db/db.ts create mode 100644 apps/api-languages/db/seed.ts create mode 100644 apps/api-languages/index.ts create mode 100644 apps/api-languages/jest.config.js create mode 100644 apps/api-languages/project.json create mode 100644 apps/api-languages/schema.graphql create mode 100644 apps/api-languages/src/app/.gitkeep create mode 100644 apps/api-languages/src/app/__generated__/graphql.ts create mode 100644 apps/api-languages/src/app/app.module.ts create mode 100644 apps/api-languages/src/app/modules/language/language.graphql create mode 100644 apps/api-languages/src/app/modules/language/language.module.ts create mode 100644 apps/api-languages/src/app/modules/language/language.resolver.ts create mode 100644 apps/api-languages/src/app/modules/language/language.service.ts create mode 100644 apps/api-languages/src/assets/.gitkeep create mode 100644 apps/api-languages/src/environments/environment.prod.ts create mode 100644 apps/api-languages/src/environments/environment.ts create mode 100644 apps/api-languages/src/generate-typings.ts create mode 100644 apps/api-languages/src/main.ts create mode 100644 apps/api-languages/tsconfig.app.json create mode 100644 apps/api-languages/tsconfig.json create mode 100644 apps/api-languages/tsconfig.spec.json diff --git a/apps/api-gateway/supergraph.yml b/apps/api-gateway/supergraph.yml index 46649d623c1..b729d0e0efd 100644 --- a/apps/api-gateway/supergraph.yml +++ b/apps/api-gateway/supergraph.yml @@ -7,3 +7,7 @@ subgraphs: routing_url: http://127.0.0.1:4002/graphql schema: file: ../api-users/schema.graphql + languages: + routing_url: http://127.0.0.1:4003/graphql + schema: + file: ../api-languages/schema.graphql diff --git a/apps/api-languages/.env b/apps/api-languages/.env new file mode 100644 index 00000000000..c7ab440c167 --- /dev/null +++ b/apps/api-languages/.env @@ -0,0 +1,2 @@ +DATABASE_URL="arangodb://arangodb:8529" +GOOGLE_APPLICATION_JSON= \ No newline at end of file diff --git a/apps/api-languages/.env.example b/apps/api-languages/.env.example new file mode 100644 index 00000000000..c7ab440c167 --- /dev/null +++ b/apps/api-languages/.env.example @@ -0,0 +1,2 @@ +DATABASE_URL="arangodb://arangodb:8529" +GOOGLE_APPLICATION_JSON= \ No newline at end of file diff --git a/apps/api-languages/.eslintrc.json b/apps/api-languages/.eslintrc.json new file mode 100644 index 00000000000..a8ae95b9ee0 --- /dev/null +++ b/apps/api-languages/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "parserOptions": { + "project": ["apps/api-languages/tsconfig.*?.json"] + }, + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/api-languages/Dockerfile b/apps/api-languages/Dockerfile new file mode 100644 index 00000000000..12496372dc0 --- /dev/null +++ b/apps/api-languages/Dockerfile @@ -0,0 +1,8 @@ +FROM node:14-alpine +WORKDIR /app +COPY ./dist/apps/api-languages . +EXPOSE 4002 +# dependencies that nestjs needs +RUN npm install --production --silent +RUN npm install tslib apollo-server-express @nestjs/mapped-types +CMD node ./main.js \ No newline at end of file diff --git a/apps/api-languages/db/db.ts b/apps/api-languages/db/db.ts new file mode 100644 index 00000000000..4a12769acc5 --- /dev/null +++ b/apps/api-languages/db/db.ts @@ -0,0 +1,13 @@ +import { Database } from 'arangojs' + +export function ArangoDB(): Database { + let db: Database + if (process.env.DATABASE_DB != null) + db = new Database({ + url: process.env.DATABASE_URL, + databaseName: process.env.DATABASE_DB + }) + else db = new Database({ url: process.env.DATABASE_URL }) + db.useBasicAuth(process.env.DATABASE_USER, process.env.DATABASE_PASS) + return db +} diff --git a/apps/api-languages/db/seed.ts b/apps/api-languages/db/seed.ts new file mode 100644 index 00000000000..1ebb5e82a3e --- /dev/null +++ b/apps/api-languages/db/seed.ts @@ -0,0 +1,26 @@ +import { ArangoDB } from './db' + +const db = ArangoDB() + +async function main(): Promise { + try { + await db.createCollection('languages', { keyOptions: { type: 'uuid' } }) + } catch {} + await db.collection('languages').ensureIndex({ + type: 'persistent', + fields: ['bcp47'], + name: 'bcp47', + unique: true + }) + await db.collection('languages').ensureIndex({ + type: 'persistent', + fields: ['iso3'], + name: 'iso3', + unique: true + }) +} + +main().catch((e) => { + console.error(e) + process.exit(1) +}) diff --git a/apps/api-languages/index.ts b/apps/api-languages/index.ts new file mode 100644 index 00000000000..da70d960f6c --- /dev/null +++ b/apps/api-languages/index.ts @@ -0,0 +1 @@ +export * from './src/app/__generated__/graphql' diff --git a/apps/api-languages/jest.config.js b/apps/api-languages/jest.config.js new file mode 100644 index 00000000000..c6905415b9d --- /dev/null +++ b/apps/api-languages/jest.config.js @@ -0,0 +1,15 @@ +module.exports = { + displayName: 'api-languages', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json' + } + }, + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': 'ts-jest' + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/apps/api-languages' +} diff --git a/apps/api-languages/project.json b/apps/api-languages/project.json new file mode 100644 index 00000000000..35dbcd2fd7f --- /dev/null +++ b/apps/api-languages/project.json @@ -0,0 +1,90 @@ +{ + "root": "apps/api-languages", + "sourceRoot": "apps/api-languages/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/node:build", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/api-languages", + "main": "apps/api-languages/src/main.ts", + "tsConfig": "apps/api-languages/tsconfig.app.json", + "assets": [ + "apps/api-languages/src/assets", + { + "glob": "**/*.graphql", + "input": "apps/api-languages/src/app/", + "output": "./assets" + } + ], + "generatePackageJson": true + }, + "configurations": { + "production": { + "optimization": true, + "extractLicenses": true, + "inspect": false, + "fileReplacements": [ + { + "replace": "apps/api-languages/src/environments/environment.ts", + "with": "apps/api-languages/src/environments/environment.prod.ts" + } + ] + } + } + }, + "seed": { + "executor": "@nrwl/workspace:run-commands", + "options": { + "command": "npx ts-node apps/api-languages/db/seed.ts" + } + }, + "serve-nowatch": { + "executor": "@nrwl/node:execute", + "options": { + "buildTarget": "api-languages:build" + } + }, + "serve": { + "executor": "@nrwl/workspace:run-commands", + "options": { + "commands": [ + { + "command": "nx serve-nowatch api-languages" + }, + { + "command": "npx ts-node apps/api-languages/src/generate-typings.ts" + } + ], + "parallel": true + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/api-languages/**/*.ts"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/apps/api-languages"], + "options": { + "jestConfig": "apps/api-languages/jest.config.js", + "passWithNoTests": true + } + }, + "generate-graphql": { + "executor": "@nrwl/workspace:run-commands", + "options": { + "commands": [ + { + "command": "rover subgraph introspect http://localhost:4002/graphql > apps/api-languages/schema.graphql" + } + ] + } + } + }, + "tags": [] +} diff --git a/apps/api-languages/schema.graphql b/apps/api-languages/schema.graphql new file mode 100644 index 00000000000..c909a9e1abf --- /dev/null +++ b/apps/api-languages/schema.graphql @@ -0,0 +1,11 @@ +type User @key(fields: "id") { + id: ID! + firstName: String! + lastName: String + email: String! + imageUrl: String +} + +extend type Query { + me: User +} diff --git a/apps/api-languages/src/app/.gitkeep b/apps/api-languages/src/app/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/api-languages/src/app/__generated__/graphql.ts b/apps/api-languages/src/app/__generated__/graphql.ts new file mode 100644 index 00000000000..9fd4f8f2c9f --- /dev/null +++ b/apps/api-languages/src/app/__generated__/graphql.ts @@ -0,0 +1,19 @@ + +/* + * ------------------------------------------------------- + * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) + * ------------------------------------------------------- + */ + +/* tslint:disable */ +/* eslint-disable */ +export class Language { + __typename?: 'Language'; + id: string; +} + +export abstract class IQuery { + abstract languages(): Language[] | Promise; +} + +type Nullable = T | null; diff --git a/apps/api-languages/src/app/app.module.ts b/apps/api-languages/src/app/app.module.ts new file mode 100644 index 00000000000..b6fe8e0ac3c --- /dev/null +++ b/apps/api-languages/src/app/app.module.ts @@ -0,0 +1,19 @@ +import { join } from 'path' +import { Module } from '@nestjs/common' +import { GraphQLFederationModule } from '@nestjs/graphql' +import { LanguageModule } from './modules/language/language.module' + +@Module({ + imports: [ + LanguageModule, + GraphQLFederationModule.forRoot({ + typePaths: [ + join(process.cwd(), 'apps/api-languages/src/app/**/*.graphql'), + join(process.cwd(), 'assets/**/*.graphql') + ], + cors: true, + context: ({ req }) => ({ headers: req.headers }) + }) + ] +}) +export class AppModule {} diff --git a/apps/api-languages/src/app/modules/language/language.graphql b/apps/api-languages/src/app/modules/language/language.graphql new file mode 100644 index 00000000000..4423f6b7e1b --- /dev/null +++ b/apps/api-languages/src/app/modules/language/language.graphql @@ -0,0 +1,7 @@ +type Language @key(fields: "id") { + id: ID! +} + +extend type Query { + languages: [Language!]! +} diff --git a/apps/api-languages/src/app/modules/language/language.module.ts b/apps/api-languages/src/app/modules/language/language.module.ts new file mode 100644 index 00000000000..9a1887e99b6 --- /dev/null +++ b/apps/api-languages/src/app/modules/language/language.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common' +import { DatabaseModule } from '@core/nest/database' +import { LanguageResolver } from './language.resolver' +import { LanguageService } from './language.service' + +@Module({ + imports: [DatabaseModule], + providers: [LanguageResolver, LanguageService], + exports: [LanguageService] +}) +export class LanguageModule {} diff --git a/apps/api-languages/src/app/modules/language/language.resolver.ts b/apps/api-languages/src/app/modules/language/language.resolver.ts new file mode 100644 index 00000000000..1eb6a9a7ffc --- /dev/null +++ b/apps/api-languages/src/app/modules/language/language.resolver.ts @@ -0,0 +1,15 @@ +import { Resolver, Query } from '@nestjs/graphql' +import { KeyAsId } from '@core/nest/decorators' +import { Language } from '../../__generated__/graphql' +import { LanguageService } from './language.service' + +@Resolver('Language') +export class LanguageResolver { + constructor(private readonly languageService: LanguageService) {} + + @Query() + @KeyAsId() + async languages(): Promise { + return await this.languageService.getAll() + } +} diff --git a/apps/api-languages/src/app/modules/language/language.service.ts b/apps/api-languages/src/app/modules/language/language.service.ts new file mode 100644 index 00000000000..452b77da1cb --- /dev/null +++ b/apps/api-languages/src/app/modules/language/language.service.ts @@ -0,0 +1,8 @@ +import { BaseService } from '@core/nest/database' +import { Injectable } from '@nestjs/common' +import { DocumentCollection } from 'arangojs/collection' + +@Injectable() +export class LanguageService extends BaseService { + collection: DocumentCollection = this.db.collection('languages') +} diff --git a/apps/api-languages/src/assets/.gitkeep b/apps/api-languages/src/assets/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/api-languages/src/environments/environment.prod.ts b/apps/api-languages/src/environments/environment.prod.ts new file mode 100644 index 00000000000..bc0327dbebd --- /dev/null +++ b/apps/api-languages/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +} diff --git a/apps/api-languages/src/environments/environment.ts b/apps/api-languages/src/environments/environment.ts new file mode 100644 index 00000000000..b676dc71d19 --- /dev/null +++ b/apps/api-languages/src/environments/environment.ts @@ -0,0 +1,3 @@ +export const environment = { + production: false +} diff --git a/apps/api-languages/src/generate-typings.ts b/apps/api-languages/src/generate-typings.ts new file mode 100644 index 00000000000..9d9ef20d879 --- /dev/null +++ b/apps/api-languages/src/generate-typings.ts @@ -0,0 +1,20 @@ +import { join } from 'path' +import { GraphQLDefinitionsFactory } from '@nestjs/graphql' + +const definitionsFactory = new GraphQLDefinitionsFactory() +definitionsFactory + .generate({ + federation: true, + typePaths: [join(process.cwd(), 'apps/api-languages/src/app/**/*.graphql')], + path: join( + process.cwd(), + 'apps/api-languages/src/app/__generated__/graphql.ts' + ), + outputAs: 'class', + watch: true, + emitTypenameField: true, + customScalarTypeMapping: { + DateTime: 'String' + } + }) + .catch((err) => console.log(err)) diff --git a/apps/api-languages/src/main.ts b/apps/api-languages/src/main.ts new file mode 100644 index 00000000000..f48950768fa --- /dev/null +++ b/apps/api-languages/src/main.ts @@ -0,0 +1,15 @@ +import { Logger } from '@nestjs/common' +import { NestFactory } from '@nestjs/core' +import { AppModule } from './app/app.module' + +async function bootstrap(): Promise { + const port = process.env.PORT ?? '4003' + const app = await NestFactory.create(AppModule) + await app.listen(port, () => { + Logger.log('Listening at http://localhost:' + port + '/graphql') + }) +} + +bootstrap().catch((err) => { + console.log(err) +}) diff --git a/apps/api-languages/tsconfig.app.json b/apps/api-languages/tsconfig.app.json new file mode 100644 index 00000000000..0a067b3bb5c --- /dev/null +++ b/apps/api-languages/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["node"], + "emitDecoratorMetadata": true, + "target": "es2015" + }, + "exclude": ["**/*.spec.ts"], + "include": ["**/*.ts"] +} diff --git a/apps/api-languages/tsconfig.json b/apps/api-languages/tsconfig.json new file mode 100644 index 00000000000..63dbe35fb28 --- /dev/null +++ b/apps/api-languages/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/apps/api-languages/tsconfig.spec.json b/apps/api-languages/tsconfig.spec.json new file mode 100644 index 00000000000..29efa430b2b --- /dev/null +++ b/apps/api-languages/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["**/*.spec.ts", "**/*.d.ts"] +} diff --git a/workspace.json b/workspace.json index 19c52b4c185..b3eb8be84ad 100644 --- a/workspace.json +++ b/workspace.json @@ -3,6 +3,7 @@ "projects": { "api-gateway": "apps/api-gateway", "api-journeys": "apps/api-journeys", + "api-languages": "apps/api-languages", "api-users": "apps/api-users", "apollo-logging": "libs/apollo/logging", "journeys": "apps/journeys", From a62b23717990a877c7bc1756a36c2a357d42da54 Mon Sep 17 00:00:00 2001 From: Tataihono Nikora Date: Thu, 3 Mar 2022 02:37:12 +0000 Subject: [PATCH 02/35] feat: fetch data from arclight api --- apps/api-languages/db/seed.ts | 77 +++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/apps/api-languages/db/seed.ts b/apps/api-languages/db/seed.ts index 1ebb5e82a3e..1fa6e4a889e 100644 --- a/apps/api-languages/db/seed.ts +++ b/apps/api-languages/db/seed.ts @@ -1,23 +1,76 @@ +import { aql } from 'arangojs' +import { isEmpty } from 'lodash' +import fetch from 'node-fetch' import { ArangoDB } from './db' +interface Language { + _key: string +} + const db = ArangoDB() async function main(): Promise { try { await db.createCollection('languages', { keyOptions: { type: 'uuid' } }) } catch {} - await db.collection('languages').ensureIndex({ - type: 'persistent', - fields: ['bcp47'], - name: 'bcp47', - unique: true - }) - await db.collection('languages').ensureIndex({ - type: 'persistent', - fields: ['iso3'], - name: 'iso3', - unique: true - }) + const collection = db.collection('languages') + const data = await ( + await fetch( + 'https://api.arclight.org/v2/media-languages?page=1&limit=5000&filter=default&apiKey=50105582a2e5a6.72068725' + ) + ).json() + + async function getLanguage( + languageId: number + ): Promise { + const rst = await db.query(aql` + FOR item IN ${collection} + FILTER item._key == ${languageId.toString()} + LIMIT 1 + RETURN item`) + return await rst.next() + } + + async function getLanguageByBcp47( + bcp47: string + ): Promise { + const rst = await db.query(aql` + FOR item IN ${collection} + FILTER item.bcp47 == ${bcp47} + LIMIT 1 + RETURN item`) + return await rst.next() + } + + for (const mediaLanguage of data._embedded.mediaLanguages) { + const { languageId, bcp47, iso3, nameNative } = mediaLanguage + const body = { + bcp47: isEmpty(bcp47) ? undefined : bcp47, + iso3: isEmpty(iso3) ? undefined : iso3, + nameNative + } + const language = await getLanguage(languageId) + if (language != null) { + await collection.update(languageId.toString(), body) + } else { + await collection.save({ _key: languageId.toString(), ...body }) + } + } + + for (const mediaLanguage of data._embedded.mediaLanguages) { + const { languageId, metadataLanguageTag, name } = mediaLanguage + const metadataLanguage = await getLanguageByBcp47(metadataLanguageTag) + if (metadataLanguage == null) continue + const body = { + names: [ + { + name, + languageId: metadataLanguage._key + } + ] + } + await collection.update(languageId.toString(), body) + } } main().catch((e) => { From 32060a58eadf09b725c11393d0841445100f7c30 Mon Sep 17 00:00:00 2001 From: Tataihono Nikora Date: Mon, 7 Mar 2022 00:03:07 +0000 Subject: [PATCH 03/35] chore: add videos api --- apps/api-gateway/project.json | 6 + apps/api-gateway/supergraph.yml | 4 + apps/api-videos/.env | 2 + apps/api-videos/.env.example | 2 + apps/api-videos/.eslintrc.json | 21 ++ apps/api-videos/Dockerfile | 8 + apps/api-videos/db/db.ts | 13 ++ apps/api-videos/db/seed.ts | 184 ++++++++++++++++++ apps/api-videos/index.ts | 1 + apps/api-videos/jest.config.js | 15 ++ apps/api-videos/project.json | 90 +++++++++ apps/api-videos/schema.graphql | 11 ++ apps/api-videos/src/app/.gitkeep | 0 .../src/app/__generated__/graphql.ts | 19 ++ apps/api-videos/src/app/app.module.ts | 19 ++ .../src/app/modules/compilation/video.graphql | 7 + .../app/modules/compilation/video.module.ts | 11 ++ .../app/modules/compilation/video.resolver.ts | 15 ++ .../app/modules/compilation/video.service.ts | 8 + apps/api-videos/src/assets/.gitkeep | 0 .../src/environments/environment.prod.ts | 3 + .../src/environments/environment.ts | 3 + apps/api-videos/src/generate-typings.ts | 20 ++ apps/api-videos/src/main.ts | 15 ++ apps/api-videos/tsconfig.app.json | 12 ++ apps/api-videos/tsconfig.json | 13 ++ apps/api-videos/tsconfig.spec.json | 9 + workspace.json | 1 + 28 files changed, 512 insertions(+) create mode 100644 apps/api-videos/.env create mode 100644 apps/api-videos/.env.example create mode 100644 apps/api-videos/.eslintrc.json create mode 100644 apps/api-videos/Dockerfile create mode 100644 apps/api-videos/db/db.ts create mode 100644 apps/api-videos/db/seed.ts create mode 100644 apps/api-videos/index.ts create mode 100644 apps/api-videos/jest.config.js create mode 100644 apps/api-videos/project.json create mode 100644 apps/api-videos/schema.graphql create mode 100644 apps/api-videos/src/app/.gitkeep create mode 100644 apps/api-videos/src/app/__generated__/graphql.ts create mode 100644 apps/api-videos/src/app/app.module.ts create mode 100644 apps/api-videos/src/app/modules/compilation/video.graphql create mode 100644 apps/api-videos/src/app/modules/compilation/video.module.ts create mode 100644 apps/api-videos/src/app/modules/compilation/video.resolver.ts create mode 100644 apps/api-videos/src/app/modules/compilation/video.service.ts create mode 100644 apps/api-videos/src/assets/.gitkeep create mode 100644 apps/api-videos/src/environments/environment.prod.ts create mode 100644 apps/api-videos/src/environments/environment.ts create mode 100644 apps/api-videos/src/generate-typings.ts create mode 100644 apps/api-videos/src/main.ts create mode 100644 apps/api-videos/tsconfig.app.json create mode 100644 apps/api-videos/tsconfig.json create mode 100644 apps/api-videos/tsconfig.spec.json diff --git a/apps/api-gateway/project.json b/apps/api-gateway/project.json index 30704091044..8fa1ff97f99 100644 --- a/apps/api-gateway/project.json +++ b/apps/api-gateway/project.json @@ -42,6 +42,12 @@ { "command": "nx serve api-users" }, + { + "command": "nx serve api-languages" + }, + { + "command": "nx serve api-videos" + }, { "command": "nx serve api-gateway" } diff --git a/apps/api-gateway/supergraph.yml b/apps/api-gateway/supergraph.yml index b729d0e0efd..cf1f667e765 100644 --- a/apps/api-gateway/supergraph.yml +++ b/apps/api-gateway/supergraph.yml @@ -11,3 +11,7 @@ subgraphs: routing_url: http://127.0.0.1:4003/graphql schema: file: ../api-languages/schema.graphql + videos: + routing_url: http://127.0.0.1:4004/graphql + schema: + file: ../api-videos/schema.graphql diff --git a/apps/api-videos/.env b/apps/api-videos/.env new file mode 100644 index 00000000000..e0df6388eda --- /dev/null +++ b/apps/api-videos/.env @@ -0,0 +1,2 @@ +DATABASE_URL="arangodb://arangodb:8529" +ARCLIGHT_API_KEY= \ No newline at end of file diff --git a/apps/api-videos/.env.example b/apps/api-videos/.env.example new file mode 100644 index 00000000000..e0df6388eda --- /dev/null +++ b/apps/api-videos/.env.example @@ -0,0 +1,2 @@ +DATABASE_URL="arangodb://arangodb:8529" +ARCLIGHT_API_KEY= \ No newline at end of file diff --git a/apps/api-videos/.eslintrc.json b/apps/api-videos/.eslintrc.json new file mode 100644 index 00000000000..a7eb98f6587 --- /dev/null +++ b/apps/api-videos/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "parserOptions": { + "project": ["apps/api-videos/tsconfig.*?.json"] + }, + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/api-videos/Dockerfile b/apps/api-videos/Dockerfile new file mode 100644 index 00000000000..74970a7d39b --- /dev/null +++ b/apps/api-videos/Dockerfile @@ -0,0 +1,8 @@ +FROM node:14-alpine +WORKDIR /app +COPY ./dist/apps/api-videos . +EXPOSE 4002 +# dependencies that nestjs needs +RUN npm install --production --silent +RUN npm install tslib apollo-server-express @nestjs/mapped-types +CMD node ./main.js \ No newline at end of file diff --git a/apps/api-videos/db/db.ts b/apps/api-videos/db/db.ts new file mode 100644 index 00000000000..4a12769acc5 --- /dev/null +++ b/apps/api-videos/db/db.ts @@ -0,0 +1,13 @@ +import { Database } from 'arangojs' + +export function ArangoDB(): Database { + let db: Database + if (process.env.DATABASE_DB != null) + db = new Database({ + url: process.env.DATABASE_URL, + databaseName: process.env.DATABASE_DB + }) + else db = new Database({ url: process.env.DATABASE_URL }) + db.useBasicAuth(process.env.DATABASE_USER, process.env.DATABASE_PASS) + return db +} diff --git a/apps/api-videos/db/seed.ts b/apps/api-videos/db/seed.ts new file mode 100644 index 00000000000..6c00fcce717 --- /dev/null +++ b/apps/api-videos/db/seed.ts @@ -0,0 +1,184 @@ +import { aql } from 'arangojs' +import fetch from 'node-fetch' +import { ArangoDB } from './db' + +interface Video { + _key: string +} + +const db = ArangoDB() + +interface MediaComponent { + mediaComponentId: string + primaryLanguageId: number + title: string + shortDescription: string + longDescription: string + metadataLanguageTag: string + imageUrls: { + mobileCinematicHigh: string + } +} + +interface MediaComponentLanguage { + refId: string + languageId: number + lengthInMilliseconds: number + subtitleUrls: { + vtt?: Array<{ + languageId: number + url: string + }> + } + streamingUrls: { + hls: Array<{ + url: string + }> + } +} + +interface Language { + languageId: number + bcp47: string +} + +async function getLanguages(): Promise { + const response: { + _embedded: { mediaLanguages: Language[] } + } = await ( + await fetch( + `https://api.arclight.org/v2/media-languages?limit=5000&filter=default&apiKey=${ + process.env.ARCLIGHT_API_KEY ?? '' + }` + ) + ).json() + return response._embedded.mediaLanguages +} + +async function getMediaComponents(): Promise { + const response: { + _embedded: { mediaComponents: MediaComponent[] } + } = await ( + await fetch( + `https://api.arclight.org/v2/media-components?limit=5000&isDeprecated=false&type=content&contentTypes=video&apiKey=${ + process.env.ARCLIGHT_API_KEY ?? '' + }` + ) + ).json() + return response._embedded.mediaComponents +} + +async function getMediaComponentLanguage( + mediaComponentId: string +): Promise { + const response: { + _embedded: { mediaComponentLanguage: MediaComponentLanguage[] } + } = await ( + await fetch( + `https://api.arclight.org/v2/media-components/${mediaComponentId}/languages?apiKey=${ + process.env.ARCLIGHT_API_KEY ?? '' + }` + ) + ).json() + return response._embedded.mediaComponentLanguage +} + +async function digestMediaComponent( + languages: Language[], + mediaComponent: MediaComponent +): Promise { + const metadataLanguageId = languages + .find(({ bcp47 }) => bcp47 === mediaComponent.metadataLanguageTag) + ?.languageId.toString() + + console.log('mediaComponent:', mediaComponent.mediaComponentId) + + for (const mediaComponentLanguage of await getMediaComponentLanguage( + mediaComponent.mediaComponentId + )) { + await digestMediaComponentLanguage( + { + ...mediaComponent, + metadataLanguageId: metadataLanguageId ?? '529' // english by default + }, + mediaComponentLanguage + ) + } +} + +async function digestMediaComponentLanguage( + mediaComponent: MediaComponent & { metadataLanguageId: string }, + mediaComponentLanguage: MediaComponentLanguage +): Promise { + const body = { + title: [ + { + value: mediaComponent.title, + languageId: mediaComponent.metadataLanguageId + } + ], + snippet: [ + { + value: mediaComponent.shortDescription, + languageId: mediaComponent.metadataLanguageId + } + ], + description: [ + { + value: mediaComponent.longDescription, + languageId: mediaComponent.metadataLanguageId + } + ], + subtitles: + mediaComponentLanguage.subtitleUrls.vtt?.map(({ languageId, url }) => ({ + languageId: languageId.toString(), + url + })) ?? [], + hls: mediaComponentLanguage.streamingUrls.hls[0].url, + languageId: mediaComponentLanguage.languageId.toString(), + duration: Math.round(mediaComponentLanguage.lengthInMilliseconds * 0.001), + image: mediaComponent.imageUrls.mobileCinematicHigh + } + console.log('mediaComponentLanguage:', mediaComponentLanguage.refId) + const video = await getVideo(mediaComponentLanguage.refId) + if (video != null) { + await db.collection('videos').update(mediaComponentLanguage.refId, body) + } else { + await db + .collection('videos') + .save({ _key: mediaComponentLanguage.refId, ...body }) + } +} + +async function getVideo(videoId: string): Promise