feat: TTLock — add Учетная запись + Пароль fields for OAuth
Per Sciener docs: OAuth requires TTHotel account credentials (Учетная запись + Пароль) separate from developer app client_id/client_secret. - Migration 055: add ttlock_username, ttlock_password columns - Backend: store and pass new fields to agent - UI: add input fields matching TTHotel PMS integration page - Agent: getAccessToken() uses ttlockUsername/ttlockPassword when provided Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -47,29 +47,32 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
|
||||
|
||||
const { rows } = await db.query(
|
||||
`SELECT is_enabled, client_id, card_sectors, api_server FROM hotel_ttlock_config WHERE hotel_id = $1`,
|
||||
`SELECT is_enabled, client_id, card_sectors, api_server, ttlock_username FROM hotel_ttlock_config WHERE hotel_id = $1`,
|
||||
[hotelId],
|
||||
)
|
||||
if (!rows[0]) {
|
||||
return { isEnabled: false, clientId: '', cardSectors: '1,2,3,4,5,6,7,8,9,10', apiServer: 'https://euapi.ttlock.com' }
|
||||
return { isEnabled: false, clientId: '', cardSectors: '1,2,3,4,5,6,7,8,9,10', apiServer: 'https://euapi.ttlock.com', ttlockUsername: '' }
|
||||
}
|
||||
const r = rows[0]
|
||||
return {
|
||||
isEnabled: r.is_enabled,
|
||||
clientId: r.client_id,
|
||||
cardSectors: r.card_sectors,
|
||||
apiServer: r.api_server ?? 'https://euapi.ttlock.com',
|
||||
isEnabled: r.is_enabled,
|
||||
clientId: r.client_id,
|
||||
cardSectors: r.card_sectors,
|
||||
apiServer: r.api_server ?? 'https://euapi.ttlock.com',
|
||||
ttlockUsername: r.ttlock_username ?? '',
|
||||
}
|
||||
})
|
||||
|
||||
// ── PATCH /api/hotels/:slug/ttlock/config ───────────────────────────────────
|
||||
fastify.patch<SlugParam & {
|
||||
Body: {
|
||||
is_enabled?: boolean
|
||||
client_id?: string
|
||||
client_secret?: string
|
||||
card_sectors?: string
|
||||
api_server?: string
|
||||
is_enabled?: boolean
|
||||
client_id?: string
|
||||
client_secret?: string
|
||||
card_sectors?: string
|
||||
api_server?: string
|
||||
ttlock_username?: string
|
||||
ttlock_password?: string
|
||||
}
|
||||
}>('/api/hotels/:slug/ttlock/config', { onRequest: [fastify.authenticate] }, async (req, reply) => {
|
||||
const { slug } = req.params
|
||||
@@ -78,25 +81,33 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
const hotelId = await getHotelId(slug)
|
||||
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
|
||||
|
||||
const { is_enabled: isEnabled, client_id: clientId, client_secret: clientSecret, card_sectors: cardSectors, api_server: apiServer } = req.body
|
||||
const {
|
||||
is_enabled: isEnabled, client_id: clientId, client_secret: clientSecret,
|
||||
card_sectors: cardSectors, api_server: apiServer,
|
||||
ttlock_username: ttlockUsername, ttlock_password: ttlockPassword,
|
||||
} = req.body
|
||||
|
||||
await db.query(
|
||||
`INSERT INTO hotel_ttlock_config (hotel_id, is_enabled, client_id, client_secret, card_sectors, api_server)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
`INSERT INTO hotel_ttlock_config (hotel_id, is_enabled, client_id, client_secret, card_sectors, api_server, ttlock_username, ttlock_password)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (hotel_id) DO UPDATE SET
|
||||
is_enabled = COALESCE($2, hotel_ttlock_config.is_enabled),
|
||||
client_id = COALESCE($3, hotel_ttlock_config.client_id),
|
||||
client_secret = COALESCE(NULLIF($4, ''), hotel_ttlock_config.client_secret),
|
||||
card_sectors = COALESCE($5, hotel_ttlock_config.card_sectors),
|
||||
api_server = COALESCE($6, hotel_ttlock_config.api_server),
|
||||
updated_at = NOW()`,
|
||||
is_enabled = COALESCE($2, hotel_ttlock_config.is_enabled),
|
||||
client_id = COALESCE($3, hotel_ttlock_config.client_id),
|
||||
client_secret = COALESCE(NULLIF($4, ''), hotel_ttlock_config.client_secret),
|
||||
card_sectors = COALESCE($5, hotel_ttlock_config.card_sectors),
|
||||
api_server = COALESCE($6, hotel_ttlock_config.api_server),
|
||||
ttlock_username = COALESCE(NULLIF($7, ''), hotel_ttlock_config.ttlock_username),
|
||||
ttlock_password = COALESCE(NULLIF($8, ''), hotel_ttlock_config.ttlock_password),
|
||||
updated_at = NOW()`,
|
||||
[
|
||||
hotelId,
|
||||
isEnabled ?? null,
|
||||
clientId ?? null,
|
||||
clientSecret ?? '',
|
||||
cardSectors ?? null,
|
||||
apiServer ?? null,
|
||||
isEnabled ?? null,
|
||||
clientId ?? null,
|
||||
clientSecret ?? '',
|
||||
cardSectors ?? null,
|
||||
apiServer ?? null,
|
||||
ttlockUsername ?? '',
|
||||
ttlockPassword ?? '',
|
||||
],
|
||||
)
|
||||
return { ok: true }
|
||||
@@ -173,7 +184,7 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
|
||||
|
||||
const { rows: cfgRows } = await db.query(
|
||||
'SELECT client_id, client_secret, api_server FROM hotel_ttlock_config WHERE hotel_id = $1',
|
||||
'SELECT client_id, client_secret, api_server, ttlock_username, ttlock_password FROM hotel_ttlock_config WHERE hotel_id = $1',
|
||||
[hotelId],
|
||||
)
|
||||
const cfg = cfgRows[0]
|
||||
@@ -184,10 +195,12 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
|
||||
try {
|
||||
const result = await sendCommandAndWait(workstationId, {
|
||||
type: 'ttlock:test_api',
|
||||
clientId: cfg.client_id,
|
||||
clientSecret: cfg.client_secret,
|
||||
apiServer: cfg.api_server ?? 'https://euapi.ttlock.com',
|
||||
type: 'ttlock:test_api',
|
||||
clientId: cfg.client_id,
|
||||
clientSecret: cfg.client_secret,
|
||||
ttlockUsername: cfg.ttlock_username ?? '',
|
||||
ttlockPassword: cfg.ttlock_password ?? '',
|
||||
apiServer: cfg.api_server ?? 'https://euapi.ttlock.com',
|
||||
}, 15_000) as { ok?: boolean; lockCount?: number; error?: string }
|
||||
|
||||
if (!result.ok) return reply.code(502).send({ error: result.error ?? 'Ошибка API' })
|
||||
@@ -340,7 +353,7 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
|
||||
// Конфигурация TTHotel (client_id, card_sectors)
|
||||
const { rows: cfgRows } = await db.query(
|
||||
'SELECT client_id, client_secret, card_sectors, is_enabled, api_server FROM hotel_ttlock_config WHERE hotel_id = $1',
|
||||
'SELECT client_id, client_secret, card_sectors, is_enabled, api_server, ttlock_username, ttlock_password FROM hotel_ttlock_config WHERE hotel_id = $1',
|
||||
[hotelId],
|
||||
)
|
||||
const cfg = cfgRows[0]
|
||||
@@ -388,15 +401,17 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
let result: unknown
|
||||
try {
|
||||
result = await sendCommandAndWait(workstationId, {
|
||||
type: 'ttlock:write_card',
|
||||
lockMac: lock.lock_mac,
|
||||
comPort: comPort,
|
||||
cardSectors: cfg.card_sectors,
|
||||
clientId: cfg.client_id,
|
||||
clientSecret: cfg.client_secret,
|
||||
apiServer: cfg.api_server ?? 'https://euapi.ttlock.com',
|
||||
startDate: checkIn,
|
||||
endDate: checkOut,
|
||||
type: 'ttlock:write_card',
|
||||
lockMac: lock.lock_mac,
|
||||
comPort: comPort,
|
||||
cardSectors: cfg.card_sectors,
|
||||
clientId: cfg.client_id,
|
||||
clientSecret: cfg.client_secret,
|
||||
ttlockUsername: cfg.ttlock_username ?? '',
|
||||
ttlockPassword: cfg.ttlock_password ?? '',
|
||||
apiServer: cfg.api_server ?? 'https://euapi.ttlock.com',
|
||||
startDate: checkIn,
|
||||
endDate: checkOut,
|
||||
}, 30_000)
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'Агент не ответил'
|
||||
|
||||
Reference in New Issue
Block a user