fix: handling data error from internal crestron

This commit is contained in:
2025-12-08 18:53:31 +07:00
parent cc59d59090
commit 59f2c277bd
5 changed files with 56 additions and 39 deletions
+23
View File
@@ -1,3 +1,26 @@
export const passwordHash = (password: string) => Bun.password.hashSync(password, { algorithm: 'bcrypt' }) export const passwordHash = (password: string) => Bun.password.hashSync(password, { algorithm: 'bcrypt' })
export const passwordVerify = (password: string, hash: string) => Bun.password.verifySync(password, hash) export const passwordVerify = (password: string, hash: string) => Bun.password.verifySync(password, hash)
export const dataMapping = [
{
deviceName: 'AC',
command: 'AC=',
},
{
deviceName: 'L',
command: 'Light=',
},
{
deviceName: 'DL',
command: 'Doorlock=',
},
{
deviceName: 'BL',
command: 'Blind=',
},
{
deviceName: 'BL',
command: 'Blind=',
},
]
+14 -3
View File
@@ -1,7 +1,7 @@
import { httpConfig } from '~/config' import { httpConfig } from '~/config'
import logger from '~/plugins/logger' import logger from '~/plugins/logger'
export async function sendCommandToThirdParty(topicData: string, payload: string): Promise<void> { export async function sendCommandToThirdParty(topicData: string, payload: string) {
try { try {
const response = await fetch(`${httpConfig.baseUrl}/command`, { const response = await fetch(`${httpConfig.baseUrl}/command`, {
method: 'POST', method: 'POST',
@@ -13,10 +13,21 @@ export async function sendCommandToThirdParty(topicData: string, payload: string
payload, payload,
}), }),
}) })
logger.info({ msg: 'Third-party command sent successfully', topic: topicData, status: response.status }) logger.info({ msg: 'Third-party command sent successfully', topic: topicData, status: response.status })
if (response.status != 200) {
return {
status: response.status,
message: response.statusText,
data: null,
}
}
return {
status: response.status,
message: response.statusText,
data: await response.json(),
}
} catch (error) { } catch (error) {
logger.error({ msg: 'Failed to send command to third-party service', topic: topicData, error: (error as Error).message }) logger.error({ msg: 'Failed to send command to third-party service', topic: topicData, error: (error as Error).message })
throw new Error('third_party_command_failed') throw error
} }
} }
+11 -21
View File
@@ -1,32 +1,22 @@
import { dataMapping } from '~/helpers/utils'
import { DeviceCommandBody } from '../schema' import { DeviceCommandBody } from '../schema'
import { sendCommandToThirdParty } from './command' import { sendCommandToThirdParty } from './command'
export default abstract class CommandService { export default abstract class CommandService {
static async sendCommand(body: DeviceCommandBody): Promise<{ static async sendCommand(body: DeviceCommandBody): Promise<{
status: 'success' status: string
message: 'Command accepted' message: string
receivedAt: string receivedAt: string
}> { }> {
try { const topicData = `${body.merchantName}/${body.floorName}_${body.unitNumber}-${body.deviceName}-${body.roomName}-${body.deviceType}-${body.commandType}-${body.towerNumber}`
const topicData = `${body.merchantName}/${body.floorName}_${body.unitNumber}-${body.deviceName}-${body.roomName}-${body.deviceType}-${body.commandType}-${body.towerNumber}` const cmd = dataMapping.find((device) => device.deviceName == body.deviceName)
const request = await sendCommandToThirdParty(topicData, `${cmd?.command}${body.payload.action}`)
if (topicData == 'SAVY/L2_01-L-LV-S-C-T1' && ['On', 'Off'].includes(body.payload.action)) { if (request.status !== 200) {
return { return {
status: 'success', status: 'error' as const,
message: 'Command accepted', message: request.status == 404 ? 'Device not found' : 'Command failed' as const,
receivedAt: new Date().toISOString(), receivedAt: new Date().toISOString(),
}
} }
if (topicData == 'SAVY/L2_01-BL-LV-S-C-T1' && ['Open', 'Closed'].includes(body.payload.action)) {
return {
status: 'success',
message: 'Command accepted',
receivedAt: new Date().toISOString(),
}
}
await sendCommandToThirdParty(topicData, body.payload.action)
} catch {
throw new Error('internal_error')
} }
return { return {
status: 'success' as const, status: 'success' as const,
+5 -12
View File
@@ -12,19 +12,12 @@ export const router = new Elysia({
}) })
.use(apiKeyAuthMacro) .use(apiKeyAuthMacro)
.post('/command', async ({ body, set }) => { .post('/command', async ({ body, set }) => {
try { const result = await CommandService.sendCommand(body)
const result = await CommandService.sendCommand(body) if (result.status === 'error' && result.message === 'Device not found') {
return result set.status = 404
} catch (error) { return { status: 'not_found', message: 'Device not found' }
if (error instanceof Error && error.message in deviceErrorResponseMap) {
const { status, response } = deviceErrorResponseMap[error.message]
set.status = status
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return response as any
}
set.status = 500
return { status: 'internal_error', message: 'Server error' }
} }
return result
}, { }, {
body: deviceCommandBody, body: deviceCommandBody,
response: deviceCommandResponseSchema, response: deviceCommandResponseSchema,
+3 -3
View File
@@ -32,8 +32,8 @@ export type DeviceQueryParams = z.infer<typeof deviceQueryParams>
// Response schemas // Response schemas
export const deviceCommandResponseSchema = { export const deviceCommandResponseSchema = {
200: z.object({ 200: z.object({
status: z.literal('success'), status: z.string(),
message: z.literal('Command accepted'), message: z.string(),
receivedAt: z.string().datetime(), receivedAt: z.string().datetime(),
}), }),
400: z.object({ 400: z.object({
@@ -56,7 +56,7 @@ export const deviceCommandResponseSchema = {
}), }),
404: z.object({ 404: z.object({
status: z.literal('not_found'), status: z.literal('not_found'),
message: z.literal('Device status not found'), message: z.literal('Device not found'),
}), }),
500: z.object({ 500: z.object({
status: z.literal('internal_error'), status: z.literal('internal_error'),