UNPKG

@letsparky/api-v2-client

Version:

TypeScript client for the LetsParky API V2

850 lines (849 loc) 33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Devices = void 0; const validators_1 = require("./validators"); const errors_1 = require("./errors"); /** * Device management client for the LetsParky API. * * This class provides comprehensive device management functionality including: * - Device CRUD operations (Create, Read, Update, Delete) * - Location-based device search * - User and tenant device assignments * - Device status management (activate, deactivate, block, unblock) * - Advanced filtering and pagination * * @example * ```typescript * const client = new ApiClient({ apiKey: 'your-api-key' }); * const devices = new Devices(client); * * // List all devices * const allDevices = await devices.list(); * * // Create a new device * const newDevice = await devices.create({ * name: 'Parking Sensor 1', * type: 'sensor', * location: { lat: 40.7128, lng: -74.0060 } * }); * * // Find nearby devices * const nearbyDevices = await devices.searchNearby({ * lat: 40.7128, * lng: -74.0060, * radius: 1000 * }); * ``` * * @since 1.0.0 */ class Devices { /** * Creates a new Devices instance. * * @param client - The ApiClient instance to use for API requests * * @since 1.0.0 */ constructor(client) { this.client = client; } /** * Retrieves a list of all devices. * * This method returns all devices that the authenticated user has access to. * For paginated results, use `listPaginated()` instead. * * @returns Promise resolving to an array of devices * * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When the API returns an error response * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * const response = await devices.list(); * console.log(`Found ${response.data.length} devices`); * ``` * * @since 1.0.0 */ async list() { const response = await this.client.get('/devices', undefined); return response.data.data; } /** * Retrieves a paginated list of devices with optional filtering. * * This method provides pagination support and basic filtering capabilities. * For advanced filtering, use `listWithFilters()` instead. * * @param paginationParams - Pagination parameters (page, limit, sort, order) * @param filters - Optional basic filter parameters * @param filters.status - Filter by device status ('active' or 'inactive') * @param filters.type - Filter by device type * @param filters.name - Filter by device name (partial match) * @returns Promise resolving to paginated device results * * @throws {ValidationError} When pagination parameters are invalid * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When the API returns an error response * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * * // Get first page with 10 devices * const response = await devices.listPaginated({ page: 1, limit: 10 }); * * // Get active sensors, sorted by name * const activeSensors = await devices.listPaginated( * { page: 1, limit: 20, sort: 'name', order: 'asc' }, * { status: 'active', type: 'sensor' } * ); * * console.log(`Page ${response.page} of ${response.pages}`); * console.log(`${response.data.length} devices on this page`); * ``` * * @since 1.0.0 */ async listPaginated(paginationParams, filters) { return this.client.getPaginated('/devices', paginationParams, filters); } /** * Retrieves devices with advanced filtering capabilities. * * This method provides comprehensive filtering options including status arrays, * tenant filtering, and public/private device filtering. * * @param filters - Advanced device filter parameters * @returns Promise resolving to filtered and paginated device results * * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When the API returns an error response * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * * // Get active and inactive sensors for a specific tenant * const response = await devices.listWithFilters({ * status: ['ACTIVE', 'INACTIVE'], * type: ['sensor', 'gateway'], * tenant_id: 'tenant-123', * is_public: true, * page: 1, * limit: 50 * }); * ``` * * @since 1.0.0 */ async listWithFilters(filters) { const params = new URLSearchParams(); if (filters) { Object.entries(filters).forEach(([key, value]) => { if (value !== undefined && value !== null) { params.append(key, String(value)); } }); } const queryString = params.toString(); const url = queryString ? `/devices?${queryString}` : '/devices'; return this.client.get(url); } /** * Validates and serializes coordinates for location-based queries. */ buildLocationSearchParams(params) { if (params.lat === undefined || params.lat === null) { throw new errors_1.ValidationError('Latitude is required', { lat: ['Latitude is required'] }); } if (params.lng === undefined || params.lng === null) { throw new errors_1.ValidationError('Longitude is required', { lng: ['Longitude is required'] }); } if (params.lat < -90 || params.lat > 90) { throw new errors_1.ValidationError('Invalid latitude', { lat: ['Latitude must be between -90 and 90'] }); } if (params.lng < -180 || params.lng > 180) { throw new errors_1.ValidationError('Invalid longitude', { lng: ['Longitude must be between -180 and 180'] }); } if (params.radius !== undefined && (params.radius <= 0 || params.radius > 50000)) { throw new errors_1.ValidationError('Invalid radius', { radius: ['Radius must be between 1 and 50000 meters'] }); } const searchParams = new URLSearchParams(); searchParams.append('lat', params.lat.toString()); searchParams.append('lng', params.lng.toString()); if (params.radius !== undefined) { searchParams.append('radius', params.radius.toString()); } if (params.page !== undefined) { searchParams.append('page', params.page.toString()); } if (params.limit !== undefined) { searchParams.append('limit', params.limit.toString()); } return searchParams; } /** * Searches for devices near a specific location. * * This method finds devices within a specified radius of the given coordinates * and returns them with distance information. Results are sorted by distance. * * @param params - Location search parameters * @returns Promise resolving to nearby devices with distance information * * @throws {ValidationError} When location parameters are invalid * @throws {ApiError} When the API returns an error response * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * * // Find devices within 500 meters of coordinates * const nearbyDevices = await devices.searchNearby({ * lat: 40.7831, * lng: -73.9712, * radius: 500, * page: 1, * limit: 20 * }); * * nearbyDevices.data.forEach(device => { * console.log(`${device.name}: ${device.distance}m away`); * }); * ``` * * @since 1.0.0 */ async searchNearby(params) { const searchParams = this.buildLocationSearchParams(params); return this.client.get(`/devices/nearby?${searchParams.toString()}`); } /** * Searches for devices around a location using the public search endpoint. * * Wraps `GET /devices/search/location` and returns the raw device list with * pagination metadata as described in the API specification. */ async searchByLocation(params) { const searchParams = this.buildLocationSearchParams(params); return this.client.get(`/devices/search/location?${searchParams.toString()}`); } /** * Retrieves a specific device by its ID. * * This method fetches detailed information about a single device. * The device ID must be a valid UUID format. * * @param deviceId - The unique identifier of the device to retrieve * @returns Promise resolving to the device details * * @throws {ValidationError} When the device ID is invalid or missing * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When the device is not found or access is denied * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * * try { * const device = await devices.get('device-uuid-here'); * console.log(`Device: ${device.data.name} (${device.data.type})`); * } catch (error) { * if (error instanceof ApiError && error.status === 404) { * console.log('Device not found'); * } * } * ``` * * @since 1.0.0 */ async get(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.get(`/devices/${deviceId}`); } /** * Retrieves a specific public device by its ID. * * This method fetches detailed information about a single public device with restricted fields. * The device ID must be a valid UUID format. * * @param deviceId - The unique identifier of the device to retrieve * @returns Promise resolving to the device details * * @throws {ValidationError} When the device ID is invalid or missing * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When the device is not found or access is denied * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * * try { * const device = await devices.get('device-uuid-here'); * console.log(`Device: ${device.data.name} (${device.data.type})`); * } catch (error) { * if (error instanceof ApiError && error.status === 404) { * console.log('Device not found'); * } * } * ``` * * @since 1.0.3 */ async getPublic(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.get(`/devices/public/${deviceId}`); } /** * Retrieves detailed information about a specific device. * * This method provides extended device information that may include * additional metadata, statistics, or configuration details not available * in the basic `get()` method. * * @param deviceId - The unique identifier of the device * @returns Promise resolving to detailed device information * * @throws {ValidationError} When the device ID is invalid or missing * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When the device is not found or access is denied * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * const deviceInfo = await devices.getInfo('device-uuid-here'); * console.log(`Device info: ${JSON.stringify(deviceInfo.data, null, 2)}`); * ``` * * @since 1.0.0 */ async getInfo(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.get(`/devices/${deviceId}/info`); } /** * Creates a new device. * * This method creates a new device with the specified parameters. * Name and type are required fields. Location and other parameters are optional. * * @param params - Device creation parameters * @param params.name - The device name (required) * @param params.type - The device type (required) * @param params.description - Optional device description * @param params.location - Optional device location coordinates * @param params.is_public - Whether the device is publicly accessible * @param params.tenant_id - Optional tenant ID to assign the device to * @returns Promise resolving to the created device * * @throws {ValidationError} When required parameters are missing or invalid * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When device creation fails due to business rules * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * * const newDevice = await devices.create({ * name: 'Parking Sensor #1', * type: 'ultrasonic_sensor', * description: 'Main entrance parking sensor', * location: { lat: 40.7128, lng: -74.0060 }, * is_public: true * }); * * console.log(`Created device: ${newDevice.data.id}`); * ``` * * @since 1.0.0 */ async create(params) { if (!params.nick_name) { throw new errors_1.ValidationError('Device name is required', { name: ['Device name is required'] }); } if (!params.type) { throw new errors_1.ValidationError('Device type is required', { type: ['Device type is required'] }); } return this.client.post('/devices', params); } /** * Update an existing device * @param deviceId The ID of the device to update * @param params The device update parameters * @returns Promise with the updated device */ async update(deviceId, params) { validators_1.Validators.validateId(deviceId, 'Device'); validators_1.Validators.validateDeviceUpdate(params); return this.client.put(`/devices/${deviceId}`, params); } /** * Delete a device * @param deviceId The ID of the device to delete * @returns Promise with the deletion result */ async delete(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.delete(`/devices/${deviceId}`); } /** * Get devices for the authenticated user * @returns Promise with user's devices */ async getUserDevices(scope = 'owned') { return this.client.get(`/devices/user?scope=${scope}`); } /** * Get devices for a specific user * @param user_id The ID of the user * @returns Promise with user's devices */ async getDevicesForUser(user_id) { validators_1.Validators.validateId(user_id, 'User'); return this.client.get(`/devices/user/${user_id}`); } /** * Get devices for a specific tenant * @param tenantId The ID of the tenant * @param filters Optional filter parameters * @returns Promise with tenant's devices */ async getDevicesForTenant(tenantId, filters) { validators_1.Validators.validateId(tenantId, 'Tenant'); const params = new URLSearchParams(); if (filters) { Object.entries(filters).forEach(([key, value]) => { if (value !== undefined && value !== null) { params.append(key, String(value)); } }); } const queryString = params.toString(); const url = queryString ? `/devices/tenant/${tenantId}?${queryString}` : `/devices/tenant/${tenantId}`; return this.client.get(url); } /** * Assign a device to a user * @param deviceId The ID of the device * @param params Assignment parameters * @returns Promise with the assignment result */ async assignToUser(deviceId, params) { validators_1.Validators.validateId(deviceId, 'Device'); validators_1.Validators.validateId(params.user_id, 'User'); return this.client.post(`/devices/${deviceId}/users`, params); } /** * Get users assigned to a device * @param deviceId The ID of the device * @returns Promise with assigned users */ async getAssignedUsers(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.get(`/devices/${deviceId}/users`); } /** * Revoke device access from a user * @param deviceId The ID of the device * @param user_id The ID of the user * @returns Promise with the revocation result */ async revokeUserAccess(deviceId, user_id) { validators_1.Validators.validateId(deviceId, 'Device'); validators_1.Validators.validateId(user_id, 'User'); return this.client.delete(`/devices/${deviceId}/users/${user_id}`); } /** * Assign a device to a tenant * @param deviceId The ID of the device * @param params Tenant assignment parameters * @returns Promise with the updated device */ async assignToTenant(deviceId, params) { validators_1.Validators.validateId(deviceId, 'Device'); validators_1.Validators.validateId(params.tenant_id, 'Tenant'); return this.client.put(`/devices/${deviceId}/tenant`, params); } /** * Link a device to a parking area. * * Wraps `PUT /devices/{deviceId}/parking-area` and validates the required * parking area identifier before making the request. */ async assignToParkingArea(deviceId, params) { validators_1.Validators.validateId(deviceId, 'Device'); if (!(params === null || params === void 0 ? void 0 : params.parking_area_id)) { throw new errors_1.ValidationError('Parking area ID is required', { parking_area_id: ['Parking area ID is required'] }); } validators_1.Validators.validateId(params.parking_area_id, 'Parking Area'); return this.client.put(`/devices/${deviceId}/parking-area`, params); } /** * Block a device * @param deviceId The ID of the device to block * @returns Promise with the blocked device */ async block(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.post(`/devices/${deviceId}/block`); } /** * Unblock a device * @param deviceId The ID of the device to unblock * @returns Promise with the unblocked device */ async unblock(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.post(`/devices/${deviceId}/unblock`); } /** * Reboot a device * @param deviceId The ID of the device to reboot * @returns Promise with the rebooted device */ async reboot(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.post(`/devices/${deviceId}/reboot`); } // ============================================================================ // QR DATA MANAGEMENT METHODS // ============================================================================ /** * Find a device by QR data or device code * @param params Search parameters with either qr_data or device_code * @returns Promise with the found device information */ async findByQr(params) { if (!params.identifier) { throw new errors_1.ValidationError('Either qr_data or device id or serial no is required', { identifier: ['Either qr_data or device id or serial no must be provided'] }); } const searchParams = new URLSearchParams(); if (params.identifier) searchParams.append('identifier', params.identifier); return this.client.get(`/devices/find-by-qr?${searchParams.toString()}`, undefined); } /** * Find a device by QR data or device code * @param params Search parameters with either qr_data or device_code * @returns Promise with the found device information */ async findByQrPublic(params) { if (!params.identifier) { throw new errors_1.ValidationError('Either qr_data or device id or serial no is required', { identifier: ['Either qr_data or device id or serial no must be provided'], }); } const searchParams = new URLSearchParams(); if (params.identifier) searchParams.append('identifier', params.identifier); return this.client.get(`/devices/public/find-by-qr?${searchParams.toString()}`); } /** * Create QR data for a specific device * @param deviceId The ID of the device * @param params QR data creation parameters * @returns Promise with the created QR data information */ async createQrData(deviceId, params) { validators_1.Validators.validateId(deviceId, 'Device'); if (!params.qr_data || params.qr_data.trim() === '') { throw new errors_1.ValidationError('QR data is required', { qr_data: ['QR data is required'] }); } return this.client.post(`/devices/${deviceId}/qr-data`, params); } /** * Update QR data for a specific device * @param deviceId The ID of the device * @param params QR data update parameters * @returns Promise with the updated QR data information */ async updateQrData(deviceId, params) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.put(`/devices/${deviceId}/qr-data`, params); } /** * Remove QR data from a specific device * @param deviceId The ID of the device * @returns Promise with the removal result */ async removeQrData(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.delete(`/devices/${deviceId}/qr-data`); } /** * Get QR data list with filtering and pagination * @param filters Optional filter parameters * @returns Promise with paginated QR data list */ async listQrData(filters) { const params = new URLSearchParams(); if (filters) { if (filters.is_assigned !== undefined) { params.append('is_assigned', String(filters.is_assigned)); } if (filters.device_id) { validators_1.Validators.validateId(filters.device_id, 'Device'); params.append('device_id', filters.device_id); } if (filters.search) { params.append('search', filters.search); } if (filters.page) { params.append('page', String(filters.page)); } if (filters.limit) { params.append('limit', String(filters.limit)); } if (filters.sort_by) { params.append('sort_by', filters.sort_by); } if (filters.sort_direction) { params.append('sort_direction', filters.sort_direction); } } const queryString = params.toString(); const url = queryString ? `/devices/qr-data?${queryString}` : '/devices/qr-data'; return this.client.get(url); } /** * Bulk create QR codes * @param params Bulk creation parameters * @returns Promise with the creation results */ async bulkCreateQrData(params) { if (!params.qr_codes || params.qr_codes.length === 0) { throw new errors_1.ValidationError('QR codes array is required and cannot be empty', { qr_codes: ['QR codes array is required and cannot be empty'] }); } // Validate each QR code params.qr_codes.forEach((qrCode, index) => { if (!qrCode.qr_data || qrCode.qr_data.trim() === '') { throw new errors_1.ValidationError(`QR data is required for item at index ${index}`, { [`qr_codes[${index}].qr_data`]: ['QR data is required'] }); } }); return this.client.post('/devices/qr-data', params); } /** * Get device-QR data summary statistics * @returns Promise with summary statistics */ async getQrDataSummary() { return this.client.get('/devices/qr-data/summary'); } /** * Bulk assign QR codes to devices * @param params Bulk assignment parameters * @returns Promise with the assignment results */ async bulkAssignQrData(params) { if (!params.assignments || params.assignments.length === 0) { throw new errors_1.ValidationError('Assignments array is required and cannot be empty', { assignments: ['Assignments array is required and cannot be empty'] }); } // Validate each assignment params.assignments.forEach((assignment, index) => { if (!assignment.device_id) { throw new errors_1.ValidationError(`Device ID is required for assignment at index ${index}`, { [`assignments[${index}].device_id`]: ['Device ID is required'] }); } validators_1.Validators.validateId(assignment.device_id, 'Device'); if (!assignment.qr_data && !assignment.device_code) { throw new errors_1.ValidationError(`Either qr_data or device_code is required for assignment at index ${index}`, { [`assignments[${index}].qr_data`]: ['Either qr_data or device_code must be provided'], [`assignments[${index}].device_code`]: ['Either qr_data or device_code must be provided'] }); } }); return this.client.post('/devices/qr-data/bulk-assign', params); } /** * Validate QR data uniqueness * @param params Validation parameters * @returns Promise with validation results */ async validateQrData(params) { if (!params.identifier) { throw new errors_1.ValidationError('Either qr_data or device id or serial no is required for validation', { identifier: ['Either qr_data or device id or serial no must be provided'], }); } const searchParams = new URLSearchParams(); if (params.identifier) searchParams.append('identifier', params.identifier); return this.client.get(`/devices/qr-data/validate?${searchParams.toString()}`); } /** * Delete QR data by ID * @param qrDataId The ID of the QR data to delete * @returns Promise with the deletion result */ async deleteQrData(qrDataId) { validators_1.Validators.validateId(qrDataId, 'QR Data'); return this.client.delete(`/devices/qr-data/${qrDataId}`); } /** * Factory reset a device * * This method performs a factory reset on the specified device. * Only device owners are allowed to factory reset their devices. * * @param deviceId - The unique identifier of the device to factory reset * @returns Promise resolving to the factory reset result * * @throws {ValidationError} When the device ID is invalid or missing * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When the device is not found or user lacks permission * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * * try { * await devices.factoryReset('device-uuid-here'); * console.log('Device factory reset completed'); * } catch (error) { * if (error instanceof ApiError && error.status === 403) { * console.log('Only device owners can factory reset'); * } * } * ``` * * @since 1.0.0 */ async factoryReset(deviceId) { validators_1.Validators.validateId(deviceId, 'Device'); return this.client.post(`/devices/${deviceId}/factory-reset`); } /** * Check user permissions for a device * * This method checks if the authenticated user has permission to perform * a specific operation on the specified device. * * @param deviceId - The unique identifier of the device * @param operation - The operation to check permission for (operate, block, unblock, exit) * @returns Promise resolving to permission check result * * @throws {ValidationError} When the device ID is invalid or missing * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When the device is not found * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * * const permission = await devices.checkPermission('device-uuid-here', 'operate'); * if (permission.data.allowed) { * console.log('User can operate the device'); * } else { * console.log('Permission denied:', permission.data.reason); * } * ``` * * @since 1.0.0 */ async checkPermission(deviceId, operation = 'operate') { validators_1.Validators.validateId(deviceId, 'Device'); const searchParams = new URLSearchParams(); searchParams.append('operation', operation); return this.client.get(`/devices/${deviceId}/check-permission?${searchParams.toString()}`); } /** * Add owner to an unowned device * * This method adds an owner to a device that currently has no owner. * Requires the device serial number and user ID. * * @param params - Parameters for adding owner to device * @param params.serialNo - Serial number of the device (required) * @param params.user_id - ID of the user to assign as owner (required) * @param params.nickName - Optional nickname for the device * @param params.geoLat - Optional latitude coordinate * @param params.geoLng - Optional longitude coordinate * @param params.location - Optional location description * @param params.qrData - Optional QR data for the device * @param params.photo - Optional photo URL for the device * @returns Promise resolving to the add owner result * * @throws {ValidationError} When required parameters are missing or invalid * @throws {AuthenticationError} When authentication is required or fails * @throws {ApiError} When device is not found, already has owner, or user lacks permission * @throws {NetworkError} When the request fails due to network issues * * @example * ```typescript * const devices = new Devices(client); * * try { * await devices.addOwner({ * serialNo: 'DEVICE_SERIAL_123', * user_id: 'user-uuid-here', * nickName: 'My Parking Sensor', * location: 'Parking Lot A' * }); * console.log('Owner added successfully'); * } catch (error) { * if (error instanceof ApiError && error.status === 400) { * console.log('Device already has an owner or invalid request'); * } * } * ``` * * @since 1.0.0 */ async addOwner(params) { if (!params.serialNo || params.serialNo.trim() === '') { throw new errors_1.ValidationError('Device serial number is required', { serialNo: ['Device serial number is required'] }); } if (!params.user_id || params.user_id.trim() === '') { throw new errors_1.ValidationError('User ID is required', { user_id: ['User ID is required'] }); } validators_1.Validators.validateId(params.user_id, 'User'); return this.client.post('/devices/add-owner', params); } } exports.Devices = Devices;