add all needed routes and stuff
This commit is contained in:
@@ -42,14 +42,7 @@ export async function homeAssistantRoutes(
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
const position_result =
|
||||
service_result.result as HomeAssistantDeskPositionResult;
|
||||
|
||||
return {
|
||||
position: position_result.as_text(),
|
||||
is_standing: position_result.as_boolean,
|
||||
last_changed: position_result.last_changed.toReadable(true),
|
||||
};
|
||||
return haService.convertPosResultToApiAnswer(service_result.result as HomeAssistantDeskPositionResult);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
API_HA_DeskPosition,
|
||||
BaseService,
|
||||
type HomeAssistantDeskPositionResult,
|
||||
type HomeAssistantEntity,
|
||||
@@ -74,6 +75,14 @@ export class HomeAssistantService extends BaseService<HomeAssistantClient> {
|
||||
}
|
||||
}
|
||||
|
||||
convertPosResultToApiAnswer(position: HomeAssistantDeskPositionResult): API_HA_DeskPosition {
|
||||
return {
|
||||
position: position.as_text(),
|
||||
is_standing: position.as_boolean,
|
||||
last_changed: position.last_changed.toReadable(true),
|
||||
}
|
||||
}
|
||||
|
||||
async getTemperatureText(): Promise<ServiceResult<string>> {
|
||||
try {
|
||||
const entities = await this.getTemperatures();
|
||||
|
||||
52
src/homepage/routes.ts
Normal file
52
src/homepage/routes.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import { z } from "zod";
|
||||
import type { TidalService } from "../tidal/service.js";
|
||||
import { HomeAssistantService } from "../homeassistant/service.js";
|
||||
import { HomepageService } from "./service.js";
|
||||
|
||||
export async function homepageRoutes(
|
||||
fastify: FastifyInstance,
|
||||
{
|
||||
hpService,
|
||||
verifyAPIKey,
|
||||
}: {
|
||||
hpService: HomepageService
|
||||
verifyAPIKey: (
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) => Promise<void>;
|
||||
},
|
||||
) {
|
||||
fastify.get(
|
||||
"/homepage/status",
|
||||
{
|
||||
schema: {
|
||||
description: "Get all current information for dpu status page",
|
||||
tags: ["homepage"],
|
||||
response: {
|
||||
200: z.object({
|
||||
title: z.string(),
|
||||
artists: z.string(),
|
||||
status: z.string(),
|
||||
current: z.string(),
|
||||
duration: z.string(),
|
||||
url: z.string(),
|
||||
}),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
const service_result = await hpService.getFullInformation();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
}
|
||||
47
src/homepage/service.ts
Normal file
47
src/homepage/service.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
BaseClient,
|
||||
BaseService,
|
||||
FullInformation,
|
||||
HomeAssistantDeskPositionResult,
|
||||
SseService,
|
||||
TidalGetCurrent,
|
||||
type ServiceResult,
|
||||
} from "@dpu/shared";
|
||||
import { logWarning } from "@dpu/shared/dist/logger.js";
|
||||
import { HomeAssistantService } from "../homeassistant/service";
|
||||
import { TidalService } from "../tidal/service";
|
||||
|
||||
export class HomepageService extends BaseService<null> {
|
||||
private haService: HomeAssistantService;
|
||||
private tidalService: TidalService;
|
||||
private sseService: SseService;
|
||||
|
||||
constructor(haService: HomeAssistantService, tidalService: TidalService, sseService: SseService) {
|
||||
super(null);
|
||||
this.haService = haService;
|
||||
this.tidalService = tidalService;
|
||||
this.sseService = sseService;
|
||||
}
|
||||
|
||||
async getFullInformation(): Promise<ServiceResult<FullInformation | string>> {
|
||||
try {
|
||||
const [desk, temp, song] = await Promise.all([
|
||||
this.haService.getDeskPosition(),
|
||||
this.haService.getTemperatureText(),
|
||||
this.tidalService.getSong()
|
||||
]);
|
||||
|
||||
const result = {
|
||||
ha_desk_position: desk.successful ? this.haService.convertPosResultToApiAnswer(desk.result as HomeAssistantDeskPositionResult) : null,
|
||||
ha_temp: temp.successful ? temp.result : null,
|
||||
tidal_current: song ? song.result as TidalGetCurrent : null,
|
||||
}
|
||||
|
||||
return this.getSuccessfulResult(result);
|
||||
} catch {
|
||||
const error_message = "error getting all information";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/index.ts
138
src/index.ts
@@ -5,10 +5,10 @@ import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import Fastify from "fastify";
|
||||
import fastifyAxios from "fastify-axios";
|
||||
import {
|
||||
jsonSchemaTransform,
|
||||
serializerCompiler,
|
||||
validatorCompiler,
|
||||
type ZodTypeProvider,
|
||||
jsonSchemaTransform,
|
||||
serializerCompiler,
|
||||
validatorCompiler,
|
||||
type ZodTypeProvider,
|
||||
} from "fastify-type-provider-zod";
|
||||
import { SwaggerTheme, SwaggerThemeNameEnum } from "swagger-themes";
|
||||
import { z } from "zod";
|
||||
@@ -17,8 +17,12 @@ import { HomeAssistantClient } from "./homeassistant/client.js";
|
||||
import { homeAssistantRoutes } from "./homeassistant/routes.js";
|
||||
import { HomeAssistantService } from "./homeassistant/service.js";
|
||||
import { TidalClient } from "./tidal/client.js";
|
||||
import { tidalRoutes } from "./tidal/routes.js";
|
||||
import { TidalService } from "./tidal/service.js";
|
||||
import { tidalRoutes } from "./tidal/routes.js";
|
||||
import { sseRoutes } from "./sse/routes.js";
|
||||
import { SseService } from "@dpu/shared";
|
||||
import { HomepageService } from "./homepage/service.js";
|
||||
import { homepageRoutes } from "./homepage/routes.js";
|
||||
|
||||
const fastify = Fastify().withTypeProvider<ZodTypeProvider>();
|
||||
|
||||
@@ -26,47 +30,47 @@ fastify.setValidatorCompiler(validatorCompiler);
|
||||
fastify.setSerializerCompiler(serializerCompiler);
|
||||
|
||||
await fastify.register(fastifySwagger, {
|
||||
openapi: {
|
||||
info: {
|
||||
title: "DPU API",
|
||||
description: "API Documentation",
|
||||
version: "1.0.0",
|
||||
},
|
||||
servers: [
|
||||
{ url: "http://localhost:8080", description: "dev" },
|
||||
{ url: "https://dpu.dariusbag.dev/api", description: "prod" },
|
||||
],
|
||||
},
|
||||
transform: jsonSchemaTransform,
|
||||
openapi: {
|
||||
info: {
|
||||
title: "DPU API",
|
||||
description: "API Documentation",
|
||||
version: "1.0.0",
|
||||
},
|
||||
servers: [
|
||||
{ url: "http://localhost:8080", description: "dev" },
|
||||
{ url: "https://dpu.dariusbag.dev/api", description: "prod" },
|
||||
],
|
||||
},
|
||||
transform: jsonSchemaTransform,
|
||||
});
|
||||
|
||||
const theme = new SwaggerTheme();
|
||||
const content = theme.getBuffer(SwaggerThemeNameEnum.ONE_DARK);
|
||||
|
||||
await fastify.register(fastifySwaggerUi, {
|
||||
routePrefix: "/docs",
|
||||
indexPrefix: `${Config.env_dev ? "" : "/api"}`,
|
||||
uiConfig: {
|
||||
docExpansion: "list",
|
||||
deepLinking: false,
|
||||
},
|
||||
theme: {
|
||||
css: [{ filename: "theme.css", content: content }],
|
||||
},
|
||||
routePrefix: "/docs",
|
||||
indexPrefix: `${Config.env_dev ? "" : "/api"}`,
|
||||
uiConfig: {
|
||||
docExpansion: "list",
|
||||
deepLinking: false,
|
||||
},
|
||||
theme: {
|
||||
css: [{ filename: "theme.css", content: content }],
|
||||
},
|
||||
});
|
||||
|
||||
await fastify.register(fastifyAxios, {
|
||||
clients: {
|
||||
homeassistant: {
|
||||
baseURL: Config.homeassistant.api_url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${Config.homeassistant.api_token}`,
|
||||
},
|
||||
},
|
||||
tidal: {
|
||||
baseURL: `${Config.tidal.host}:${Config.tidal.port}`,
|
||||
},
|
||||
},
|
||||
clients: {
|
||||
homeassistant: {
|
||||
baseURL: Config.homeassistant.api_url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${Config.homeassistant.api_token}`,
|
||||
},
|
||||
},
|
||||
tidal: {
|
||||
baseURL: `${Config.tidal.host}:${Config.tidal.port}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const haClient = new HomeAssistantClient(fastify.axios.homeassistant);
|
||||
@@ -75,16 +79,20 @@ const haService = new HomeAssistantService(haClient);
|
||||
const tidalClient = new TidalClient(fastify.axios.tidal);
|
||||
const tidalService = new TidalService(tidalClient);
|
||||
|
||||
async function verifyAPIKey(
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
): Promise<void> {
|
||||
const apiKey = request.headers["x-api-key"];
|
||||
const sseService = new SseService();
|
||||
|
||||
if (!apiKey || apiKey !== Config.api_key) {
|
||||
logInfo("POST Request with wrong API key received");
|
||||
return reply.code(401).send({ error: "Invalid API key" });
|
||||
}
|
||||
const hpService = new HomepageService(haService, tidalService, sseService);
|
||||
|
||||
async function verifyAPIKey(
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
): Promise<void> {
|
||||
const apiKey = request.headers["x-api-key"];
|
||||
|
||||
if (!apiKey || apiKey !== Config.api_key) {
|
||||
logInfo("POST Request with wrong API key received");
|
||||
return reply.code(401).send({ error: "Invalid API key" });
|
||||
}
|
||||
}
|
||||
|
||||
const port = parseInt(Config.port, 10);
|
||||
@@ -92,28 +100,30 @@ const port = parseInt(Config.port, 10);
|
||||
// Register routes
|
||||
await fastify.register(homeAssistantRoutes, { haService, verifyAPIKey });
|
||||
await fastify.register(tidalRoutes, { tidalService, verifyAPIKey });
|
||||
await fastify.register(sseRoutes, { sseService, verifyAPIKey });
|
||||
await fastify.register(homepageRoutes, { hpService, verifyAPIKey });
|
||||
|
||||
fastify.get(
|
||||
"/ping",
|
||||
{
|
||||
schema: {
|
||||
description: "Health check endpoint",
|
||||
tags: ["default"],
|
||||
response: {
|
||||
200: z.literal("pong"),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_request, _reply) => {
|
||||
return "pong" as const;
|
||||
},
|
||||
"/ping",
|
||||
{
|
||||
schema: {
|
||||
description: "Health check endpoint",
|
||||
tags: ["default"],
|
||||
response: {
|
||||
200: z.literal("pong"),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_request, _reply) => {
|
||||
return "pong" as const;
|
||||
},
|
||||
);
|
||||
|
||||
await fastify.ready();
|
||||
fastify.listen({ port: port, host: "0.0.0.0" }, (err, address) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Server listening at ${address}`);
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Server listening at ${address}`);
|
||||
});
|
||||
|
||||
45
src/sse/routes.ts
Normal file
45
src/sse/routes.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import { SseService } from "@dpu/shared";
|
||||
|
||||
export async function sseRoutes(
|
||||
fastify: FastifyInstance,
|
||||
{
|
||||
sseService,
|
||||
verifyAPIKey,
|
||||
}: {
|
||||
sseService: SseService;
|
||||
verifyAPIKey: (
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) => Promise<void>;
|
||||
},
|
||||
) {
|
||||
fastify.get(
|
||||
"/events",
|
||||
{
|
||||
schema: {
|
||||
description: "Register for SSE",
|
||||
tags: ["sse"],
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
reply.raw.setHeader("Content-Type", "text/event-stream");
|
||||
reply.raw.setHeader("Cache-Control", "no-cache");
|
||||
reply.raw.setHeader("Connection", "keep-alive");
|
||||
|
||||
const clientId = Date.now();
|
||||
|
||||
const sendEvent = (data: unknown) => {
|
||||
reply.raw.write(`data: ${JSON.stringify(data)}\n\n`);
|
||||
};
|
||||
|
||||
sseService.addClient({ id: clientId, send: sendEvent });
|
||||
|
||||
sendEvent({ type: "connected", message: "SSE connected" });
|
||||
|
||||
request.raw.on("close", () => {
|
||||
sseService.removeClient(clientId);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -11,15 +11,17 @@ export class TidalService extends BaseService<TidalClient> {
|
||||
const req = await this.getSong();
|
||||
if (req.successful) {
|
||||
const song = req.result as TidalGetCurrent;
|
||||
const status = song.status === "playing" ? "▶️" : "⏸️";
|
||||
return this.getSuccessfulResult(
|
||||
`listening to ${song.title} by ${song.artists}. ${status} ${song.current}/${song.duration}. link: ${song.url}`,
|
||||
);
|
||||
return this.getSuccessfulResult(this.formatSong(song));
|
||||
} else {
|
||||
return this.getErrorResult(req.result as string);
|
||||
}
|
||||
}
|
||||
|
||||
formatSong(song: TidalGetCurrent): string {
|
||||
const status = song.status === "playing" ? "▶️" : "⏸️";
|
||||
return `listening to ${song.title} by ${song.artists}. ${status} ${song.current}/${song.duration}. link: ${song.url}`;
|
||||
}
|
||||
|
||||
async getSong(): Promise<ServiceResult<TidalGetCurrent | string>> {
|
||||
try {
|
||||
const response = await this.getClient().get<TidalGetCurrent>("current");
|
||||
|
||||
Reference in New Issue
Block a user