documentation
This commit is contained in:
111
src/homeassistant/routes.ts
Normal file
111
src/homeassistant/routes.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { HomeAssistantDeskPositionResult } from "@dpu/shared";
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import { z } from "zod";
|
||||
import type { HomeAssistantService } from "../homeassistant/service.js";
|
||||
|
||||
export async function homeAssistantRoutes(
|
||||
fastify: FastifyInstance,
|
||||
{
|
||||
haService,
|
||||
verifyAPIKey,
|
||||
}: {
|
||||
haService: HomeAssistantService;
|
||||
verifyAPIKey: (
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) => Promise<void>;
|
||||
},
|
||||
) {
|
||||
fastify.get(
|
||||
"/homeassistant/desk/position",
|
||||
{
|
||||
schema: {
|
||||
description: "Get current desk position",
|
||||
tags: ["homeassistant"],
|
||||
response: {
|
||||
200: z.object({
|
||||
position: z.string(),
|
||||
is_standing: z.boolean(),
|
||||
last_changed: z.string(),
|
||||
}),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
const service_result = await haService.getDeskPosition();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
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),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
"/homeassistant/desk/stand",
|
||||
{
|
||||
preHandler: verifyAPIKey,
|
||||
schema: {
|
||||
description: "Trigger standing desk automation",
|
||||
tags: ["homeassistant"],
|
||||
response: {
|
||||
200: z.unknown(),
|
||||
401: z.object({
|
||||
error: z.literal("Invalid API key"),
|
||||
}),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
const service_result = await haService.startStandingAutomation();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
fastify.get(
|
||||
"/homeassistant/temperature",
|
||||
{
|
||||
schema: {
|
||||
description: "Get current room temperature",
|
||||
tags: ["homeassistant"],
|
||||
response: {
|
||||
200: z.string(),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
const service_result = await haService.getTemperatureText();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
}
|
||||
178
src/index.ts
178
src/index.ts
@@ -1,17 +1,49 @@
|
||||
import type { HomeAssistantDeskPositionResult } from "@dpu/shared";
|
||||
import { logInfo } from "@dpu/shared/dist/logger.js";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import fastify from "fastify";
|
||||
import Fastify from "fastify";
|
||||
import fastifyAxios from "fastify-axios";
|
||||
import {
|
||||
serializerCompiler,
|
||||
validatorCompiler,
|
||||
type ZodTypeProvider,
|
||||
} from "fastify-type-provider-zod";
|
||||
import z from "zod";
|
||||
import { Config } from "./config.js";
|
||||
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";
|
||||
|
||||
const server = fastify();
|
||||
const fastify = Fastify().withTypeProvider<ZodTypeProvider>();
|
||||
|
||||
await server.register(fastifyAxios, {
|
||||
fastify.setValidatorCompiler(validatorCompiler);
|
||||
fastify.setSerializerCompiler(serializerCompiler);
|
||||
|
||||
await fastify.register(require("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: {
|
||||
title: "DPU API",
|
||||
description: "API Documentation",
|
||||
version: "1.0.0",
|
||||
},
|
||||
servers: [{ url: "http://localhost:8080", description: "Development" }],
|
||||
},
|
||||
transform: ({ schema, url }: { schema: unknown; url: string }) => {
|
||||
return { schema, url };
|
||||
},
|
||||
});
|
||||
|
||||
await fastify.register(require("@fastify/swagger-ui"), {
|
||||
routePrefix: "/docs",
|
||||
uiConfig: {
|
||||
docExpansion: "list",
|
||||
deepLinking: false,
|
||||
},
|
||||
});
|
||||
|
||||
await fastify.register(fastifyAxios, {
|
||||
clients: {
|
||||
homeassistant: {
|
||||
baseURL: Config.homeassistant.api_url,
|
||||
@@ -25,6 +57,12 @@ await server.register(fastifyAxios, {
|
||||
},
|
||||
});
|
||||
|
||||
const haClient = new HomeAssistantClient(fastify.axios.homeassistant);
|
||||
const haService = new HomeAssistantService(haClient);
|
||||
|
||||
const tidalClient = new TidalClient(fastify.axios.tidal);
|
||||
const tidalService = new TidalService(tidalClient);
|
||||
|
||||
async function verifyAPIKey(
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
@@ -37,119 +75,35 @@ async function verifyAPIKey(
|
||||
}
|
||||
}
|
||||
|
||||
const haClient = new HomeAssistantClient(server.axios.homeassistant);
|
||||
const haService = new HomeAssistantService(haClient);
|
||||
const port = parseInt(Config.port, 10);
|
||||
|
||||
const tidalClient = new TidalClient(server.axios.tidal);
|
||||
const tidalService = new TidalService(tidalClient);
|
||||
// Register routes
|
||||
await fastify.register(homeAssistantRoutes, { haService, verifyAPIKey });
|
||||
await fastify.register(tidalRoutes, { tidalService, verifyAPIKey });
|
||||
|
||||
// HOME ASSISTANT
|
||||
server.get("/homeassistant/desk/position", async (_request, reply) => {
|
||||
const service_result = await haService.getDeskPosition();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
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),
|
||||
};
|
||||
});
|
||||
|
||||
server.post(
|
||||
"/homeassistant/desk/stand",
|
||||
{ preHandler: verifyAPIKey },
|
||||
async (_request, reply) => {
|
||||
const service_result = await haService.startStandingAutomation();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
fastify.get(
|
||||
"/ping",
|
||||
{
|
||||
schema: {
|
||||
description: "Health check endpoint",
|
||||
tags: ["default"],
|
||||
response: {
|
||||
200: z.literal("pong"),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_request, _reply) => {
|
||||
return "pong" as const;
|
||||
},
|
||||
);
|
||||
|
||||
server.get("/homeassistant/temperature", async (_request, reply) => {
|
||||
const service_result = await haService.getTemperatureText();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
await fastify.ready();
|
||||
fastify.listen({ port: port, host: "0.0.0.0" }, (err, address) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
console.log(`Server listening at ${address}`);
|
||||
});
|
||||
|
||||
// TIDAL
|
||||
server.get("/tidal/song", async (_request, reply) => {
|
||||
const service_result = await tidalService.getSong();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
});
|
||||
|
||||
server.get("/tidal/songFormatted", async (_request, reply) => {
|
||||
const service_result = await tidalService.getSongFormatted();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
});
|
||||
|
||||
server.get("/tidal/volume", async (_request, reply) => {
|
||||
const service_result = await tidalService.getVolume();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
});
|
||||
|
||||
server.post(
|
||||
"/tidal/volume",
|
||||
{ preHandler: verifyAPIKey },
|
||||
async (request, reply) => {
|
||||
const volume = request.body as string;
|
||||
const service_result = await tidalService.setVolume(volume);
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
// Default
|
||||
server.get("/ping", async (_request, _reply) => {
|
||||
return "pong";
|
||||
});
|
||||
|
||||
server.listen(
|
||||
{ port: parseInt(Config.port, 10), host: "0.0.0.0" },
|
||||
(err, address) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Server listening at ${address}`);
|
||||
},
|
||||
);
|
||||
console.log(`API docs available at http://localhost:${port}/docs`);
|
||||
|
||||
139
src/tidal/routes.ts
Normal file
139
src/tidal/routes.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import { z } from "zod";
|
||||
import type { TidalService } from "../tidal/service.js";
|
||||
|
||||
export async function tidalRoutes(
|
||||
fastify: FastifyInstance,
|
||||
{
|
||||
tidalService,
|
||||
verifyAPIKey,
|
||||
}: {
|
||||
tidalService: TidalService;
|
||||
verifyAPIKey: (
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) => Promise<void>;
|
||||
},
|
||||
) {
|
||||
fastify.get(
|
||||
"/tidal/song",
|
||||
{
|
||||
schema: {
|
||||
description: "Get currently playing song",
|
||||
tags: ["tidal"],
|
||||
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 tidalService.getSong();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
fastify.get(
|
||||
"/tidal/songFormatted",
|
||||
{
|
||||
schema: {
|
||||
description: "Get currently playing song (formatted)",
|
||||
tags: ["tidal"],
|
||||
response: {
|
||||
200: z.string(),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
const service_result = await tidalService.getSongFormatted();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
fastify.get(
|
||||
"/tidal/volume",
|
||||
{
|
||||
schema: {
|
||||
description: "Get current volume level",
|
||||
tags: ["tidal"],
|
||||
response: {
|
||||
200: z.object({
|
||||
volume: z.number(),
|
||||
}),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
const service_result = await tidalService.getVolume();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
"/tidal/volume",
|
||||
{
|
||||
preHandler: verifyAPIKey,
|
||||
schema: {
|
||||
description:
|
||||
"Set volume level (accepts absolute number or relative +/- value)",
|
||||
tags: ["tidal"],
|
||||
body: z.string(),
|
||||
response: {
|
||||
200: z.object({
|
||||
volume: z.number(),
|
||||
}),
|
||||
401: z.object({
|
||||
error: z.literal("Invalid API key"),
|
||||
}),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const volume = request.body as string;
|
||||
const service_result = await tidalService.setVolume(volume);
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user