Skip to content

Commit d117e08

Browse files
committed
wip
1 parent 23dea2f commit d117e08

61 files changed

Lines changed: 8830 additions & 4089 deletions

Some content is hidden

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

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,5 @@ Bumpgen offers a number of default templates to use for your channels, as well a
5050
- Plugins: some kind of versioning + publish types + example repo + language/locale in template
5151
- Frontend for configuring
5252
- Allow animations?
53+
- Unit tests
54+
- Publishing & output comparison (maybe via some kind of PR & github actions?)

apps/backend/.swcrc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
"jsc": {
33
"parser": {
44
"syntax": "typescript",
5-
"tsx": false
5+
"tsx": false,
6+
"decorators": true
7+
},
8+
"target": "es2016",
9+
"transform": {
10+
"legacyDecorator": false,
11+
"decoratorMetadata": true
612
}
713
}
814
}

apps/backend/package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@
2424
"dotenv": "^16.4.7",
2525
"fabric": "^6.5.4",
2626
"fastify": "^5.1.0",
27+
"fastify-plugin": "^5.0.1",
2728
"ffmpeg-static": "^4.4.1",
2829
"ffprobe-static": "^3.1.0",
2930
"fluent-ffmpeg": "^2.1.3",
3031
"glob": "^11.0.0",
3132
"gsap": "^3.12.5",
3233
"node-schedule": "^2.1.1",
33-
"ts-node": "^10.9.2"
34+
"reflect-metadata": "^0.2.2",
35+
"ts-node": "^10.9.2",
36+
"typedi": "^0.10.0",
37+
"ws": "^8.18.0"
3438
},
3539
"devDependencies": {
3640
"@swc/cli": "^0.5.2",
@@ -39,6 +43,7 @@
3943
"@types/ffmpeg-static": "^3.0.3",
4044
"@types/ffprobe-static": "^2.0.3",
4145
"@types/fluent-ffmpeg": "^2.1.27",
42-
"@types/node-schedule": "^2.1.7"
46+
"@types/node-schedule": "^2.1.7",
47+
"@types/ws": "^8.5.14"
4348
}
4449
}

apps/backend/src/api/index.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Fastify from "fastify";
2+
3+
import errorsPlugin from "./plugins/errors.js";
4+
import isInitiliazedPlugin from "./plugins/isInitiliazed.js";
5+
6+
import settingsRoutes from "./routes/settings.js";
7+
import cacheRoutes from "./routes/cache.js";
8+
import initializeRoutes from "./routes/initialize.js";
9+
import Container from "typedi";
10+
import { LiveStatsService } from "../services/LiveStatsService.js";
11+
12+
const V1_API_BASE = "/api/v1";
13+
14+
export const initializeApi = async () => {
15+
const fastify = Fastify({
16+
logger: true,
17+
});
18+
19+
// Register plugins
20+
fastify.register(errorsPlugin);
21+
22+
// Register routers that can be accessed pre-initialization
23+
fastify.register(initializeRoutes, { prefix: `${V1_API_BASE}/initialize` });
24+
25+
fastify.register(isInitiliazedPlugin, (fastifyWithAppInitialized) => {
26+
// Register routes
27+
fastifyWithAppInitialized.register(settingsRoutes, {
28+
prefix: `${V1_API_BASE}/settings`,
29+
});
30+
fastifyWithAppInitialized.register(cacheRoutes, {
31+
prefix: `${V1_API_BASE}/cache`,
32+
});
33+
});
34+
35+
fastify.server.on("upgrade", (req, socket, head) => {
36+
console.log(req.url);
37+
if (req.url === "/ws") {
38+
Container.get(LiveStatsService).wss.handleUpgrade(
39+
req,
40+
socket,
41+
head,
42+
function done(ws) {
43+
Container.get(LiveStatsService).wss.emit("connection", ws, req);
44+
},
45+
);
46+
}
47+
});
48+
49+
await fastify.listen({ port: 4000 });
50+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import type { FastifyPluginCallback } from "fastify";
2+
import fp from "fastify-plugin";
3+
4+
export enum ApiErrorType {
5+
APP_NOT_INITIALIZED = 100,
6+
APP_ALREADY_INITIALIZED = 101,
7+
UNABLE_TO_INITIALIZE = 102,
8+
UNABLE_TO_ADD_RESOURCE = 200,
9+
UNABLE_TO_UPDATE_RESOURCE = 201,
10+
}
11+
12+
declare module "fastify" {
13+
interface FastifyReply {
14+
sendError: (errorType: ApiErrorType, errorSpecifics?: unknown) => void;
15+
}
16+
}
17+
18+
const pluginCallback: FastifyPluginCallback = (fastify, options, done) => {
19+
fastify.decorateReply(
20+
"sendError",
21+
function (errorType: ApiErrorType, errorSpecifics: unknown) {
22+
const baseError = {
23+
error: true,
24+
code: errorType,
25+
errorSpecifics,
26+
};
27+
28+
switch (errorType) {
29+
case ApiErrorType.APP_NOT_INITIALIZED: {
30+
this.status(500).send({
31+
...baseError,
32+
message: "App is not initialized",
33+
});
34+
break;
35+
}
36+
case ApiErrorType.APP_ALREADY_INITIALIZED: {
37+
this.status(500).send({
38+
...baseError,
39+
message: "App is already initialized",
40+
});
41+
break;
42+
}
43+
case ApiErrorType.UNABLE_TO_INITIALIZE: {
44+
this.status(500).send({
45+
...baseError,
46+
message: "Failed to initialize app",
47+
});
48+
break;
49+
}
50+
case ApiErrorType.UNABLE_TO_ADD_RESOURCE: {
51+
this.status(500).send({
52+
...baseError,
53+
message: "Unable to add resource",
54+
});
55+
break;
56+
}
57+
case ApiErrorType.UNABLE_TO_UPDATE_RESOURCE: {
58+
this.status(500).send({
59+
...baseError,
60+
message: "Unable to update resource",
61+
});
62+
break;
63+
}
64+
}
65+
},
66+
);
67+
68+
done();
69+
};
70+
71+
export default fp(pluginCallback);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { FastifyPluginCallback } from "fastify";
2+
import fp from "fastify-plugin";
3+
import { ApiErrorType } from "./errors.js";
4+
import { Container } from "typedi";
5+
import { AppConfigService } from "../../services/AppConfigService.js";
6+
7+
const pluginCallback: FastifyPluginCallback = (fastify, options, done) => {
8+
fastify.addHook("onRequest", (request, reply, done) => {
9+
if (!Container.get(AppConfigService).isInitialized) {
10+
reply.sendError(ApiErrorType.APP_NOT_INITIALIZED);
11+
} else {
12+
done();
13+
}
14+
});
15+
16+
done();
17+
};
18+
19+
export default fp(pluginCallback, {
20+
encapsulate: true,
21+
decorators: {
22+
reply: ["sendError"],
23+
},
24+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type { FastifyInstance /* FastifyRegisterOptions */ } from "fastify";
2+
3+
import { type Static, Type } from "@sinclair/typebox";
4+
import { Container } from "typedi";
5+
import { XmlTvService } from "../../services/XmlTvService.js";
6+
import { BackgroundContentService } from "../../services/BackgroundContentService.js";
7+
import { TemplatesService } from "../../services/TemplatesService.js";
8+
import { unwrap } from "../../result/index.js";
9+
10+
const CacheDataSchema = Type.Object({
11+
channels: Type.Array(
12+
Type.Object({
13+
name: Type.String(),
14+
id: Type.String(),
15+
}),
16+
),
17+
backgroundContent: Type.Array(
18+
Type.Object({
19+
fullPath: Type.String(),
20+
relativePath: Type.String(),
21+
length: Type.Number(),
22+
}),
23+
),
24+
templates: Type.Array(
25+
Type.Object({
26+
name: Type.String(),
27+
}),
28+
),
29+
});
30+
31+
type CacheData = Static<typeof CacheDataSchema>;
32+
33+
const routes = async (
34+
fastify: FastifyInstance,
35+
// options: FastifyRegisterOptions<never>,
36+
) => {
37+
fastify.get<{ Reply: CacheData }>(
38+
"/",
39+
{ schema: { response: { 200: CacheDataSchema } } },
40+
(request, reply) => {
41+
const xmlTvService = Container.get(XmlTvService);
42+
const backgroundContentService = Container.get(BackgroundContentService);
43+
const templatesService = Container.get(TemplatesService);
44+
45+
reply.status(200).send({
46+
channels: xmlTvService.channels.map((channel) => ({
47+
name:
48+
unwrap(
49+
xmlTvService.getValueForConfiguredLang(channel.displayName),
50+
) || "unknown",
51+
id: channel.id,
52+
})),
53+
backgroundContent: backgroundContentService.files.map(
54+
({ fullPath, relativePath, length }) => ({
55+
fullPath,
56+
relativePath,
57+
length,
58+
}),
59+
),
60+
templates: Object.keys(templatesService.templates).map((name) => ({
61+
name,
62+
})),
63+
});
64+
},
65+
);
66+
};
67+
68+
export default routes;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { FastifyInstance /* FastifyRegisterOptions */ } from "fastify";
2+
import { type Static, Type } from "@sinclair/typebox";
3+
import { ApiErrorType } from "../plugins/errors.js";
4+
import { isFailure } from "../../result/index.js";
5+
import { Container } from "typedi";
6+
import { AppConfigService } from "../../services/AppConfigService.js";
7+
8+
const InitializeSchema = Type.Object({
9+
xmlTvUrl: Type.String(),
10+
language: Type.String(),
11+
outputFolder: Type.String(),
12+
backgroundContentFolder: Type.String(),
13+
});
14+
15+
type InitializeSchema = Static<typeof InitializeSchema>;
16+
17+
const routes = async (
18+
fastify: FastifyInstance,
19+
// options: FastifyRegisterOptions<never>,
20+
) => {
21+
fastify.get("/", () => {
22+
const appConfigService = Container.get(AppConfigService);
23+
return {
24+
isInitialized: appConfigService.isInitialized,
25+
};
26+
});
27+
28+
fastify.post<{ Body: InitializeSchema }>(
29+
"/",
30+
{ schema: { body: InitializeSchema } },
31+
async (request, reply) => {
32+
const appConfigService = Container.get(AppConfigService);
33+
if (appConfigService.isInitialized) {
34+
reply.sendError(ApiErrorType.APP_ALREADY_INITIALIZED);
35+
} else {
36+
const result = await appConfigService.initializeFromDefaults(
37+
request.body.xmlTvUrl,
38+
request.body.language,
39+
request.body.outputFolder,
40+
request.body.backgroundContentFolder,
41+
);
42+
43+
if (isFailure(result)) {
44+
reply.sendError(
45+
ApiErrorType.UNABLE_TO_INITIALIZE,
46+
result.error.message,
47+
);
48+
} else {
49+
reply.status(200).send(appConfigService.config);
50+
}
51+
}
52+
},
53+
);
54+
};
55+
56+
export default routes;

0 commit comments

Comments
 (0)