Skip to content

Commit 7e9a1b9

Browse files
committed
Add more web app & live stats service
1 parent d117e08 commit 7e9a1b9

14 files changed

Lines changed: 696 additions & 88 deletions

File tree

apps/backend/src/api/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import isInitiliazedPlugin from "./plugins/isInitiliazed.js";
66
import settingsRoutes from "./routes/settings.js";
77
import cacheRoutes from "./routes/cache.js";
88
import initializeRoutes from "./routes/initialize.js";
9-
import Container from "typedi";
9+
import { Container } from "typedi";
1010
import { LiveStatsService } from "../services/LiveStatsService.js";
1111

1212
const V1_API_BASE = "/api/v1";
@@ -33,13 +33,16 @@ export const initializeApi = async () => {
3333
});
3434

3535
fastify.server.on("upgrade", (req, socket, head) => {
36+
console.log("HELLO WORLD!");
3637
console.log(req.url);
37-
if (req.url === "/ws") {
38+
if (req.url === "/api/v1/ws") {
39+
console.log("upgrading...");
3840
Container.get(LiveStatsService).wss.handleUpgrade(
3941
req,
4042
socket,
4143
head,
4244
function done(ws) {
45+
console.log("done!");
4346
Container.get(LiveStatsService).wss.emit("connection", ws, req);
4447
},
4548
);

apps/backend/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { TemplatesService } from "./services/TemplatesService.js";
1111
import { XmlTvService } from "./services/XmlTvService.js";
1212
import { JobSchedulerService } from "./services/JobSchedulerService.js";
1313
import { logInfo } from "./logger/index.js";
14+
import { LiveStatsService } from "./services/LiveStatsService.js";
1415

1516
const initialize = async () => {
1617
// appConfig.onInitialised(async () => {
@@ -31,6 +32,7 @@ const initialize = async () => {
3132
await Container.get(BackgroundContentService).load();
3233
await Container.get(XmlTvService).load();
3334
await Container.get(JobSchedulerService).load();
35+
await Container.get(LiveStatsService).load();
3436

3537
logInfo("Services successfully started");
3638

apps/backend/src/jobs/main.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ const channelTask = async (
7575
),
7676
},
7777
background: backgroundContent.result,
78+
onGenerateStart: () =>
79+
Container.get(LiveStatsService).running(channel.id, "generating"),
7880
});
7981
};
8082

@@ -84,6 +86,7 @@ export default {
8486
job: async () => {
8587
logInfo(`Starting main job...`);
8688
const liveStatsService = Container.get(LiveStatsService);
89+
liveStatsService.start();
8790

8891
const xmlTvResult = await Container.get(XmlTvService).run();
8992
if (isSuccess(xmlTvResult)) {
@@ -99,10 +102,11 @@ export default {
99102
}
100103

101104
for (const channel of Container.get(XmlTvService).channels) {
102-
liveStatsService.running(channel.id);
105+
liveStatsService.running(channel.id, "starting");
103106
const channelConfig = getChannelConfig(channel.id);
104107
if (!channelConfig) {
105108
logDebug(`Skipping channel ${channel.id}, no config available`);
109+
liveStatsService.channelResult(channel.id, 0, "not-configured");
106110
continue;
107111
}
108112

@@ -113,7 +117,12 @@ export default {
113117
`Failed channel task for channel ${channel.id}`,
114118
result.error,
115119
);
120+
liveStatsService.channelResult(channel.id, 0, "error");
116121
} else {
122+
const resultString =
123+
result.result === "generated" ? "generated" : "up-to-date";
124+
liveStatsService.channelResult(channel.id, 0, resultString);
125+
117126
logInfo(
118127
`Completed task for channel ${channel.id} (Video ${result.result.replace("-", " ")})`,
119128
);

apps/backend/src/services/LiveStatsService.ts

Lines changed: 138 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,23 @@ import { WebSocketServer, WebSocket } from "ws";
1515

1616
import { Service } from "typedi";
1717
import type { BumpgenService } from "./types";
18-
import { success } from "../result";
19-
import { AppConfigService } from "./AppConfigService";
20-
import type { JobSchedulerService } from "./JobSchedulerService";
18+
import { success } from "../result/index.js";
19+
import { AppConfigService } from "./AppConfigService.js";
20+
import { JobSchedulerService } from "./JobSchedulerService.js";
21+
import { XmlTvService } from "./XmlTvService.js";
2122

2223
type StatusEvent =
24+
| {
25+
status: "starting"; // Fetching XML TV Data
26+
data: Record<string, never>;
27+
}
2328
| {
2429
status: "running";
2530
data: {
31+
numChannels: number;
32+
channelIndex: number;
2633
channelId: string;
34+
task: "starting" | "generating";
2735
};
2836
}
2937
| {
@@ -35,12 +43,22 @@ type StatusEvent =
3543
};
3644

3745
interface StatsEvent {
38-
lastRun: number;
46+
lastRun: {
47+
time: number;
48+
channelIds: string[];
49+
channelResults: Record<
50+
string,
51+
{
52+
duration: number;
53+
result: "generated" | "up-to-date" | "error" | "not-configured";
54+
}
55+
>;
56+
};
3957
}
4058

4159
type Event =
42-
| { name: "status"; event: StatusEvent }
43-
| { name: "stats"; event: StatsEvent };
60+
| { name: "status"; event: StatusEvent; id: number }
61+
| { name: "stats"; event: StatsEvent; id: number };
4462

4563
@Service()
4664
export class LiveStatsService implements BumpgenService {
@@ -50,46 +68,123 @@ export class LiveStatsService implements BumpgenService {
5068
return this._wss;
5169
}
5270

53-
private lastStatsEvent: Event | undefined;
71+
private idCount = 0;
72+
73+
private latestStatusEvent: StatusEvent | undefined;
74+
private latestStatsEvent: StatsEvent | undefined;
5475

5576
constructor(
5677
public appConfigService: AppConfigService,
5778
public jobSchedulerService: JobSchedulerService,
79+
public xmlTvService: XmlTvService,
5880
) {
5981
this._wss = new WebSocketServer({ noServer: true });
6082
}
6183

62-
public running = (channelId: string) => {
63-
const event: Event = {
64-
name: "status",
65-
event: {
66-
status: "running",
67-
data: {
68-
channelId,
69-
},
84+
public channelResult = (
85+
channelId: string,
86+
duration: number,
87+
result: "generated" | "up-to-date" | "error" | "not-configured",
88+
) => {
89+
const event: StatsEvent = this.latestStatsEvent ?? {
90+
lastRun: {
91+
time: new Date().getTime(),
92+
channelIds: this.xmlTvService.channels.map((c) => c.id),
93+
channelResults: {},
7094
},
7195
};
72-
this.emitEvent(event);
73-
this.lastStatsEvent = event;
96+
97+
this.emitStatsEvent({
98+
...event,
99+
lastRun: {
100+
...event.lastRun,
101+
channelResults: {
102+
...event.lastRun.channelResults,
103+
[channelId]: {
104+
duration,
105+
result,
106+
},
107+
},
108+
},
109+
});
110+
};
111+
112+
public xmlTvFetched = () => {
113+
if (this.latestStatsEvent) {
114+
this.emitStatsEvent({
115+
...this.latestStatsEvent,
116+
lastRun: {
117+
...this.latestStatsEvent.lastRun,
118+
channelIds: this.xmlTvService.channels.map((c) => c.id),
119+
},
120+
});
121+
}
122+
};
123+
124+
public start = () => {
125+
this.emitStatusEvent({
126+
status: "starting",
127+
data: {},
128+
});
129+
130+
this.emitStatsEvent({
131+
lastRun: {
132+
time: new Date().getTime(),
133+
channelIds: this.xmlTvService.channels.map((c) => c.id),
134+
channelResults: {},
135+
},
136+
});
137+
};
138+
139+
public running = (channelId: string, task: "generating" | "starting") => {
140+
this.emitStatusEvent({
141+
status: "running",
142+
data: {
143+
numChannels: this.xmlTvService.channels.length,
144+
channelIndex: this.xmlTvService.channels.findIndex(
145+
(c) => c.id === channelId,
146+
),
147+
channelId,
148+
task,
149+
},
150+
});
74151
};
75152

76153
public waiting = () => {
77154
const nextRun = this.jobSchedulerService.getNextRunTime("main")?.getTime();
78155
if (nextRun) {
79-
const event: Event = {
80-
name: "status",
81-
event: {
82-
status: "waiting",
83-
data: {
84-
nextRun,
85-
},
156+
this.emitStatusEvent({
157+
status: "waiting",
158+
data: {
159+
nextRun,
86160
},
87-
};
88-
this.emitEvent(event);
89-
this.lastStatsEvent = event;
161+
});
90162
}
91163
};
92164

165+
private getNextId = () => {
166+
this.idCount += 1;
167+
return this.idCount;
168+
};
169+
170+
private emitStatusEvent = (event: StatusEvent, saveAsLatest = true) => {
171+
this.emitEvent({
172+
name: "status",
173+
id: this.getNextId(),
174+
event,
175+
});
176+
if (saveAsLatest) this.latestStatusEvent = event;
177+
};
178+
179+
private emitStatsEvent = (event: StatsEvent, saveAsLatest = true) => {
180+
this.emitEvent({
181+
name: "stats",
182+
id: this.getNextId(),
183+
event,
184+
});
185+
if (saveAsLatest) this.latestStatsEvent = event;
186+
};
187+
93188
private emitEvent = (event: Event) => {
94189
this._wss.clients.forEach((client) => {
95190
if (client.readyState === WebSocket.OPEN) {
@@ -99,7 +194,22 @@ export class LiveStatsService implements BumpgenService {
99194
};
100195

101196
private onConnect = (client: WebSocket) => {
102-
client.send(JSON.stringify(this.lastStatsEvent));
197+
if (this.latestStatusEvent) {
198+
const event: Event = {
199+
name: "status",
200+
id: 0,
201+
event: this.latestStatusEvent,
202+
};
203+
client.send(JSON.stringify(event));
204+
}
205+
if (this.latestStatsEvent) {
206+
const event: Event = {
207+
name: "stats",
208+
id: 1,
209+
event: this.latestStatsEvent,
210+
};
211+
client.send(JSON.stringify(event));
212+
}
103213
};
104214

105215
load = () => {

apps/backend/src/video-generator/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface VideoOptions {
3333
height: number;
3434
length: number;
3535
template: FabricTemplate;
36+
onGenerateStart?: () => void;
3637
}
3738

3839
const randomInteger = (min: number, max: number) => {
@@ -109,6 +110,8 @@ export const makeVideo = async (
109110
return success("not-generated");
110111
}
111112

113+
if (options.onGenerateStart) options.onGenerateStart();
114+
112115
const {
113116
width,
114117
height,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useCallback } from "react";
2+
import { useNavigate } from "react-router";
3+
import { paths } from "../utils/constants";
4+
5+
export const useAppNavigation = () => {
6+
const navigate = useNavigate();
7+
8+
const navigateToHome = useCallback(() => navigate(paths.home), [navigate]);
9+
const navigateToSettings = useCallback(
10+
() => navigate(paths.settings),
11+
[navigate],
12+
);
13+
const navigateToBackgroundContent = useCallback(
14+
() => navigate(paths.backgroundContent.list),
15+
[navigate],
16+
);
17+
const navigateToAddBackgroundContent = useCallback(
18+
() => navigate(paths.backgroundContent.add),
19+
[navigate],
20+
);
21+
const navigateToEditBackgroundContent = useCallback(
22+
(item: string) => navigate(paths.backgroundContent.edit.replace("*", item)),
23+
[navigate],
24+
);
25+
26+
return {
27+
navigateToHome,
28+
navigateToSettings,
29+
navigateToBackgroundContent,
30+
navigateToAddBackgroundContent,
31+
navigateToEditBackgroundContent,
32+
};
33+
};

0 commit comments

Comments
 (0)