Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export function assert(predicate: boolean | (() => boolean), message?: string): asserts predicate {
|
||||
switch (typeof predicate) {
|
||||
case 'boolean': {
|
||||
if (!predicate) {
|
||||
throw new Error(message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'function': {
|
||||
if (!predicate()) {
|
||||
throw new Error(message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const exhaustiveCheck: never = predicate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function notNull(object: any): boolean {
|
||||
return object !== undefined && object !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that obj2 contains all keys from obj1.
|
||||
* @param obj1
|
||||
* @param obj2
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export function deepDirectionalEqual(obj1: any, obj2: any, seenObjects = new WeakSet()): boolean {
|
||||
if (obj1 === obj2) return true;
|
||||
|
||||
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if obj1 has already been seen to avoid cycles
|
||||
if (seenObjects.has(obj1)) {
|
||||
return true; // If we've already seen obj1, we assume it's equivalent.
|
||||
}
|
||||
|
||||
// Add obj1 to the seen set
|
||||
seenObjects.add(obj1);
|
||||
|
||||
const keys1 = Object.keys(obj1);
|
||||
const keys2 = Object.keys(obj2);
|
||||
|
||||
for (const key of keys1) {
|
||||
if (!keys2.includes(key) || !deepDirectionalEqual(obj1[key], obj2[key], seenObjects)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export interface BootConfig {
|
||||
/**
|
||||
* Also declared in SystemConfig. If absent, SystemConfig value is used.
|
||||
*/
|
||||
heartbeatInterval?: number | null;
|
||||
/**
|
||||
* Also declared in SystemConfig. If absent, SystemConfig value is used.
|
||||
*/
|
||||
bootRetryInterval?: number | null;
|
||||
status: string;
|
||||
statusInfo?: object | null;
|
||||
/**
|
||||
* Also declared in SystemConfig. If absent, SystemConfig value is used.
|
||||
*/
|
||||
getBaseReportOnPending?: boolean | null;
|
||||
/**
|
||||
* Ids of variable attributes to be sent in SetVariablesRequest on pending boot
|
||||
*/
|
||||
pendingBootSetVariableIds?: number[] | null;
|
||||
/**
|
||||
* Also declared in SystemConfig. If absent, SystemConfig value is used.
|
||||
*/
|
||||
bootWithRejectedVariables?: boolean | null;
|
||||
/**
|
||||
* Specifically for OCPP 1.6 which plays similar role to pendingBootSetVariableIds
|
||||
*/
|
||||
changeConfigurationsOnPending?: boolean | null;
|
||||
/**
|
||||
* Specifically for OCPP 1.6 which plays similar role to getBaseReportOnPending
|
||||
*/
|
||||
getConfigurationsOnPending?: boolean | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache boot status is used to keep track of the overall boot process for Rejected or Pending.
|
||||
* When Accepting a boot, blacklist needs to be cleared if and only if there was a previously
|
||||
* Rejected or Pending boot. When starting to configure charger, i.e. sending GetBaseReport or
|
||||
* SetVariables, this should only be done if configuring is not still ongoing from a previous
|
||||
* BootNotificationRequest. Cache boot status mediates this behavior.
|
||||
*/
|
||||
export const BOOT_STATUS = 'boot_status';
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { IFileStorage } from '@interfaces/files/fileStorage.js';
|
||||
import type { SystemConfig } from './types.js';
|
||||
|
||||
export interface ConfigStore extends IFileStorage {
|
||||
fetchConfig(): Promise<SystemConfig | null>;
|
||||
saveConfig(config: SystemConfig): Promise<void>;
|
||||
}
|
||||
|
||||
export class ConfigStoreFactory {
|
||||
private static instance: ConfigStore | null = null;
|
||||
|
||||
static setConfigStore(configStorage: ConfigStore): ConfigStore {
|
||||
if (this.instance === null) {
|
||||
this.instance = configStorage;
|
||||
} else {
|
||||
console.warn('ConfigStore has already been initialized.');
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
static getInstance(): ConfigStore {
|
||||
if (this.instance === null) {
|
||||
throw new Error(
|
||||
'ConfigStore has not been initialized. Call ConfigStoreFactory.setConfigStore() first.',
|
||||
);
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BOOTSTRAP_CONFIG_ENV_VAR_PREFIX } from './defineConfig.js';
|
||||
|
||||
// Bootstrap schema contains what's needed to start the application
|
||||
export const bootstrapConfigSchema = z.object({
|
||||
configFileName: z.string().default('config.json'),
|
||||
configDir: z.string().optional(),
|
||||
|
||||
// Database configuration
|
||||
database: z.object({
|
||||
host: z.string().default('localhost'),
|
||||
port: z.number().int().positive().default(5432),
|
||||
database: z.string().default('citrine'),
|
||||
dialect: z.string().default('postgres'),
|
||||
username: z.string().default('citrine'),
|
||||
password: z.string().default('citrine'),
|
||||
pool: z
|
||||
.object({
|
||||
max: z.number().int().positive().optional(),
|
||||
min: z.number().int().nonnegative().optional(),
|
||||
acquire: z.number().int().positive().optional(),
|
||||
idle: z.number().int().positive().optional(),
|
||||
})
|
||||
.optional(),
|
||||
sync: z.boolean().default(false),
|
||||
alter: z.boolean().default(false),
|
||||
force: z.boolean().default(false),
|
||||
maxRetries: z.number().int().positive().default(3),
|
||||
retryDelay: z.number().int().positive().default(1000),
|
||||
ssl: z
|
||||
.object({
|
||||
require: z.boolean().optional(),
|
||||
rejectUnauthorized: z.boolean().optional(),
|
||||
ca: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
|
||||
// File access configuration
|
||||
fileAccess: z
|
||||
.object({
|
||||
type: z.enum(['local', 's3', 'gcp']),
|
||||
local: z
|
||||
.object({
|
||||
defaultFilePath: z.string().default('data'),
|
||||
})
|
||||
.optional(),
|
||||
s3: z
|
||||
.object({
|
||||
region: z.string().optional(),
|
||||
endpoint: z.string().optional(),
|
||||
defaultBucketName: z.string().default('citrineos-s3-bucket'),
|
||||
s3ForcePathStyle: z.boolean().default(true),
|
||||
accessKeyId: z.string().optional(),
|
||||
secretAccessKey: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
gcp: z
|
||||
.object({
|
||||
projectId: z.string(),
|
||||
credentials: z.object().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.refine(
|
||||
(obj) => {
|
||||
// Ensure the selected type has corresponding config
|
||||
switch (obj.type) {
|
||||
case 'local':
|
||||
return !!obj.local;
|
||||
case 's3':
|
||||
return !!obj.s3;
|
||||
case 'gcp':
|
||||
return !!obj.gcp;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
message: 'Configuration for the selected file access type must be provided',
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
export type BootstrapConfig = z.infer<typeof bootstrapConfigSchema>;
|
||||
|
||||
/**
|
||||
* Helper function to load environment variables based on prefix
|
||||
*/
|
||||
function getEnvVarValue(key: string): string | undefined {
|
||||
const envKey = `${BOOTSTRAP_CONFIG_ENV_VAR_PREFIX}${key}`.toUpperCase();
|
||||
return process.env[envKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a potentially JSON-formatted environment variable
|
||||
*/
|
||||
function parseEnvValue(value: string): any {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load bootstrap configuration from environment variables
|
||||
*/
|
||||
export function loadBootstrapConfig(): BootstrapConfig {
|
||||
const config: Record<string, any> = {
|
||||
configFileName: getEnvVarValue('config_filename') || 'config.json',
|
||||
configDir: getEnvVarValue('config_dir'),
|
||||
|
||||
// Database configuration
|
||||
database: {
|
||||
host: getEnvVarValue('database_host'),
|
||||
port: getEnvVarValue('database_port') && parseInt(getEnvVarValue('database_port')!, 10),
|
||||
database: getEnvVarValue('database_name'),
|
||||
dialect: getEnvVarValue('database_dialect'),
|
||||
username: getEnvVarValue('database_username'),
|
||||
password: getEnvVarValue('database_password'),
|
||||
sync: getEnvVarValue('database_sync') && parseEnvValue(getEnvVarValue('database_sync')!),
|
||||
alter: getEnvVarValue('database_alter') && parseEnvValue(getEnvVarValue('database_alter')!),
|
||||
force: getEnvVarValue('database_force') && parseEnvValue(getEnvVarValue('database_force')!),
|
||||
maxRetries:
|
||||
getEnvVarValue('database_max_retries') &&
|
||||
parseInt(getEnvVarValue('database_max_retries')!, 10),
|
||||
retryDelay:
|
||||
getEnvVarValue('database_retry_delay') &&
|
||||
parseInt(getEnvVarValue('database_retry_delay')!, 10),
|
||||
},
|
||||
|
||||
fileAccess: {
|
||||
type: getEnvVarValue('file_access_type') || 'local',
|
||||
},
|
||||
};
|
||||
const pool = {
|
||||
max: getEnvVarValue('database_pool_max') && parseInt(getEnvVarValue('database_pool_max')!, 10),
|
||||
min: getEnvVarValue('database_pool_min') && parseInt(getEnvVarValue('database_pool_min')!, 10),
|
||||
acquire:
|
||||
getEnvVarValue('database_pool_acquire') &&
|
||||
parseInt(getEnvVarValue('database_pool_acquire')!, 10),
|
||||
idle:
|
||||
getEnvVarValue('database_pool_idle') && parseInt(getEnvVarValue('database_pool_idle')!, 10),
|
||||
};
|
||||
if (Object.keys(pool).length > 0) {
|
||||
config.database.pool = pool;
|
||||
}
|
||||
const sslRequire = getEnvVarValue('database_ssl_require');
|
||||
if (sslRequire !== undefined) {
|
||||
config.database.ssl = {
|
||||
require: parseEnvValue(sslRequire),
|
||||
rejectUnauthorized:
|
||||
getEnvVarValue('database_ssl_reject_unauthorized') !== undefined
|
||||
? parseEnvValue(getEnvVarValue('database_ssl_reject_unauthorized')!)
|
||||
: undefined,
|
||||
ca: getEnvVarValue('database_ssl_ca'),
|
||||
};
|
||||
}
|
||||
|
||||
// File access configuration
|
||||
switch (config.fileAccess.type) {
|
||||
case 'local':
|
||||
config.fileAccess.local = {
|
||||
defaultFilePath: getEnvVarValue('file_access_local_default_file_path'),
|
||||
};
|
||||
break;
|
||||
case 's3':
|
||||
config.fileAccess.s3 = {
|
||||
region: getEnvVarValue('file_access_s3_region'),
|
||||
endpoint: getEnvVarValue('file_access_s3_endpoint'),
|
||||
defaultBucketName: getEnvVarValue('file_access_s3_default_bucket_name'),
|
||||
s3ForcePathStyle:
|
||||
getEnvVarValue('file_access_s3_force_path_style') &&
|
||||
parseEnvValue(getEnvVarValue('file_access_s3_force_path_style')!),
|
||||
accessKeyId: getEnvVarValue('file_access_s3_access_key_id'),
|
||||
secretAccessKey: getEnvVarValue('file_access_s3_secret_access_key'),
|
||||
};
|
||||
break;
|
||||
case 'gcp':
|
||||
config.fileAccess.gcp = {
|
||||
projectId: getEnvVarValue('file_access_gcp_project_id'),
|
||||
credentials: getEnvVarValue('file_access_gcp_credentials'),
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
return bootstrapConfigSchema.parse(config);
|
||||
} catch (error) {
|
||||
console.error('Bootstrap configuration validation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import type { SystemConfig, SystemConfigInput } from './types.js';
|
||||
import { systemConfigSchema } from './types.js';
|
||||
|
||||
const args = typeof process !== 'undefined' && process.argv ? process.argv.slice(2) : [];
|
||||
|
||||
let dynamicPrefix = 'citrineos_';
|
||||
for (const arg of args) {
|
||||
if (arg.startsWith('--env-prefix=')) {
|
||||
dynamicPrefix = arg.split('=')[1].toLowerCase();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export const CITRINE_ENV_VAR_PREFIX = dynamicPrefix;
|
||||
export const BOOTSTRAP_CONFIG_ENV_VAR_PREFIX = `bootstrap_${CITRINE_ENV_VAR_PREFIX}`;
|
||||
|
||||
/**
|
||||
* Finds a case-insensitive match for a key in an object.
|
||||
* @param obj The object to search.
|
||||
* @param targetKey The target key.
|
||||
* @returns The matching key or undefined.
|
||||
*/
|
||||
function findCaseInsensitiveMatch<T>(
|
||||
obj: Record<string, T>,
|
||||
targetKey: string,
|
||||
): string | undefined {
|
||||
const lowerTargetKey = targetKey.toLowerCase();
|
||||
return Object.keys(obj).find((key) => key.toLowerCase() === lowerTargetKey);
|
||||
}
|
||||
|
||||
const getZodSchemaKeyMap = (schema: z.ZodTypeAny): Record<string, any> => {
|
||||
if (schema instanceof z.ZodNullable || schema instanceof z.ZodOptional) {
|
||||
return getZodSchemaKeyMap((schema as z.ZodNullable<any> | z.ZodOptional<any>).unwrap());
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodArray) {
|
||||
return getZodSchemaKeyMap(schema.element as z.ZodTypeAny);
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodObject) {
|
||||
const entries = Object.entries<z.ZodType>(schema.shape);
|
||||
|
||||
return entries.reduce(
|
||||
(acc, [key, value]) => {
|
||||
const nested = getZodSchemaKeyMap(value);
|
||||
|
||||
if (Object.keys(nested).length > 0) {
|
||||
acc[key] = nested;
|
||||
} else {
|
||||
acc[key.toLowerCase()] = key;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
);
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges configuration from environment variables into the default configuration. Allows any to keep it as generic as possible.
|
||||
* @param defaultConfig The default configuration.
|
||||
* @param envVars The environment variables.
|
||||
* @returns The merged configuration.
|
||||
*/
|
||||
function mergeConfigFromEnvVars<T extends Record<string, any>>(
|
||||
defaultConfig: T,
|
||||
envVars: NodeJS.ProcessEnv,
|
||||
configKeyMap: Record<string, any>,
|
||||
): T {
|
||||
const config: T = { ...defaultConfig };
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const [fullEnvKey, value] of Object.entries(envVars)) {
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
const lowercaseEnvKey = fullEnvKey.toLowerCase();
|
||||
if (lowercaseEnvKey.startsWith(CITRINE_ENV_VAR_PREFIX)) {
|
||||
const envKeyWithoutPrefix = lowercaseEnvKey.substring(CITRINE_ENV_VAR_PREFIX.length);
|
||||
const path = envKeyWithoutPrefix.split('_');
|
||||
|
||||
let currentConfigPart: Record<string, any> = config;
|
||||
let currentConfigKeyMap: Record<string, any> = configKeyMap;
|
||||
let validMapping = true;
|
||||
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const part = path[i];
|
||||
const matchingKey = findCaseInsensitiveMatch(currentConfigKeyMap, part);
|
||||
if (!matchingKey) {
|
||||
errors.push(
|
||||
`Environment variable '${fullEnvKey}' refers to unknown configuration segment '${part}'.`,
|
||||
);
|
||||
validMapping = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentConfigPart[matchingKey] === undefined) {
|
||||
currentConfigPart[matchingKey] = {};
|
||||
} else if (
|
||||
typeof currentConfigPart[matchingKey] !== 'object' ||
|
||||
currentConfigPart[matchingKey] === null
|
||||
) {
|
||||
errors.push(
|
||||
`Environment variable '${fullEnvKey}' refers to configuration segment '${part}', but its current value is not an object.`,
|
||||
);
|
||||
validMapping = false;
|
||||
break;
|
||||
}
|
||||
|
||||
currentConfigPart = currentConfigPart[matchingKey];
|
||||
currentConfigKeyMap = currentConfigKeyMap[matchingKey];
|
||||
}
|
||||
|
||||
if (!validMapping) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const finalPart = path[path.length - 1];
|
||||
const keyToUse = currentConfigKeyMap[finalPart.toLowerCase()] || finalPart;
|
||||
|
||||
try {
|
||||
currentConfigPart[keyToUse] = JSON.parse(value as string);
|
||||
} catch {
|
||||
console.debug(`Mapping '${value}' as string for environment variable '${fullEnvKey}'.`);
|
||||
currentConfigPart[keyToUse] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errors.forEach((err) => console.error(err));
|
||||
|
||||
return config as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the system configuration to ensure required properties are set.
|
||||
* @param finalConfig The final system configuration.
|
||||
* @throws Error if required properties are not set.
|
||||
*/
|
||||
function validateFinalConfig(finalConfig: SystemConfigInput): SystemConfig {
|
||||
return systemConfigSchema.parse(finalConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the application configuration by merging input configuration which is defined in a file with environment variables.
|
||||
* Takes environment variables over predefined
|
||||
* @param inputConfig The file defined input configuration.
|
||||
* @returns The final system configuration.
|
||||
* @throws Error if required environment variables are not set or if there are parsing errors.
|
||||
*/
|
||||
export function defineConfig(inputConfig: SystemConfigInput): SystemConfig {
|
||||
const configKeyMap: Record<string, any> = getZodSchemaKeyMap(systemConfigSchema);
|
||||
const appConfig = mergeConfigFromEnvVars<SystemConfigInput>(
|
||||
inputConfig,
|
||||
process.env,
|
||||
configKeyMap,
|
||||
);
|
||||
|
||||
return validateFinalConfig(appConfig);
|
||||
}
|
||||
|
||||
export const DEFAULT_TENANT_ID = 1;
|
||||
670
tools/citrineos-core-main/packages/base/src/config/types.ts
Normal file
670
tools/citrineos-core-main/packages/base/src/config/types.ts
Normal file
@@ -0,0 +1,670 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { RegistrationStatusEnum } from '@interfaces/dto/types/enums.js';
|
||||
import { EventGroup } from '@interfaces/messages/internal-types.js';
|
||||
import { OCPP1_6 } from '@ocpp/model/index.js';
|
||||
import { OCPP_CallAction, OCPPVersion, type OCPPVersionType } from '@ocpp/rpc/message.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
const CallActionSchema = z.nativeEnum(OCPP_CallAction);
|
||||
|
||||
export const oidcClientConfigSchema = z
|
||||
.object({
|
||||
tokenUrl: z.string(),
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string(),
|
||||
audience: z.string(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
export const OCPP_VERSION_LIST: OCPPVersionType[] = [
|
||||
OCPPVersion.OCPP2_1,
|
||||
OCPPVersion.OCPP2_0_1,
|
||||
OCPPVersion.OCPP1_6,
|
||||
] as const;
|
||||
|
||||
const signedMeterValuesSigningMethods = ['RSASSA-PKCS1-v1_5', 'ECDSA', 'SECP192R1'] as const;
|
||||
|
||||
// TODO: Refactor other objects out of system config, such as certificatesModuleInputSchema etc.
|
||||
export const websocketServerInputSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8080).optional(),
|
||||
pingInterval: z.number().int().min(1).default(60).optional(),
|
||||
protocols: z.array(z.enum(OCPP_VERSION_LIST)).default(['ocpp2.0.1']).optional(),
|
||||
securityProfile: z.number().int().min(0).max(3).default(0).optional(),
|
||||
allowUnknownChargingStations: z.boolean().default(false).optional(),
|
||||
ignoreAuthenticationHeaders: z.boolean().default(false).optional(), // When true, authorization headers will be ignored and authentication will be bypassed.
|
||||
tlsKeyFilePath: z.string().optional(), // Leaf certificate's private key pem which decrypts the message from client
|
||||
tlsCertificateChainFilePath: z.string().optional(), // Certificate chain pem consist of a leaf followed by sub CAs
|
||||
mtlsCertificateAuthorityKeyFilePath: z.string().optional(), // Sub CA's private key which signs the leaf (e.g.,
|
||||
// charging station certificate and csms certificate)
|
||||
rootCACertificateFilePath: z.string().optional(), // Root CA certificate that overrides default CA certificates
|
||||
// allowed by Mozilla
|
||||
tenantId: z.number().optional(),
|
||||
// Mapping from path segments to tenant IDs.
|
||||
// Example: { "my-tenant": 1 }
|
||||
tenantPathMapping: z.record(z.string(), z.number()).optional(),
|
||||
// When true, tenant can be resolved at connection upgrade time from the request
|
||||
// (query param, path segment, or header). Defaults to false for strict per-server tenant.
|
||||
dynamicTenantResolution: z.boolean().optional().default(false),
|
||||
// Forces a set protocol to communicate on, mostly used for dev purposes
|
||||
forceProtocol: z.enum(OCPP_VERSION_LIST).optional(),
|
||||
});
|
||||
|
||||
export const HUBJECT_DEFAULT_BASEURL = 'https://open.plugncharge-test.hubject.com';
|
||||
export const HUBJECT_DEFAULT_TOKENURL =
|
||||
'https://hubject.stoplight.io/api/v1/projects/cHJqOjk0NTg5/nodes/6bb8b3bc79c2e-authorization-token';
|
||||
export const HUBJECT_DEFAULT_CLIENTID = 'YOUR_CLIENT_ID';
|
||||
export const HUBJECT_DEFAULT_CLIENTSECRET = 'YOUR_CLIENT_SECRET';
|
||||
|
||||
export const systemConfigInputSchema = z.object({
|
||||
env: z.enum(['development', 'production']),
|
||||
centralSystem: z.object({
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8081).optional(),
|
||||
}),
|
||||
modules: z.object({
|
||||
certificates: z
|
||||
.object({
|
||||
endpointPrefix: z.string().default(EventGroup.Certificates).optional(),
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8081).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
})
|
||||
.optional(),
|
||||
configuration: z.object({
|
||||
heartbeatInterval: z.number().int().min(1).default(60).optional(),
|
||||
bootRetryInterval: z.number().int().min(1).default(10).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
ocpp2_0_1: z
|
||||
.object({
|
||||
unknownChargerStatus: z
|
||||
.enum([
|
||||
RegistrationStatusEnum.Accepted,
|
||||
RegistrationStatusEnum.Pending,
|
||||
RegistrationStatusEnum.Rejected,
|
||||
])
|
||||
.default(RegistrationStatusEnum.Accepted)
|
||||
.optional(), // Unknown chargers have no entry in BootConfig table
|
||||
getBaseReportOnPending: z.boolean().default(true).optional(),
|
||||
bootWithRejectedVariables: z.boolean().default(true).optional(),
|
||||
autoAccept: z.boolean().default(true).optional(), // If false, only data endpoint can update boot status to accepted
|
||||
})
|
||||
.optional(),
|
||||
ocpp2_1: z
|
||||
.object({
|
||||
unknownChargerStatus: z
|
||||
.enum([
|
||||
RegistrationStatusEnum.Accepted,
|
||||
RegistrationStatusEnum.Pending,
|
||||
RegistrationStatusEnum.Rejected,
|
||||
])
|
||||
.default(RegistrationStatusEnum.Accepted)
|
||||
.optional(), // Unknown chargers have no entry in BootConfig table
|
||||
getBaseReportOnPending: z.boolean().default(true).optional(),
|
||||
bootWithRejectedVariables: z.boolean().default(true).optional(),
|
||||
autoAccept: z.boolean().default(true).optional(), // If false, only data endpoint can update boot status to accepted
|
||||
})
|
||||
.optional(),
|
||||
ocpp1_6: z
|
||||
.object({
|
||||
unknownChargerStatus: z
|
||||
.enum([
|
||||
OCPP1_6.BootNotificationResponseStatus.Accepted,
|
||||
OCPP1_6.BootNotificationResponseStatus.Pending,
|
||||
OCPP1_6.BootNotificationResponseStatus.Rejected,
|
||||
])
|
||||
.default(OCPP1_6.BootNotificationResponseStatus.Accepted)
|
||||
.optional(), // Unknown chargers have no entry in BootConfig table
|
||||
})
|
||||
.optional(),
|
||||
endpointPrefix: z.string().default(EventGroup.Configuration).optional(),
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8081).optional(),
|
||||
}),
|
||||
evdriver: z.object({
|
||||
endpointPrefix: z.string().default(EventGroup.EVDriver).optional(),
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8081).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
enableGetChargingProfilesOnStartTransaction: z.boolean().default(true).optional(),
|
||||
}),
|
||||
monitoring: z.object({
|
||||
endpointPrefix: z.string().default(EventGroup.Monitoring).optional(),
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8081).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
}),
|
||||
reporting: z.object({
|
||||
endpointPrefix: z.string().default(EventGroup.Reporting).optional(),
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8081).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
}),
|
||||
smartcharging: z
|
||||
.object({
|
||||
endpointPrefix: z.string().default(EventGroup.SmartCharging).optional(),
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8081).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
})
|
||||
.optional(),
|
||||
tenant: z
|
||||
.object({
|
||||
endpointPrefix: z.string().default(EventGroup.Tenant).optional(),
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8081).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
ocppRouterBaseUrl: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
transactions: z.object({
|
||||
endpointPrefix: z.string().default(EventGroup.Transactions).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8081).optional(),
|
||||
costUpdatedInterval: z.number().int().min(1).default(60).optional(),
|
||||
sendCostUpdatedOnMeterValue: z.boolean().default(false).optional(),
|
||||
signedMeterValuesConfiguration: z
|
||||
.object({
|
||||
publicKeyFileId: z.string(),
|
||||
signingMethod: z.enum(signedMeterValuesSigningMethods),
|
||||
rejectUnsupportedSignedMeterValues: z.boolean().default(false).optional(),
|
||||
})
|
||||
.optional(),
|
||||
/** Base URL for generating receipt URLs when ReceiptByCSMS is true (C21). */
|
||||
receiptBaseUrl: z.string().optional(),
|
||||
}),
|
||||
}),
|
||||
util: z.object({
|
||||
cache: z
|
||||
.object({
|
||||
memory: z.boolean().optional(),
|
||||
redis: z
|
||||
.union([
|
||||
z.object({
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(6379).optional(),
|
||||
}),
|
||||
z.object({
|
||||
url: z.url().refine((v) => v.startsWith('redis://') || v.startsWith('rediss://'), {
|
||||
message: 'Redis URL must start with redis:// or rediss://',
|
||||
}),
|
||||
}),
|
||||
])
|
||||
.optional(),
|
||||
})
|
||||
.refine((obj) => obj.memory || obj.redis, {
|
||||
message: 'A cache implementation must be set',
|
||||
}),
|
||||
messageBroker: z
|
||||
.object({
|
||||
amqp: z
|
||||
.object({
|
||||
url: z.string(),
|
||||
exchange: z.string(),
|
||||
instanceIdentifier: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.refine((obj) => obj.amqp, {
|
||||
message: 'A message broker implementation must be set',
|
||||
}),
|
||||
authProvider: z
|
||||
.object({
|
||||
oidc: z
|
||||
.object({
|
||||
jwksUri: z.string(),
|
||||
issuer: z.string(),
|
||||
audience: z.string(),
|
||||
cacheTime: z.number().int().min(1).optional(),
|
||||
rateLimit: z.boolean().default(false).optional(),
|
||||
})
|
||||
.optional(),
|
||||
localByPass: z.boolean().default(false).optional(),
|
||||
})
|
||||
.refine((obj) => obj.oidc || obj.localByPass, {
|
||||
message: 'An auth provider implementation must be set',
|
||||
}),
|
||||
swagger: z
|
||||
.object({
|
||||
path: z.string().default('/docs').optional(),
|
||||
logoPath: z.string(),
|
||||
exposeData: z.boolean().default(true).optional(),
|
||||
exposeMessage: z.boolean().default(true).optional(),
|
||||
})
|
||||
.optional(),
|
||||
networkConnection: z.object({
|
||||
websocketServers: z.array(websocketServerInputSchema.optional()),
|
||||
}),
|
||||
certificateAuthority: z.object({
|
||||
v2gCA: z
|
||||
.object({
|
||||
name: z.enum(['hubject']).default('hubject'),
|
||||
hubject: z
|
||||
.object({
|
||||
baseUrl: z.string().default(HUBJECT_DEFAULT_BASEURL),
|
||||
tokenUrl: z.string().default(HUBJECT_DEFAULT_TOKENURL),
|
||||
clientId: z.string().default(HUBJECT_DEFAULT_CLIENTID),
|
||||
clientSecret: z.string().default(HUBJECT_DEFAULT_CLIENTSECRET),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.refine(
|
||||
(obj) => {
|
||||
if (obj.name === 'hubject') {
|
||||
return (
|
||||
obj.hubject &&
|
||||
obj.hubject.baseUrl &&
|
||||
obj.hubject.tokenUrl &&
|
||||
obj.hubject.clientId &&
|
||||
obj.hubject.clientSecret
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
message: 'Hubject requires baseUrl, tokenUrl, clientId, and clientSecret',
|
||||
},
|
||||
),
|
||||
chargingStationCA: z
|
||||
.object({
|
||||
name: z.enum(['acme']).default('acme'),
|
||||
acme: z
|
||||
.object({
|
||||
env: z.enum(['staging', 'production']).default('staging'),
|
||||
accountKeyFilePath: z.string(),
|
||||
email: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.refine((obj) => {
|
||||
if (obj.name === 'acme') {
|
||||
return obj.acme;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
logLevel: z.number().min(0).max(6).default(0).optional(),
|
||||
maxCallLengthSeconds: z.number().int().min(1).default(20).optional(),
|
||||
maxCachingSeconds: z.number().int().min(1).default(30).optional(),
|
||||
maxReconnectDelay: z.number().int().min(1).default(30).optional(),
|
||||
shutdownGracePeriodSeconds: z.number().int().min(1).default(30).optional(),
|
||||
ocpiServer: z.object({
|
||||
host: z.string().default('localhost').optional(),
|
||||
port: z.number().int().min(1).default(8085).optional(),
|
||||
}),
|
||||
userPreferences: z.object({
|
||||
telemetryConsent: z.boolean().default(false).optional(),
|
||||
}),
|
||||
rbacRulesFileName: z.string().default('rbac-rules.json').optional(),
|
||||
rbacRulesDir: z.string().optional(),
|
||||
realTimeAuthDefaultTimeoutSeconds: z.number().int().min(1).default(15).optional(),
|
||||
notReadyThresholdSeconds: z.number().int().min(1).default(60).optional(),
|
||||
});
|
||||
|
||||
export type SystemConfigInput = z.infer<typeof systemConfigInputSchema>;
|
||||
|
||||
export const websocketServerSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
host: z.string(),
|
||||
port: z.number().int().min(1),
|
||||
pingInterval: z.number().int().min(1),
|
||||
protocols: z.array(z.enum(OCPP_VERSION_LIST)),
|
||||
securityProfile: z.number().int().min(0).max(3),
|
||||
allowUnknownChargingStations: z.boolean(),
|
||||
ignoreAuthenticationHeaders: z.boolean().default(false).optional(),
|
||||
tlsKeyFilePath: z.string().optional(),
|
||||
tlsCertificateChainFilePath: z.string().optional(),
|
||||
mtlsCertificateAuthorityKeyFilePath: z.string().optional(),
|
||||
rootCACertificateFilePath: z.string().optional(),
|
||||
tenantId: z.number().optional(),
|
||||
tenantPathMapping: z.record(z.string(), z.number()).optional(),
|
||||
// When true, tenant can be resolved at connection upgrade time from the request
|
||||
// (query param, path segment, or header). Defaults to false for strict per-server tenant.
|
||||
dynamicTenantResolution: z.boolean().optional().default(false),
|
||||
forceProtocol: z.enum(OCPP_VERSION_LIST).optional(),
|
||||
})
|
||||
.refine((obj) => {
|
||||
switch (obj.securityProfile) {
|
||||
case 0: // No security
|
||||
case 1: // Basic Auth
|
||||
return true;
|
||||
case 2: // Basic Auth + TLS
|
||||
return obj.tlsKeyFilePath && obj.tlsCertificateChainFilePath;
|
||||
case 3: // mTLS
|
||||
return (
|
||||
obj.tlsCertificateChainFilePath &&
|
||||
obj.tlsKeyFilePath &&
|
||||
obj.mtlsCertificateAuthorityKeyFilePath
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.refine((obj) => {
|
||||
if ((obj.tenantId !== undefined) === obj.dynamicTenantResolution) {
|
||||
return false; // Cannot have both or neither tenantId and dynamicTenantResolution
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}, 'Invalid websocket server configuration: tenantId and dynamicTenantResolution are mutually exclusive and one must be set');
|
||||
|
||||
export const systemConfigSchema = z
|
||||
.object({
|
||||
env: z.enum(['development', 'production']),
|
||||
centralSystem: z.object({
|
||||
host: z.string(),
|
||||
port: z.number().int().min(1),
|
||||
}),
|
||||
modules: z.object({
|
||||
certificates: z
|
||||
.object({
|
||||
endpointPrefix: z.string(),
|
||||
host: z.string().optional(),
|
||||
port: z.number().int().min(1).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
})
|
||||
.optional(),
|
||||
evdriver: z.object({
|
||||
endpointPrefix: z.string(),
|
||||
host: z.string().optional(),
|
||||
port: z.number().int().min(1).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
enableGetChargingProfilesOnStartTransaction: z.boolean().optional(),
|
||||
}),
|
||||
configuration: z
|
||||
.object({
|
||||
heartbeatInterval: z.number().int().min(1),
|
||||
bootRetryInterval: z.number().int().min(1),
|
||||
ocpp2_0_1: z
|
||||
.object({
|
||||
unknownChargerStatus: z.enum([
|
||||
RegistrationStatusEnum.Accepted,
|
||||
RegistrationStatusEnum.Pending,
|
||||
RegistrationStatusEnum.Rejected,
|
||||
]), // Unknown chargers have no entry in BootConfig table
|
||||
getBaseReportOnPending: z.boolean(),
|
||||
bootWithRejectedVariables: z.boolean(),
|
||||
/**
|
||||
* If false, only data endpoint can update boot status to accepted
|
||||
*/
|
||||
autoAccept: z.boolean(),
|
||||
})
|
||||
.optional(),
|
||||
ocpp2_1: z
|
||||
.object({
|
||||
unknownChargerStatus: z.enum([
|
||||
RegistrationStatusEnum.Accepted,
|
||||
RegistrationStatusEnum.Pending,
|
||||
RegistrationStatusEnum.Rejected,
|
||||
]), // Unknown chargers have no entry in BootConfig table
|
||||
getBaseReportOnPending: z.boolean(),
|
||||
bootWithRejectedVariables: z.boolean(),
|
||||
/**
|
||||
* If false, only data endpoint can update boot status to accepted
|
||||
*/
|
||||
autoAccept: z.boolean(),
|
||||
})
|
||||
.optional(),
|
||||
ocpp1_6: z
|
||||
.object({
|
||||
unknownChargerStatus: z.enum([
|
||||
OCPP1_6.BootNotificationResponseStatus.Accepted,
|
||||
OCPP1_6.BootNotificationResponseStatus.Pending,
|
||||
OCPP1_6.BootNotificationResponseStatus.Rejected,
|
||||
]), // Unknown chargers have no entry in BootConfig table
|
||||
})
|
||||
.optional(),
|
||||
endpointPrefix: z.string(),
|
||||
host: z.string().optional(),
|
||||
port: z.number().int().min(1).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
})
|
||||
.refine((obj) => obj.ocpp1_6 || obj.ocpp2_0_1 || obj.ocpp2_1, {
|
||||
message: 'A protocol configuration must be set',
|
||||
}), // Configuration module is required
|
||||
monitoring: z.object({
|
||||
endpointPrefix: z.string(),
|
||||
host: z.string().optional(),
|
||||
port: z.number().int().min(1).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
}),
|
||||
reporting: z.object({
|
||||
endpointPrefix: z.string(),
|
||||
host: z.string().optional(),
|
||||
port: z.number().int().min(1).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
}),
|
||||
smartcharging: z
|
||||
.object({
|
||||
endpointPrefix: z.string(),
|
||||
host: z.string().optional(),
|
||||
port: z.number().int().min(1).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
})
|
||||
.optional(),
|
||||
tenant: z.object({
|
||||
endpointPrefix: z.string(),
|
||||
host: z.string().optional(),
|
||||
port: z.number().int().min(1).optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
ocppRouterBaseUrl: z.string().optional(),
|
||||
}),
|
||||
transactions: z
|
||||
.object({
|
||||
endpointPrefix: z.string(),
|
||||
host: z.string().optional(),
|
||||
port: z.number().int().min(1).optional(),
|
||||
costUpdatedInterval: z.number().int().min(1).optional(),
|
||||
sendCostUpdatedOnMeterValue: z.boolean().optional(),
|
||||
requests: z.array(CallActionSchema),
|
||||
responses: z.array(CallActionSchema),
|
||||
signedMeterValuesConfiguration: z
|
||||
.object({
|
||||
publicKeyFileId: z.string(),
|
||||
signingMethod: z.enum(signedMeterValuesSigningMethods),
|
||||
rejectUnsupportedSignedMeterValues: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
/** Base URL for generating receipt URLs when ReceiptByCSMS is true (C21). */
|
||||
receiptBaseUrl: z.string().optional(),
|
||||
})
|
||||
.refine(
|
||||
(obj) =>
|
||||
!(obj.costUpdatedInterval && obj.sendCostUpdatedOnMeterValue) &&
|
||||
(obj.costUpdatedInterval || obj.sendCostUpdatedOnMeterValue),
|
||||
{
|
||||
message:
|
||||
'Can only update cost based on the interval or in response to a transaction event /meter value' +
|
||||
' update. Not allowed to have both costUpdatedInterval and sendCostUpdatedOnMeterValue configured',
|
||||
},
|
||||
),
|
||||
}),
|
||||
util: z.object({
|
||||
cache: z
|
||||
.object({
|
||||
memory: z.boolean().optional(),
|
||||
redis: z
|
||||
.union([
|
||||
z.object({
|
||||
host: z.string(),
|
||||
port: z.number().int().min(1),
|
||||
}),
|
||||
z.object({
|
||||
url: z.url().refine((v) => v.startsWith('redis://') || v.startsWith('rediss://'), {
|
||||
message: 'Redis URL must start with redis:// or rediss://',
|
||||
}),
|
||||
}),
|
||||
])
|
||||
.optional(),
|
||||
})
|
||||
.refine((obj) => obj.memory || obj.redis, {
|
||||
message: 'A cache implementation must be set',
|
||||
}),
|
||||
messageBroker: z
|
||||
.object({
|
||||
amqp: z
|
||||
.object({
|
||||
url: z.string(),
|
||||
exchange: z.string(),
|
||||
instanceIdentifier: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.refine((obj) => obj.amqp, {
|
||||
message: 'A message broker implementation must be set',
|
||||
}),
|
||||
authProvider: z
|
||||
.object({
|
||||
oidc: z
|
||||
.object({
|
||||
jwksUri: z.string(),
|
||||
issuer: z.string(),
|
||||
audience: z.string(),
|
||||
cacheTime: z.number().int().min(1).optional(),
|
||||
rateLimit: z.boolean(),
|
||||
})
|
||||
.optional(),
|
||||
localByPass: z.boolean().default(false).optional(),
|
||||
})
|
||||
.refine((obj) => obj.oidc || obj.localByPass, {
|
||||
message: 'An auth provider implementation must be set',
|
||||
}),
|
||||
swagger: z
|
||||
.object({
|
||||
path: z.string(),
|
||||
logoPath: z.string(),
|
||||
exposeData: z.boolean(),
|
||||
exposeMessage: z.boolean(),
|
||||
})
|
||||
.optional(),
|
||||
networkConnection: z.object({
|
||||
websocketServers: z.array(websocketServerSchema).refine((array) => {
|
||||
const idsSeen = new Set<string>();
|
||||
return array.filter((obj) => {
|
||||
if (idsSeen.has(obj.id)) {
|
||||
return false;
|
||||
} else {
|
||||
idsSeen.add(obj.id);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}),
|
||||
}),
|
||||
certificateAuthority: z.object({
|
||||
v2gCA: z
|
||||
.object({
|
||||
name: z.enum(['hubject']),
|
||||
hubject: z
|
||||
.object({
|
||||
baseUrl: z.string(),
|
||||
tokenUrl: z.string(),
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.refine(
|
||||
(obj) => {
|
||||
if (obj.name === 'hubject') {
|
||||
return (
|
||||
obj.hubject &&
|
||||
obj.hubject.baseUrl &&
|
||||
obj.hubject.tokenUrl &&
|
||||
obj.hubject.clientId &&
|
||||
obj.hubject.clientSecret
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
message: 'Hubject requires baseUrl, tokenUrl, clientId, and clientSecret',
|
||||
},
|
||||
),
|
||||
chargingStationCA: z
|
||||
.object({
|
||||
name: z.enum(['acme']),
|
||||
acme: z
|
||||
.object({
|
||||
env: z.enum(['staging', 'production']),
|
||||
accountKeyFilePath: z.string(),
|
||||
email: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.refine((obj) => {
|
||||
if (obj.name === 'acme') {
|
||||
return obj.acme;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
logLevel: z.number().min(0).max(6),
|
||||
maxCallLengthSeconds: z.number().int().min(1),
|
||||
maxCachingSeconds: z.number().int().min(1),
|
||||
maxReconnectDelay: z.number().int().min(1).default(30),
|
||||
shutdownGracePeriodSeconds: z.number().int().min(1).default(30),
|
||||
ocpiServer: z.object({
|
||||
host: z.string(),
|
||||
port: z.number().int().min(1),
|
||||
}),
|
||||
userPreferences: z.object({
|
||||
telemetryConsent: z.boolean().optional(),
|
||||
}),
|
||||
rbacRulesFileName: z.string().optional(),
|
||||
rbacRulesDir: z.string().optional(),
|
||||
oidcClient: oidcClientConfigSchema,
|
||||
realTimeAuthDefaultTimeoutSeconds: z.number().int().min(1).default(15),
|
||||
notReadyThresholdSeconds: z.number().int().min(1).default(60),
|
||||
})
|
||||
.refine((obj) => obj.maxCachingSeconds >= obj.maxCallLengthSeconds, {
|
||||
message: 'maxCachingSeconds cannot be less than maxCallLengthSeconds',
|
||||
});
|
||||
|
||||
export const HttpMethodSchema = z.record(
|
||||
z.string(), // HTTP method (GET, POST, etc., or * for all methods)
|
||||
z.array(z.string()), // Array of role names required for this method
|
||||
);
|
||||
|
||||
export const UrlPatternSchema = z.record(
|
||||
z.string(), // URL pattern (/api/users, /api/users/:id, etc.)
|
||||
HttpMethodSchema,
|
||||
);
|
||||
|
||||
export const TenantSchema = z.record(
|
||||
z.string(), // Tenant ID
|
||||
UrlPatternSchema,
|
||||
);
|
||||
|
||||
export const RbacRulesSchema = TenantSchema;
|
||||
|
||||
export type RbacRules = z.infer<typeof RbacRulesSchema>;
|
||||
|
||||
export type WebsocketServerConfig = z.infer<typeof websocketServerSchema>;
|
||||
export type SystemConfig = z.infer<typeof systemConfigSchema>;
|
||||
@@ -0,0 +1,488 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
|
||||
import 'reflect-metadata';
|
||||
import type { ILogObj } from 'tslog';
|
||||
import { Logger } from 'tslog';
|
||||
import type { IDataEndpointDefinition } from './DataEndpointDefinition.js';
|
||||
import type { IMessageEndpointDefinition } from '@interfaces/api/MessageEndpointDefinition.js';
|
||||
import { METADATA_DATA_ENDPOINTS, METADATA_MESSAGE_ENDPOINTS } from '@interfaces/api/metadata.js';
|
||||
import { HttpMethod } from '@interfaces/api/HttpMethods.js';
|
||||
import type { SystemConfig } from '@config/types.js';
|
||||
import type { OcppRequest } from '@ocpp/internal-types.js';
|
||||
import { ConfigStoreFactory } from '@config/ConfigStore.js';
|
||||
import { MessageConfirmationSchema } from '@ocpp/persistence/querySchema.js';
|
||||
import { Namespace, OCPP1_6_Namespace } from '@ocpp/persistence/namespace.js';
|
||||
import { OCPPVersion } from '@ocpp/rpc/message.js';
|
||||
import { systemConfigSchema } from '@config/types.js';
|
||||
import { OCPP2_Namespace } from '@ocpp/persistence/index.js';
|
||||
import type { CallAction } from '@ocpp/rpc/message.js';
|
||||
import type { IMessageConfirmation } from '@interfaces/messages/index.js';
|
||||
import type { IModule } from '@interfaces/modules/Module.js';
|
||||
import { IMessageQuerystringSchema } from '@interfaces/api/MessageQuerystring.js';
|
||||
import type { IModuleApi } from '@interfaces/api/ModuleApi.js';
|
||||
import { AuthorizationSecurity } from '@interfaces/api/AuthorizationSecurity.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Abstract module api class implementation.
|
||||
*/
|
||||
export abstract class AbstractModuleApi<T extends IModule> implements IModuleApi {
|
||||
protected readonly _server: FastifyInstance;
|
||||
protected readonly _module: T;
|
||||
protected readonly _logger: Logger<ILogObj>;
|
||||
protected readonly _ocppVersion: OCPPVersion | null;
|
||||
|
||||
constructor(
|
||||
module: T,
|
||||
server: FastifyInstance,
|
||||
ocppVersion: OCPPVersion | null,
|
||||
logger?: Logger<ILogObj>,
|
||||
) {
|
||||
this._module = module;
|
||||
this._server = server;
|
||||
this._ocppVersion = ocppVersion;
|
||||
|
||||
this._logger = logger
|
||||
? logger.getSubLogger({ name: this.constructor.name })
|
||||
: new Logger<ILogObj>({ name: this.constructor.name });
|
||||
this._init(this._module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API for the given module.
|
||||
*
|
||||
* @param {T} module - The module to initialize the API for.
|
||||
*/
|
||||
protected _init(module: T): void {
|
||||
(
|
||||
Reflect.getMetadata(
|
||||
METADATA_MESSAGE_ENDPOINTS,
|
||||
this.constructor,
|
||||
) as Array<IMessageEndpointDefinition>
|
||||
)?.forEach((expose) => {
|
||||
this._addMessageRoute.call(
|
||||
this,
|
||||
expose.action,
|
||||
expose.method,
|
||||
typeof expose.bodySchema === 'function' ? expose.bodySchema(this) : expose.bodySchema,
|
||||
expose.optionalQuerystrings,
|
||||
);
|
||||
});
|
||||
|
||||
const dataEndpointDefinitions = Reflect.getMetadata(
|
||||
METADATA_DATA_ENDPOINTS,
|
||||
this.constructor,
|
||||
) as Array<IDataEndpointDefinition>;
|
||||
dataEndpointDefinitions?.forEach((expose) => {
|
||||
this._addDataRoute.call(
|
||||
this,
|
||||
expose.namespace,
|
||||
expose.method,
|
||||
expose.httpMethod,
|
||||
expose.querySchema,
|
||||
expose.paramSchema,
|
||||
expose.headerSchema,
|
||||
expose.bodySchema,
|
||||
expose.responseSchema,
|
||||
expose.tags,
|
||||
expose.description,
|
||||
expose.security,
|
||||
);
|
||||
});
|
||||
|
||||
if (dataEndpointDefinitions && dataEndpointDefinitions.length > 0) {
|
||||
this.registerSystemConfigRoutes(module);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message route to the server.
|
||||
*
|
||||
* @param {CallAction} action - The action to be called.
|
||||
* @param {Function} method - The method to be executed.
|
||||
* @param {object} bodySchema - The schema for the route.
|
||||
* @param {Record<string, any>} optionalQuerystrings - Optional querystrings for the route.
|
||||
* @return {void}
|
||||
*/
|
||||
protected _addMessageRoute(
|
||||
action: CallAction,
|
||||
method: (...args: any[]) => any,
|
||||
bodySchema: object,
|
||||
optionalQuerystrings?: Record<string, any>,
|
||||
): void {
|
||||
if (!bodySchema) {
|
||||
this._logger.debug(
|
||||
`Skipping message route for ${action} — schema not available for ${this._ocppVersion}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
this._logger.debug(`Adding message route for ${action}`, this._toMessagePath(action));
|
||||
|
||||
/**
|
||||
* Executes the handler function for the given request.
|
||||
*
|
||||
* @param {FastifyRequest<{ Body: OcppRequest, Querystring: IMessageQuerystring }>} request - The request object containing the body and querystring.
|
||||
* @return {Promise<IMessageConfirmation>} The promise that resolves to the message confirmation.
|
||||
*/
|
||||
const _handler = async (
|
||||
request: FastifyRequest<{
|
||||
Body: OcppRequest;
|
||||
Querystring: Record<string, any>;
|
||||
}>,
|
||||
): Promise<IMessageConfirmation[]> => {
|
||||
const { identifier, tenantId, callbackUrl, ...extraQueries } = request.query;
|
||||
|
||||
const identifiers = Array.isArray(identifier) ? identifier : [identifier];
|
||||
|
||||
return method.call(
|
||||
this,
|
||||
identifiers,
|
||||
request.body,
|
||||
callbackUrl,
|
||||
tenantId,
|
||||
Object.keys(extraQueries).length > 0 ? extraQueries : undefined,
|
||||
);
|
||||
};
|
||||
|
||||
const mergedQuerySchema = {
|
||||
...IMessageQuerystringSchema,
|
||||
properties: {
|
||||
...IMessageQuerystringSchema.properties,
|
||||
...(optionalQuerystrings || {}),
|
||||
},
|
||||
};
|
||||
|
||||
const _opts: any = {
|
||||
method: HttpMethod.Post,
|
||||
url: this._toMessagePath(action),
|
||||
handler: _handler,
|
||||
schema: {
|
||||
body: bodySchema,
|
||||
querystring: mergedQuerySchema,
|
||||
response: {
|
||||
200: {
|
||||
$id: 'MessageConfirmationSchemaArray',
|
||||
type: 'array',
|
||||
items: MessageConfirmationSchema,
|
||||
},
|
||||
},
|
||||
} as const,
|
||||
};
|
||||
|
||||
if (this._module.config.util.swagger?.exposeMessage) {
|
||||
this._server.register(async (fastifyInstance) => {
|
||||
this.registerSchemaForOpts(fastifyInstance, _opts);
|
||||
fastifyInstance.route(_opts);
|
||||
});
|
||||
} else {
|
||||
this._server.route(_opts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message route to the server.
|
||||
*
|
||||
* @param {OCPP2_Namespace | OCPP1_6_Namespace | Namespace} namespace - The entity type.
|
||||
* @param {Function} method - The method to be executed.
|
||||
* @param {HttpMethod} httpMethod - The HTTP method to be used.
|
||||
* @param {object} querySchema - The schema for the querystring.
|
||||
* @param {object} paramSchema - The schema for the parameters.
|
||||
* @param {object} headerSchema - The schema for the headers.
|
||||
* @param {object} bodySchema - The schema for the body.
|
||||
* @param {object} responseSchema - The schema for the response.
|
||||
* @param {string[]} tags - The tags for the route.
|
||||
* @param {string} description - The description for the route.
|
||||
* @param {object[]} security - The security for the route.
|
||||
* @return {void}
|
||||
*/
|
||||
protected _addDataRoute(
|
||||
namespace: OCPP2_Namespace | OCPP1_6_Namespace | Namespace,
|
||||
method: (...args: any[]) => any,
|
||||
httpMethod: HttpMethod,
|
||||
querySchema?: object,
|
||||
paramSchema?: object,
|
||||
headerSchema?: object,
|
||||
bodySchema?: object,
|
||||
responseSchema?: object,
|
||||
tags?: string[],
|
||||
description?: string,
|
||||
security?: object[],
|
||||
): void {
|
||||
this._logger.debug(
|
||||
`Adding data route for ${namespace}`,
|
||||
this._toDataPath(namespace),
|
||||
httpMethod,
|
||||
);
|
||||
const schema: Record<string, any> = {};
|
||||
if (querySchema) {
|
||||
schema['querystring'] = querySchema;
|
||||
}
|
||||
if (bodySchema) {
|
||||
schema['body'] = bodySchema;
|
||||
}
|
||||
if (paramSchema) {
|
||||
schema['params'] = paramSchema;
|
||||
}
|
||||
if (headerSchema) {
|
||||
schema['headers'] = headerSchema;
|
||||
}
|
||||
if (responseSchema) {
|
||||
schema['response'] = {
|
||||
200: responseSchema,
|
||||
};
|
||||
}
|
||||
if (tags) {
|
||||
schema['tags'] = tags;
|
||||
}
|
||||
if (description) {
|
||||
schema['description'] = description;
|
||||
}
|
||||
if (security) {
|
||||
schema['security'] = security;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request and returns a Promise resolving to an object.
|
||||
*
|
||||
* @param {FastifyRequest<{ Body: object, Querystring: object }>} request - the request object
|
||||
* @param {FastifyReply} reply - the reply object
|
||||
* @return {Promise<any>} - a Promise resolving to an object
|
||||
*/
|
||||
const _handler = async (
|
||||
request: FastifyRequest<{
|
||||
Body: object;
|
||||
Querystring: object;
|
||||
}>,
|
||||
reply: FastifyReply,
|
||||
): Promise<unknown> =>
|
||||
(method.call(this, request, reply) as Promise<undefined | string | object>).catch((err) => {
|
||||
// TODO: figure out better error codes & messages
|
||||
this._logger.error('Error in handling data route', err);
|
||||
const statusCode = err.statusCode ? err.statusCode : 500;
|
||||
reply.status(statusCode).send(err);
|
||||
});
|
||||
|
||||
const _opts: any = {
|
||||
method: httpMethod,
|
||||
url: this._toDataPath(namespace),
|
||||
schema: schema,
|
||||
handler: _handler,
|
||||
};
|
||||
|
||||
if (
|
||||
!!schema &&
|
||||
!!schema.headers &&
|
||||
!!schema.headers.properties &&
|
||||
!!schema.headers.properties.Authorization
|
||||
) {
|
||||
_opts['preHandler'] = (this._server as unknown as any).auth([
|
||||
(this._server as unknown as any).authorization,
|
||||
]);
|
||||
if (!_opts['security']) {
|
||||
_opts.schema['security'] = [AuthorizationSecurity];
|
||||
} else {
|
||||
_opts.schema['security'].push(AuthorizationSecurity);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._module.config.util.swagger?.exposeData) {
|
||||
this._server.register(async (fastifyInstance) => {
|
||||
this.registerSchemaForOpts(fastifyInstance, _opts);
|
||||
fastifyInstance.route<{ Body: object; Querystring: object }>(_opts);
|
||||
});
|
||||
} else {
|
||||
this._server.route<{ Body: object; Querystring: object }>(_opts);
|
||||
}
|
||||
}
|
||||
|
||||
private registerSchemaForOpts = (fastifyInstance: FastifyInstance, _opts: any) => {
|
||||
if (_opts.schema['querystring']) {
|
||||
_opts.schema['querystring'] = this.registerSchema(
|
||||
fastifyInstance,
|
||||
_opts.schema['querystring'],
|
||||
);
|
||||
}
|
||||
if (_opts.schema['body']) {
|
||||
_opts.schema['body'] = this.registerSchema(
|
||||
fastifyInstance,
|
||||
_opts.schema['body'],
|
||||
this._ocppVersion ? `${this._ocppVersion}-` : '',
|
||||
);
|
||||
}
|
||||
if (_opts.schema['params']) {
|
||||
_opts.schema['params'] = this.registerSchema(fastifyInstance, _opts.schema['params']);
|
||||
}
|
||||
if (_opts.schema['headers']) {
|
||||
_opts.schema['headers'] = this.registerSchema(fastifyInstance, _opts.schema['headers']);
|
||||
}
|
||||
if (_opts.schema['response']) {
|
||||
_opts.schema['response'] = {
|
||||
200: this.registerSchema(fastifyInstance, _opts.schema['response'][200]),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
protected registerSchema = (
|
||||
fastifyInstance: FastifyInstance,
|
||||
schema: any,
|
||||
schemaIdPrefix?: string,
|
||||
): object | null => {
|
||||
let id = schema['$id'];
|
||||
if (!id) {
|
||||
this._logger.error('Could not register schema because no ID', schema);
|
||||
}
|
||||
|
||||
try {
|
||||
const schemaCopy = this.removeUnknownKeys(schema);
|
||||
if (id && schemaIdPrefix) {
|
||||
id = schemaIdPrefix + id;
|
||||
schemaCopy['$id'] = id;
|
||||
this._logger.debug(`Update schema id: ${schemaCopy['$id']}`);
|
||||
}
|
||||
if (
|
||||
schemaCopy.required &&
|
||||
Array.isArray(schemaCopy.required) &&
|
||||
schemaCopy.required.length === 0
|
||||
) {
|
||||
delete schemaCopy.required;
|
||||
}
|
||||
if (schema.definitions) {
|
||||
Object.keys(schema.definitions).forEach((key) => {
|
||||
const definition = schema.definitions[key];
|
||||
if (!definition['$id']) {
|
||||
definition['$id'] = key;
|
||||
}
|
||||
this.registerSchema(fastifyInstance, definition);
|
||||
});
|
||||
}
|
||||
if (schemaCopy.properties) {
|
||||
Object.keys(schemaCopy.properties).forEach((key) => {
|
||||
const property = schemaCopy.properties[key];
|
||||
if (property.$ref) {
|
||||
property.$ref = property.$ref.replace('#/definitions/', '');
|
||||
}
|
||||
if (property.items && property.items.$ref) {
|
||||
property.items.$ref = property.items.$ref.replace('#/definitions/', '');
|
||||
}
|
||||
});
|
||||
}
|
||||
fastifyInstance.addSchema(schemaCopy);
|
||||
this._server.addSchema(schemaCopy);
|
||||
return {
|
||||
$ref: `${id}`,
|
||||
};
|
||||
} catch (e: any) {
|
||||
// ignore already declared
|
||||
if (e.code === 'FST_ERR_SCH_ALREADY_PRESENT') {
|
||||
return {
|
||||
$ref: `${id}`,
|
||||
};
|
||||
} else {
|
||||
this._logger.error('Could not register schema', e, schema);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
protected registerSystemConfigRoutes(module: T) {
|
||||
this._addDataRoute.call(
|
||||
this,
|
||||
OCPP2_Namespace.SystemConfig,
|
||||
() => new Promise((resolve) => resolve(module.config)),
|
||||
HttpMethod.Get,
|
||||
);
|
||||
|
||||
const systemConfigJsonSchema = z.toJSONSchema(systemConfigSchema, {
|
||||
target: 'openapi-3.0',
|
||||
});
|
||||
this._addDataRoute.call(
|
||||
this,
|
||||
OCPP2_Namespace.SystemConfig,
|
||||
async (request: FastifyRequest<{ Body: SystemConfig }>) => {
|
||||
await ConfigStoreFactory.getInstance().saveConfig(request.body);
|
||||
module.config = request.body;
|
||||
},
|
||||
HttpMethod.Put,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
...systemConfigJsonSchema,
|
||||
$id: 'SystemConfigSchema',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: for performance reasons can these unknown keys be removed directly from schemas?
|
||||
private removeUnknownKeys = (schema: any): any => {
|
||||
// Create a deep copy of the schema
|
||||
const schemaCopy = structuredClone(schema); // Use structuredClone for a true deep copy
|
||||
|
||||
const cleanSchema = (obj: any) => {
|
||||
if (typeof obj !== 'object' || obj === null) return;
|
||||
|
||||
// Remove specific unknown keys
|
||||
for (const unknownKey of ['comment', 'javaType', 'tsEnumNames']) {
|
||||
if (unknownKey in obj) {
|
||||
delete obj[unknownKey];
|
||||
}
|
||||
}
|
||||
|
||||
// Remove `additionalItems` if `items` is not an array
|
||||
if ('items' in obj && !Array.isArray(obj.items) && 'additionalItems' in obj) {
|
||||
delete obj.additionalItems;
|
||||
}
|
||||
|
||||
// Remove `additionalProperties` if `type` is not "object"
|
||||
if ('additionalProperties' in obj && obj.type !== 'object') {
|
||||
delete obj.additionalProperties;
|
||||
}
|
||||
|
||||
// Recursively process nested objects
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === 'object') {
|
||||
cleanSchema(obj[key]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Clean the copied schema
|
||||
cleanSchema(schemaCopy);
|
||||
|
||||
return schemaCopy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a {@link CallAction} to a normed lowercase URL path.
|
||||
*
|
||||
* @param {CallAction} input - The {@link CallAction} to convert to a URL path.
|
||||
* @param {string} prefix - The module name.
|
||||
* @returns {string} - String representation of URL path.
|
||||
*/
|
||||
protected _toMessagePath(input: CallAction, prefix?: string): string {
|
||||
const endpointPrefix = prefix || '';
|
||||
const endpointVersion = (this._ocppVersion ? this._ocppVersion : OCPPVersion.OCPP2_0_1).replace(
|
||||
/^ocpp/,
|
||||
'',
|
||||
);
|
||||
return `/ocpp/${endpointVersion}${!endpointPrefix.startsWith('/') ? '/' : ''}${endpointPrefix}${!endpointPrefix.endsWith('/') ? '/' : ''}${input.charAt(0).toLowerCase() + input.slice(1)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a namespace to a normed lowercase URL path.
|
||||
*
|
||||
* @param {OCPP2_Namespace | OCPP1_6_Namespace | Namespace} input - The {@link OCPP2_Namespace} or {@link OCPP1_6_Namespace} or {@link Namespace} to convert to a URL path.
|
||||
* @param {string} prefix - The module name.
|
||||
* @returns {string} - String representation of URL path.
|
||||
*/
|
||||
protected _toDataPath(
|
||||
input: OCPP2_Namespace | OCPP1_6_Namespace | Namespace,
|
||||
prefix?: string,
|
||||
): string {
|
||||
const endpointPrefix = prefix || '';
|
||||
return `/data${!endpointPrefix.startsWith('/') ? '/' : ''}${endpointPrefix}${!endpointPrefix.endsWith('/') ? '/' : ''}${input.charAt(0).toLowerCase() + input.slice(1)}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { IDataEndpointDefinition } from '@interfaces/api/DataEndpointDefinition.js';
|
||||
import { HttpMethod } from '@interfaces/api/HttpMethods.js';
|
||||
import { METADATA_DATA_ENDPOINTS } from '@interfaces/api/metadata.js';
|
||||
import { Namespace, OCPP1_6_Namespace, OCPP2_Namespace } from '@ocpp/persistence/index.js';
|
||||
|
||||
/**
|
||||
* Decorator for use in module API class to expose methods as REST data endpoints.
|
||||
*
|
||||
* @param {OCPP2_Namespace} namespace - The namespace value.
|
||||
* @param {HttpMethod} method - The HTTP method value.
|
||||
* @param {object} querySchema - The query schema value (optional).
|
||||
* @param {object} bodySchema - The body schema value (optional).
|
||||
* @param {object} paramSchema - The param schema value (optional).
|
||||
* @param {object} headerSchema - The header schema value (optional).
|
||||
* @param {object} responseSchema - The response schema value (optional).
|
||||
* @param {object} tags - The tags value (optional).
|
||||
* @param {object} security - The security value (optional).
|
||||
* @param {string} description - The description (optional).
|
||||
* @return {void} - No return value.
|
||||
*/
|
||||
export const AsDataEndpoint = function (
|
||||
namespace: OCPP2_Namespace | OCPP1_6_Namespace | Namespace,
|
||||
method: HttpMethod,
|
||||
querySchema?: object,
|
||||
bodySchema?: object,
|
||||
paramSchema?: object,
|
||||
headerSchema?: object,
|
||||
responseSchema?: object,
|
||||
tags?: string | string[],
|
||||
security?: object[],
|
||||
description?: string,
|
||||
) {
|
||||
return (target: any, propertyKey: string, descriptor: PropertyDescriptor): void => {
|
||||
if (!Reflect.hasMetadata(METADATA_DATA_ENDPOINTS, target.constructor)) {
|
||||
Reflect.defineMetadata(METADATA_DATA_ENDPOINTS, [], target.constructor);
|
||||
}
|
||||
const dataEndpoints = Reflect.getMetadata(
|
||||
METADATA_DATA_ENDPOINTS,
|
||||
target.constructor,
|
||||
) as Array<IDataEndpointDefinition>;
|
||||
let tagList: string[] | undefined = undefined;
|
||||
if (tags) {
|
||||
tagList = Array.isArray(tags) ? tags : [tags];
|
||||
}
|
||||
dataEndpoints.push({
|
||||
method: descriptor.value,
|
||||
methodName: propertyKey,
|
||||
namespace: namespace,
|
||||
httpMethod: method,
|
||||
querySchema: querySchema,
|
||||
bodySchema: bodySchema,
|
||||
paramSchema: paramSchema,
|
||||
headerSchema: headerSchema,
|
||||
responseSchema: responseSchema,
|
||||
tags: tagList,
|
||||
description: description,
|
||||
security: security,
|
||||
});
|
||||
Reflect.defineMetadata(METADATA_DATA_ENDPOINTS, dataEndpoints, target.constructor);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { IMessageEndpointDefinition } from '@interfaces/api/MessageEndpointDefinition.js';
|
||||
import { METADATA_MESSAGE_ENDPOINTS } from '@interfaces/api/metadata.js';
|
||||
import type { CallAction } from '@ocpp/rpc/message.js';
|
||||
|
||||
/**
|
||||
* Decorator for use in module API class to expose methods as REST OCPP message endpoints.
|
||||
*
|
||||
* @param {CallAction} action - The call action.
|
||||
* @param {object} bodySchema - The body schema.
|
||||
* @param {Record<string, any>} optionalQuerystrings - The optional querystrings.
|
||||
* @return {void} This function does not return anything.
|
||||
*/
|
||||
export const AsMessageEndpoint = function (
|
||||
action: CallAction,
|
||||
bodySchema: object | ((instance: any) => object),
|
||||
optionalQuerystrings?: Record<string, any>,
|
||||
) {
|
||||
return (target: any, propertyKey: string, descriptor: PropertyDescriptor): void => {
|
||||
if (!Reflect.hasMetadata(METADATA_MESSAGE_ENDPOINTS, target.constructor)) {
|
||||
Reflect.defineMetadata(
|
||||
METADATA_MESSAGE_ENDPOINTS,
|
||||
new Array<IMessageEndpointDefinition>(),
|
||||
target.constructor,
|
||||
);
|
||||
}
|
||||
const messageEndpoints = Reflect.getMetadata(
|
||||
METADATA_MESSAGE_ENDPOINTS,
|
||||
target.constructor,
|
||||
) as Array<IMessageEndpointDefinition>;
|
||||
messageEndpoints.push({
|
||||
action: action,
|
||||
method: descriptor.value,
|
||||
methodName: propertyKey,
|
||||
bodySchema: bodySchema,
|
||||
optionalQuerystrings: optionalQuerystrings,
|
||||
});
|
||||
Reflect.defineMetadata(METADATA_MESSAGE_ENDPOINTS, messageEndpoints, target.constructor);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export const AuthorizationSecurity = {
|
||||
authorization: [],
|
||||
};
|
||||
|
||||
export const AuthorizationSecurityList = [AuthorizationSecurity];
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { HttpMethod } from '@interfaces/api/HttpMethods.js';
|
||||
import { Namespace } from '@ocpp/persistence/namespace.js';
|
||||
import { OCPP1_6_Namespace, OCPP2_Namespace } from '@ocpp/persistence/index.js';
|
||||
|
||||
/**
|
||||
* Interface for usage in {@link AsDataEndpoint} decorator.
|
||||
*/
|
||||
export interface IDataEndpointDefinition {
|
||||
method: (...args: any[]) => any;
|
||||
methodName: string;
|
||||
namespace: OCPP2_Namespace | OCPP1_6_Namespace | Namespace;
|
||||
httpMethod: HttpMethod;
|
||||
querySchema?: object;
|
||||
bodySchema?: object;
|
||||
paramSchema?: object;
|
||||
headerSchema?: object;
|
||||
responseSchema?: object;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
summary?: string;
|
||||
security?: object[];
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
|
||||
|
||||
export interface ExceptionHandler {
|
||||
handle(error: FastifyError, request: FastifyRequest, reply: FastifyReply): void;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export enum HttpHeader {
|
||||
ContentType = 'Content-Type',
|
||||
ContentLength = 'Content-Length',
|
||||
Accept = 'Accept',
|
||||
Authorization = 'Authorization',
|
||||
CacheControl = 'Cache-Control',
|
||||
UserAgent = 'User-Agent',
|
||||
AcceptEncoding = 'Accept-Encoding',
|
||||
AcceptLanguage = 'Accept-Language',
|
||||
Connection = 'Connection',
|
||||
Host = 'Host',
|
||||
Referer = 'Referer',
|
||||
Origin = 'Origin',
|
||||
AccessControlAllowOrigin = 'Access-Control-Allow-Origin',
|
||||
ETag = 'ETag',
|
||||
IfNoneMatch = 'If-None-Match',
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export enum HttpMethod {
|
||||
Get = 'GET',
|
||||
Post = 'POST',
|
||||
Put = 'PUT',
|
||||
Delete = 'DELETE',
|
||||
Patch = 'PATCH',
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export enum HttpStatus {
|
||||
CONTINUE = 100,
|
||||
SWITCHING_PROTOCOLS = 101,
|
||||
PROCESSING = 102,
|
||||
OK = 200,
|
||||
CREATED = 201,
|
||||
ACCEPTED = 202,
|
||||
NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
NO_CONTENT = 204,
|
||||
RESET_CONTENT = 205,
|
||||
PARTIAL_CONTENT = 206,
|
||||
AMBIGUOUS = 300,
|
||||
MOVED_PERMANENTLY = 301,
|
||||
FOUND = 302,
|
||||
SEE_OTHER = 303,
|
||||
NOT_MODIFIED = 304,
|
||||
TEMPORARY_REDIRECT = 307,
|
||||
PERMANENT_REDIRECT = 308,
|
||||
BAD_REQUEST = 400,
|
||||
UNAUTHORIZED = 401,
|
||||
PAYMENT_REQUIRED = 402,
|
||||
FORBIDDEN = 403,
|
||||
NOT_FOUND = 404,
|
||||
METHOD_NOT_ALLOWED = 405,
|
||||
NOT_ACCEPTABLE = 406,
|
||||
PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||
REQUEST_TIMEOUT = 408,
|
||||
CONFLICT = 409,
|
||||
GONE = 410,
|
||||
LENGTH_REQUIRED = 411,
|
||||
PRECONDITION_FAILED = 412,
|
||||
PAYLOAD_TOO_LARGE = 413,
|
||||
URI_TOO_LONG = 414,
|
||||
UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
||||
EXPECTATION_FAILED = 417,
|
||||
I_AM_A_TEAPOT = 418,
|
||||
UNPROCESSABLE_ENTITY = 422,
|
||||
TOO_MANY_REQUESTS = 429,
|
||||
INTERNAL_SERVER_ERROR = 500,
|
||||
NOT_IMPLEMENTED = 501,
|
||||
BAD_GATEWAY = 502,
|
||||
SERVICE_UNAVAILABLE = 503,
|
||||
GATEWAY_TIMEOUT = 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { CallAction } from '@ocpp/rpc/message.js';
|
||||
|
||||
/**
|
||||
* Interface for usage in {@link AsMessageEndpoint} decorator.
|
||||
*/
|
||||
export interface IMessageEndpointDefinition {
|
||||
action: CallAction;
|
||||
method: (...args: any[]) => any;
|
||||
methodName: string;
|
||||
bodySchema: object | ((instance: any) => object);
|
||||
optionalQuerystrings?: Record<string, any>;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID } from '@config/defineConfig.js';
|
||||
|
||||
/**
|
||||
* The message querystring interface, used for every OCPP message endpoint to validate query parameters.
|
||||
*/
|
||||
export interface IMessageQuerystring {
|
||||
identifier: string | string[];
|
||||
tenantId?: number;
|
||||
callbackUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This message querystring schema describes the {@link IMessageQuerystring} interface.
|
||||
*/
|
||||
export const IMessageQuerystringSchema = {
|
||||
$id: 'MessageQuerystring',
|
||||
type: 'object',
|
||||
properties: {
|
||||
identifier: {
|
||||
anyOf: [
|
||||
{ type: 'string' },
|
||||
{
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
],
|
||||
},
|
||||
tenantId: { type: 'number', default: DEFAULT_TENANT_ID },
|
||||
callbackUrl: { type: 'string' },
|
||||
},
|
||||
required: ['identifier', 'tenantId'],
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* The module API interface.
|
||||
*/
|
||||
export interface IModuleApi {}
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { UserInfo } from './UserInfo.js';
|
||||
|
||||
/**
|
||||
* Result of authentication process
|
||||
*/
|
||||
export class ApiAuthenticationResult {
|
||||
/**
|
||||
* Whether authentication was successful
|
||||
*/
|
||||
isAuthenticated: boolean = false;
|
||||
|
||||
/**
|
||||
* User information if authentication was successful
|
||||
*/
|
||||
user?: UserInfo;
|
||||
|
||||
/**
|
||||
* Error message if authentication failed
|
||||
*/
|
||||
error?: string;
|
||||
|
||||
/**
|
||||
* Creates a new successful authentication result
|
||||
*
|
||||
* @param user Authenticated user information
|
||||
* @returns Authentication result
|
||||
*/
|
||||
static success(user: UserInfo): ApiAuthenticationResult {
|
||||
const result = new ApiAuthenticationResult();
|
||||
result.isAuthenticated = true;
|
||||
result.user = user;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new failed authentication result
|
||||
*
|
||||
* @param error Error message
|
||||
* @returns Authentication result
|
||||
*/
|
||||
static failure(error: string): ApiAuthenticationResult {
|
||||
const result = new ApiAuthenticationResult();
|
||||
result.isAuthenticated = false;
|
||||
result.error = error;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Result of authorization process
|
||||
*/
|
||||
export class ApiAuthorizationResult {
|
||||
/**
|
||||
* Whether authorization was successful
|
||||
*/
|
||||
isAuthorized: boolean = false;
|
||||
|
||||
/**
|
||||
* Error message if authorization failed
|
||||
*/
|
||||
error?: string;
|
||||
|
||||
/**
|
||||
* Creates a new successful authorization result
|
||||
*
|
||||
* @returns Authorization result
|
||||
*/
|
||||
static success(): ApiAuthorizationResult {
|
||||
const result = new ApiAuthorizationResult();
|
||||
result.isAuthorized = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new failed authorization result
|
||||
*
|
||||
* @param error Error message
|
||||
* @returns Authorization result
|
||||
*/
|
||||
static failure(error: string): ApiAuthorizationResult {
|
||||
const result = new ApiAuthorizationResult();
|
||||
result.isAuthorized = false;
|
||||
result.error = error;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { UserInfo } from './UserInfo.js';
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { ApiAuthorizationResult } from './ApiAuthorizationResult.js';
|
||||
import { ApiAuthenticationResult } from './ApiAuthenticationResult.js';
|
||||
|
||||
/**
|
||||
* Interface for authentication providers
|
||||
*/
|
||||
export interface IApiAuthProvider {
|
||||
/**
|
||||
* Extracts the authentication token from the request
|
||||
* @param request
|
||||
*/
|
||||
extractToken(request: FastifyRequest): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Authenticates a token and extracts user information
|
||||
*
|
||||
* @param token JWT or other token to authenticate
|
||||
* @returns Authentication result with user info if successful
|
||||
*/
|
||||
authenticateToken(token: string): Promise<ApiAuthenticationResult>;
|
||||
|
||||
/**
|
||||
* Authorizes a user for a specific request
|
||||
*
|
||||
* @param user User information
|
||||
* @param request Fastify request
|
||||
* @returns Authorization result
|
||||
*/
|
||||
authorizeUser(user: UserInfo, request: FastifyRequest): Promise<ApiAuthorizationResult>;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Interface for user information extracted from authentication tokens
|
||||
*/
|
||||
export interface UserInfo {
|
||||
/**
|
||||
* The user ID.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The username.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The user email.
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* The user roles.
|
||||
*/
|
||||
roles: string[];
|
||||
|
||||
/**
|
||||
* Tenant ID associated with the user.
|
||||
*/
|
||||
tenantId: string;
|
||||
|
||||
/**
|
||||
* Additional fields associated with the user
|
||||
*/
|
||||
[key: string]: any;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export class UnauthorizedError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'UnauthorizedError';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export class BadRequestError extends Error {
|
||||
statusCode = 400;
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'BadRequestError';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export class NotFoundError extends Error {
|
||||
statusCode = 404;
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'NotFoundError';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export class UnauthorizedException extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'UnauthorizedException';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export enum HttpHeader {
|
||||
ContentType = 'Content-Type',
|
||||
ContentLength = 'Content-Length',
|
||||
Accept = 'Accept',
|
||||
Authorization = 'Authorization',
|
||||
CacheControl = 'Cache-Control',
|
||||
UserAgent = 'User-Agent',
|
||||
AcceptEncoding = 'Accept-Encoding',
|
||||
AcceptLanguage = 'Accept-Language',
|
||||
Connection = 'Connection',
|
||||
Host = 'Host',
|
||||
Referer = 'Referer',
|
||||
Origin = 'Origin',
|
||||
AccessControlAllowOrigin = 'Access-Control-Allow-Origin',
|
||||
ETag = 'ETag',
|
||||
IfNoneMatch = 'If-None-Match',
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export enum HttpStatus {
|
||||
CONTINUE = 100,
|
||||
SWITCHING_PROTOCOLS = 101,
|
||||
PROCESSING = 102,
|
||||
OK = 200,
|
||||
CREATED = 201,
|
||||
ACCEPTED = 202,
|
||||
NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
NO_CONTENT = 204,
|
||||
RESET_CONTENT = 205,
|
||||
PARTIAL_CONTENT = 206,
|
||||
AMBIGUOUS = 300,
|
||||
MOVED_PERMANENTLY = 301,
|
||||
FOUND = 302,
|
||||
SEE_OTHER = 303,
|
||||
NOT_MODIFIED = 304,
|
||||
TEMPORARY_REDIRECT = 307,
|
||||
PERMANENT_REDIRECT = 308,
|
||||
BAD_REQUEST = 400,
|
||||
UNAUTHORIZED = 401,
|
||||
PAYMENT_REQUIRED = 402,
|
||||
FORBIDDEN = 403,
|
||||
NOT_FOUND = 404,
|
||||
METHOD_NOT_ALLOWED = 405,
|
||||
NOT_ACCEPTABLE = 406,
|
||||
PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||
REQUEST_TIMEOUT = 408,
|
||||
CONFLICT = 409,
|
||||
GONE = 410,
|
||||
LENGTH_REQUIRED = 411,
|
||||
PRECONDITION_FAILED = 412,
|
||||
PAYLOAD_TOO_LARGE = 413,
|
||||
URI_TOO_LONG = 414,
|
||||
UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
||||
EXPECTATION_FAILED = 417,
|
||||
I_AM_A_TEAPOT = 418,
|
||||
UNPROCESSABLE_ENTITY = 422,
|
||||
TOO_MANY_REQUESTS = 429,
|
||||
INTERNAL_SERVER_ERROR = 500,
|
||||
NOT_IMPLEMENTED = 501,
|
||||
BAD_GATEWAY = 502,
|
||||
SERVICE_UNAVAILABLE = 503,
|
||||
GATEWAY_TIMEOUT = 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import 'reflect-metadata';
|
||||
|
||||
export const METADATA_DATA_ENDPOINTS = 'dataEndpoints';
|
||||
export const METADATA_MESSAGE_ENDPOINTS = 'messageEndpoints';
|
||||
@@ -0,0 +1,28 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AuthorizationDto } from '@interfaces/dto/authorization.dto.js';
|
||||
import type { AuthorizationStatusEnumType } from '@interfaces/dto/types/enums.js';
|
||||
import type { ConnectorDto } from '@interfaces/dto/connector.dto.js';
|
||||
import type { EvseDto } from '@interfaces/dto/evse.dto.js';
|
||||
import type { IMessageContext } from '@interfaces/messages/MessageContext.js';
|
||||
|
||||
export interface IAuthorizer {
|
||||
/**
|
||||
* Interface for adding additional authorization logic.
|
||||
* If status is not Accepted, the authorization process will stop and the rejection put into the response.
|
||||
* The order of Authorizers can lead to different outputs; instantiate them in the order they should be called.
|
||||
*
|
||||
* @param {AuthorizationDto} authorization The authorization object associated with the idToken in the request. No modifications should be made to this object.
|
||||
* @param {IMessageContext} context
|
||||
*
|
||||
* @returns {Promise<AuthorizationStatusEnumType>} The updated authorization status
|
||||
**/
|
||||
authorize(
|
||||
authorization: AuthorizationDto,
|
||||
context: IMessageContext,
|
||||
evse?: EvseDto,
|
||||
connector?: ConnectorDto,
|
||||
): Promise<AuthorizationStatusEnumType>;
|
||||
}
|
||||
99
tools/citrineos-core-main/packages/base/src/interfaces/cache/cache.ts
vendored
Normal file
99
tools/citrineos-core-main/packages/base/src/interfaces/cache/cache.ts
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ClassConstructor } from 'class-transformer';
|
||||
|
||||
/**
|
||||
* Interface for cache
|
||||
* Implementers SHALL ensure minimal logic outside of promise resolution or async function to prevent lag
|
||||
* Users of this interface can assume these methods behave asynchronously
|
||||
*/
|
||||
export interface ICache {
|
||||
exists(key: string, namespace?: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Returns true if any keys exist in the given namespace.
|
||||
*
|
||||
* @param {string} namespace - The namespace to check.
|
||||
* @returns {Promise<boolean>} - Returns true if at least one key exists in the namespace.
|
||||
* */
|
||||
existsAnyInNamespace(namespace: string): Promise<boolean>;
|
||||
|
||||
remove<T>(key: string, namespace?: string): Promise<T | null>;
|
||||
|
||||
/**
|
||||
* Monitors a key for potential changes to its value.
|
||||
* If key-value does not exist this method will wait for it to exist or return null at the end of the wait period.
|
||||
* If value is removed, the method will return null.
|
||||
*
|
||||
* @param {string} key - The key for the value.
|
||||
* @param {number} [waitSeconds] - The number of seconds after which the method should return if the value has not been modified by then.
|
||||
* @param {string} [namespace] - The namespace for the key.
|
||||
* @returns {Promise<string | null>} Returns the value as string once it is modified or waitSeconds has elapsed; or null if the key does not exist.
|
||||
* */
|
||||
onChange<T>(
|
||||
key: string,
|
||||
waitSeconds: number,
|
||||
namespace?: string,
|
||||
classConstructor?: () => ClassConstructor<T>,
|
||||
): Promise<T | null>;
|
||||
|
||||
/**
|
||||
* Gets a value asynchronously from the underlying cache.
|
||||
*
|
||||
* @param {string} key - The key for the value.
|
||||
* @param {string} [namespace] - The namespace for the key.
|
||||
* @returns {Promise<string | null>} - Returns the value as string or null if the key does not exist.
|
||||
* */
|
||||
|
||||
get<T>(
|
||||
key: string,
|
||||
namespace?: string,
|
||||
classConstructor?: () => ClassConstructor<T>,
|
||||
): Promise<T | null>;
|
||||
|
||||
/**
|
||||
* Sets a value asynchronously in the underlying cache.
|
||||
*
|
||||
* @param {string} key - The key for the value.
|
||||
* @param {string} value - The value to set.
|
||||
* @param {string} [namespace] - The namespace for the key.
|
||||
* @param {number} [expireSeconds] - The number of seconds after which the key should expire.
|
||||
* @returns {Promise<boolean>} - Returns true if the value was set successfully.
|
||||
* */
|
||||
set(key: string, value: string, namespace?: string, expireSeconds?: number): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Sets a value asynchronously in the underlying cache if it doesn't exist. Returns false if the key already exists.
|
||||
*
|
||||
* @param {string} key - The key for the value.
|
||||
* @param {string} value - The value to set.
|
||||
* @param {string} [namespace] - The namespace for the key.
|
||||
* @param {number} [expireSeconds] - The number of seconds after which the key should expire.
|
||||
* @returns {Promise<boolean>} - Returns true if the value was set successfully.
|
||||
* */
|
||||
setIfNotExist(
|
||||
key: string,
|
||||
value: string,
|
||||
namespace?: string,
|
||||
expireSeconds?: number,
|
||||
): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Updates the expiration of a key without modifying its value. Returns false if the key does not exist.
|
||||
*
|
||||
* @param {string} key - The key to update.
|
||||
* @param {number} expireSeconds - The number of seconds from now after which the key should expire.
|
||||
* @param {string} [namespace] - The namespace for the key.
|
||||
* @returns {Promise<boolean>} - Returns true if the expiration was updated successfully, false if the key does not exist.
|
||||
* */
|
||||
updateExpiration(key: string, expireSeconds: number, namespace?: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Pings the cache to check if it is responsive, for health checks. Implementers should ensure this method is lightweight and does not cause significant delay.
|
||||
*
|
||||
* @returns {Promise<void>} - Resolves if the cache is responsive, rejects if it is not.
|
||||
*/
|
||||
ping(): Promise<void>;
|
||||
}
|
||||
53
tools/citrineos-core-main/packages/base/src/interfaces/cache/types.ts
vendored
Normal file
53
tools/citrineos-core-main/packages/base/src/interfaces/cache/types.ts
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID } from '@config/defineConfig.js';
|
||||
|
||||
/**
|
||||
* Cache namespace, used for grouping cache entries
|
||||
*/
|
||||
export enum CacheNamespace {
|
||||
CentralSystem = 'csms',
|
||||
ChargingStation = 'cs',
|
||||
TenantPathMapping = 'tpm',
|
||||
Transactions = 'tx',
|
||||
Connections = 'conn',
|
||||
Protocol = 'prtcl',
|
||||
Other = 'other',
|
||||
}
|
||||
|
||||
export const PATH_DELIMITER = ':';
|
||||
export const getCacheTenantPathMappingKey = (serverId: string, path: string): string => {
|
||||
return serverId + PATH_DELIMITER + path;
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper methods to create a unique identifier used in the cache and queues.
|
||||
* This is usually a combination between the tenantId and the ocppConnectionName.
|
||||
*/
|
||||
export const IDENTIFIER_DELIMITER = ':';
|
||||
export const createIdentifier = (tenantId: number, ...args: any[]): string =>
|
||||
[tenantId, ...(args ?? [])].join(IDENTIFIER_DELIMITER);
|
||||
export const getTenantIdFromIdentifier = (identifier: string): number => {
|
||||
const identifierSplit = identifier.split(IDENTIFIER_DELIMITER);
|
||||
return identifierSplit?.[0] ? Number(identifierSplit?.[0]) : DEFAULT_TENANT_ID;
|
||||
};
|
||||
export const getStationIdFromIdentifier = (identifier: string): string => {
|
||||
const identifierSplit = identifier.split(IDENTIFIER_DELIMITER);
|
||||
return identifierSplit?.[1] ?? identifier;
|
||||
};
|
||||
|
||||
/**
|
||||
* Used in the Connections Namespace as the value, to represent a websocket connection
|
||||
* Is stringified from JSON when stored in the cache
|
||||
*/
|
||||
export interface IWebsocketConnection {
|
||||
id: string;
|
||||
/**
|
||||
* Stored as ISO string in the cache, converted to Date when retrieved
|
||||
*/
|
||||
timeConnected: string;
|
||||
protocol: string;
|
||||
allowUnknownChargingStations: boolean;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { TenantPartnerSchema } from './tenant.partner.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { AsyncJobNameSchema } from './types/enums.js';
|
||||
|
||||
export const PaginatedParamsSchema = z.object({
|
||||
offset: z.number().int().optional(),
|
||||
limit: z.number().int().optional(),
|
||||
dateFrom: z.date().optional(),
|
||||
dateTo: z.date().optional(),
|
||||
});
|
||||
|
||||
export type PaginatedParams = z.infer<typeof PaginatedParamsSchema>;
|
||||
|
||||
export const AsyncJobSchema = BaseSchema.extend({
|
||||
jobId: z.string().uuid(),
|
||||
jobName: AsyncJobNameSchema,
|
||||
tenantPartnerId: z.number().int(),
|
||||
tenantPartner: TenantPartnerSchema.optional(),
|
||||
finishedAt: z.date().optional(),
|
||||
stoppedAt: z.date().nullable().optional(),
|
||||
stopScheduled: z.boolean().default(false),
|
||||
isFailed: z.boolean().default(false),
|
||||
paginatedParams: PaginatedParamsSchema,
|
||||
totalObjects: z.number().int().optional(),
|
||||
});
|
||||
|
||||
export const AsyncJobProps = AsyncJobSchema.keyof().enum;
|
||||
|
||||
export type AsyncJobDto = z.infer<typeof AsyncJobSchema>;
|
||||
|
||||
export const AsyncJobCreateSchema = AsyncJobSchema.omit({
|
||||
jobId: true,
|
||||
tenant: true,
|
||||
tenantPartner: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type AsyncJobCreate = z.infer<typeof AsyncJobCreateSchema>;
|
||||
|
||||
export const AsyncJobRequestSchema = z.object({
|
||||
tenantPartnerId: z.number().int(),
|
||||
paginatedParams: PaginatedParamsSchema,
|
||||
});
|
||||
|
||||
export type AsyncJobRequest = z.infer<typeof AsyncJobRequestSchema>;
|
||||
|
||||
export const asyncJobSchemas = {
|
||||
AsyncJob: AsyncJobSchema,
|
||||
AsyncJobCreate: AsyncJobCreateSchema,
|
||||
AsyncJobRequest: AsyncJobRequestSchema,
|
||||
};
|
||||
|
||||
const asyncJob: AsyncJobDto = {
|
||||
tenantId: 1,
|
||||
jobId: 'some-uuid',
|
||||
jobName: 'FETCH_OCPI_TOKENS',
|
||||
tenantPartnerId: 1,
|
||||
finishedAt: new Date(),
|
||||
stoppedAt: null,
|
||||
stopScheduled: false,
|
||||
isFailed: false,
|
||||
paginatedParams: {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
},
|
||||
totalObjects: 100,
|
||||
};
|
||||
|
||||
asyncJob.jobId = 'another-uuid';
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod/v4';
|
||||
import { TenantPartnerSchema } from './tenant.partner.dto.js';
|
||||
import { AdditionalInfoSchema, RealTimeAuthLastAttemptSchema } from './types/authorization.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import {
|
||||
AuthorizationStatusEnumSchema,
|
||||
AuthorizationWhitelistEnumSchema,
|
||||
IdTokenEnumSchema,
|
||||
} from './types/enums.js';
|
||||
|
||||
const authorizationFields = {
|
||||
id: z.number().int().optional(),
|
||||
allowedConnectorTypes: z.array(z.string()).optional(),
|
||||
disallowedEvseIdPrefixes: z.array(z.string()).optional(),
|
||||
idToken: z.string(),
|
||||
idTokenType: IdTokenEnumSchema.nullable().optional(),
|
||||
additionalInfo: z.tuple([AdditionalInfoSchema]).rest(AdditionalInfoSchema).nullable().optional(),
|
||||
status: AuthorizationStatusEnumSchema,
|
||||
cacheExpiryDateTime: z.iso.datetime().nullable().optional(),
|
||||
chargingPriority: z.number().int().nullable().optional(),
|
||||
language1: z.string().nullable().optional(),
|
||||
language2: z.string().nullable().optional(),
|
||||
personalMessage: z.any().nullable().optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
concurrentTransaction: z.boolean().optional(),
|
||||
isPrepaid: z.boolean().optional(),
|
||||
prepaidBalance: z.number().nullable().optional(),
|
||||
realTimeAuth: AuthorizationWhitelistEnumSchema.nullable().optional(),
|
||||
realTimeAuthLastAttempt: RealTimeAuthLastAttemptSchema.nullable().optional(),
|
||||
realTimeAuthTimeout: z.number().int().nullable().optional(),
|
||||
realTimeAuthUrl: z.string().optional(),
|
||||
tenantPartnerId: z.number().int().nullable().optional(),
|
||||
tenantPartner: TenantPartnerSchema.nullable().optional(),
|
||||
groupAuthorizationId: z.number().int().nullable().optional(),
|
||||
tariffId: z.number().int().nullable().optional(),
|
||||
};
|
||||
|
||||
export const GroupAuthorizationSchema = BaseSchema.extend(authorizationFields);
|
||||
|
||||
export type GroupAuthorizationDto = z.infer<typeof GroupAuthorizationSchema>;
|
||||
|
||||
export const AuthorizationSchema = BaseSchema.extend({
|
||||
...authorizationFields,
|
||||
groupAuthorizationId: z.number().int().nullable().optional(),
|
||||
groupAuthorization: z.lazy(() => GroupAuthorizationSchema).optional(),
|
||||
});
|
||||
|
||||
export const AuthorizationProps = AuthorizationSchema.keyof().enum;
|
||||
|
||||
export type AuthorizationDto = z.infer<typeof AuthorizationSchema>;
|
||||
|
||||
export const AuthorizationCreateSchema = AuthorizationSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
groupAuthorization: true,
|
||||
tenantPartner: true,
|
||||
});
|
||||
|
||||
export type AuthorizationCreate = z.infer<typeof AuthorizationCreateSchema>;
|
||||
|
||||
export const AuthorizationUpdateSchema = AuthorizationSchema.partial()
|
||||
.omit({
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
groupAuthorization: true,
|
||||
tenantPartner: true,
|
||||
})
|
||||
.required({ id: true, tenantId: true });
|
||||
|
||||
export type AuthorizationUpdate = z.infer<typeof AuthorizationUpdateSchema>;
|
||||
|
||||
export const authorizationSchemas = {
|
||||
Authorization: AuthorizationSchema,
|
||||
AuthorizationCreate: AuthorizationCreateSchema,
|
||||
AuthorizationUpdate: AuthorizationUpdateSchema,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { VariableAttributeSchema } from './variable.attribute.dto.js';
|
||||
|
||||
export const BootSchema = BaseSchema.extend({
|
||||
id: z.string(),
|
||||
lastBootTime: z.iso.datetime().nullable().optional(),
|
||||
heartbeatInterval: z.number().int().nullable().optional(),
|
||||
bootRetryInterval: z.number().int().nullable().optional(),
|
||||
status: z.any(),
|
||||
statusInfo: z.record(z.string(), z.any()).nullable().optional(), // JSONB
|
||||
getBaseReportOnPending: z.boolean().nullable().optional(),
|
||||
pendingBootSetVariables: z.array(VariableAttributeSchema).optional(),
|
||||
variablesRejectedOnLastBoot: z.array(z.record(z.string(), z.any())).nullable().optional(),
|
||||
bootWithRejectedVariables: z.boolean().nullable().optional(),
|
||||
changeConfigurationsOnPending: z.boolean().nullable().optional(),
|
||||
getConfigurationsOnPending: z.boolean().nullable().optional(),
|
||||
});
|
||||
|
||||
export const BootProps = BootSchema.keyof().enum;
|
||||
|
||||
export type BootDto = z.infer<typeof BootSchema>;
|
||||
|
||||
export const BootCreateSchema = BootSchema.omit({
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
pendingBootSetVariables: true,
|
||||
});
|
||||
|
||||
export type BootCreate = z.infer<typeof BootCreateSchema>;
|
||||
|
||||
export const BootUpdateSchema = BootSchema.partial()
|
||||
.omit({
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
pendingBootSetVariables: true,
|
||||
})
|
||||
.required({ id: true, tenantId: true });
|
||||
|
||||
export type BootUpdate = z.infer<typeof BootUpdateSchema>;
|
||||
|
||||
export const bootSchemas = {
|
||||
Boot: BootSchema,
|
||||
BootCreate: BootCreateSchema,
|
||||
BootUpdate: BootUpdateSchema,
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const SignatureAlgorithmSchema = z.enum(['SHA256withRSA', 'SHA256withECDSA']);
|
||||
export const CountryNameSchema = z.enum(['US']);
|
||||
|
||||
export type SignatureAlgorithm = z.infer<typeof SignatureAlgorithmSchema>;
|
||||
export type CountryName = z.infer<typeof CountryNameSchema>;
|
||||
|
||||
export const CertificateSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
serialNumber: z.number().int(), // BIGINT in DB
|
||||
issuerName: z.string(),
|
||||
organizationName: z.string(),
|
||||
commonName: z.string(),
|
||||
keyLength: z.number().int().nullable().optional(),
|
||||
validBefore: z.iso.datetime().nullable().optional(),
|
||||
signatureAlgorithm: SignatureAlgorithmSchema.nullable().optional(),
|
||||
countryName: CountryNameSchema.nullable().optional(),
|
||||
isCA: z.boolean().optional(),
|
||||
pathLen: z.number().int().nullable().optional(),
|
||||
certificateFileId: z.string().nullable().optional(),
|
||||
certificateFileHash: z.string().nullable().optional(),
|
||||
privateKeyFileId: z.string().nullable().optional(),
|
||||
signedBy: z.number().int().nullable().optional(),
|
||||
});
|
||||
|
||||
export const CertificateProps = CertificateSchema.keyof().enum;
|
||||
|
||||
export type CertificateDto = z.infer<typeof CertificateSchema>;
|
||||
|
||||
export const CertificateCreateSchema = CertificateSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type CertificateCreate = z.infer<typeof CertificateCreateSchema>;
|
||||
|
||||
export const certificateSchemas = {
|
||||
Certificate: CertificateSchema,
|
||||
CertificateCreate: CertificateCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const ChangeConfigurationSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
key: z.string(),
|
||||
value: z.string().nullable().optional(),
|
||||
readonly: z.boolean().nullable().optional(),
|
||||
});
|
||||
|
||||
export const ChangeConfigurationProps = ChangeConfigurationSchema.keyof().enum;
|
||||
|
||||
export type ChangeConfigurationDto = z.infer<typeof ChangeConfigurationSchema>;
|
||||
|
||||
export const ChangeConfigurationCreateSchema = ChangeConfigurationSchema.omit({
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ChangeConfigurationCreate = z.infer<typeof ChangeConfigurationCreateSchema>;
|
||||
|
||||
export const changeConfigurationSchemas = {
|
||||
ChangeConfiguration: ChangeConfigurationSchema,
|
||||
ChangeConfigurationCreate: ChangeConfigurationCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import {
|
||||
ACChargingParametersSchema,
|
||||
DCChargingParametersSchema,
|
||||
} from './types/charging.parameters.js';
|
||||
import { EnergyTransferModeEnumSchema } from './types/enums.js';
|
||||
|
||||
export const ChargingNeedsSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
acChargingParameters: ACChargingParametersSchema.nullable().optional(),
|
||||
dcChargingParameters: DCChargingParametersSchema.nullable().optional(),
|
||||
departureTime: z.iso.datetime().nullable().optional(),
|
||||
requestedEnergyTransfer: EnergyTransferModeEnumSchema,
|
||||
maxScheduleTuples: z.number().int().nullable().optional(),
|
||||
evseId: z.number().int(),
|
||||
transactionDatabaseId: z.number().int(),
|
||||
});
|
||||
|
||||
export const ChargingNeedsProps = ChargingNeedsSchema.keyof().enum;
|
||||
|
||||
export type ChargingNeedsDto = z.infer<typeof ChargingNeedsSchema>;
|
||||
|
||||
export const ChargingNeedsCreateSchema = ChargingNeedsSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ChargingNeedsCreate = z.infer<typeof ChargingNeedsCreateSchema>;
|
||||
|
||||
export const chargingNeedsSchemas = {
|
||||
ChargingNeeds: ChargingNeedsSchema,
|
||||
ChargingNeedsCreate: ChargingNeedsCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ChargingScheduleSchema } from './charging.schedule.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import {
|
||||
ChargingLimitSourceEnumSchema,
|
||||
ChargingProfileKindEnumSchema,
|
||||
ChargingProfilePurposeEnumSchema,
|
||||
RecurrencyKindEnumSchema,
|
||||
} from './types/enums.js';
|
||||
|
||||
export const ChargingProfileSchema = BaseSchema.extend({
|
||||
databaseId: z.number().int(),
|
||||
ocppConnectionName: z.string(),
|
||||
id: z.number().int().optional(),
|
||||
chargingProfileKind: ChargingProfileKindEnumSchema,
|
||||
chargingProfilePurpose: ChargingProfilePurposeEnumSchema,
|
||||
recurrencyKind: RecurrencyKindEnumSchema.nullable().optional(),
|
||||
stackLevel: z.number().int(),
|
||||
validFrom: z.iso.datetime().nullable().optional(),
|
||||
validTo: z.iso.datetime().nullable().optional(),
|
||||
evseId: z.number().int().nullable().optional(),
|
||||
isActive: z.boolean().default(false),
|
||||
chargingLimitSource: ChargingLimitSourceEnumSchema.default('CSO').nullable().optional(),
|
||||
chargingSchedule: z.union([
|
||||
z.tuple([ChargingScheduleSchema]),
|
||||
z.tuple([ChargingScheduleSchema, ChargingScheduleSchema]),
|
||||
z.tuple([ChargingScheduleSchema, ChargingScheduleSchema, ChargingScheduleSchema]),
|
||||
]),
|
||||
transactionDatabaseId: z.number().int().nullable().optional(),
|
||||
});
|
||||
|
||||
export const ChargingProfileProps = ChargingProfileSchema.keyof().enum;
|
||||
|
||||
export type ChargingProfileDto = z.infer<typeof ChargingProfileSchema>;
|
||||
|
||||
export const ChargingProfileCreateSchema = ChargingProfileSchema.omit({
|
||||
databaseId: true,
|
||||
tenant: true,
|
||||
chargingSchedule: true,
|
||||
transaction: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ChargingProfileCreate = z.infer<typeof ChargingProfileCreateSchema>;
|
||||
|
||||
export const chargingProfileSchemas = {
|
||||
ChargingProfile: ChargingProfileSchema,
|
||||
ChargingProfileCreate: ChargingProfileCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { SalesTariffSchema } from './sales.tariff.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { ChargingRateUnitEnumSchema } from './types/enums.js';
|
||||
|
||||
export const ChargingScheduleSchema = BaseSchema.extend({
|
||||
databaseId: z.number().int(),
|
||||
id: z.number().int(),
|
||||
ocppConnectionName: z.string(),
|
||||
chargingRateUnit: ChargingRateUnitEnumSchema,
|
||||
chargingSchedulePeriod: z.tuple([z.any()]).rest(z.any()), // Non-empty array of JSONB
|
||||
duration: z.number().int().nullable().optional(),
|
||||
minChargingRate: z.number().nullable().optional(), // DECIMAL
|
||||
startSchedule: z.string().nullable().optional(),
|
||||
timeBase: z.iso.datetime().optional(),
|
||||
chargingProfileDatabaseId: z.number().int().optional(),
|
||||
salesTariff: SalesTariffSchema.optional(),
|
||||
});
|
||||
|
||||
export const ChargingScheduleProps = ChargingScheduleSchema.keyof().enum;
|
||||
|
||||
export type ChargingScheduleDto = z.infer<typeof ChargingScheduleSchema>;
|
||||
|
||||
export const ChargingScheduleCreateSchema = ChargingScheduleSchema.omit({
|
||||
databaseId: true,
|
||||
tenant: true,
|
||||
chargingProfile: true,
|
||||
salesTariff: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ChargingScheduleCreate = z.infer<typeof ChargingScheduleCreateSchema>;
|
||||
|
||||
export const chargingScheduleSchemas = {
|
||||
ChargingSchedule: ChargingScheduleSchema,
|
||||
ChargingScheduleCreate: ChargingScheduleCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ConnectorSchema } from './connector.dto.js';
|
||||
import { EvseSchema } from './evse.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import {
|
||||
ChargingStationCapabilitySchema,
|
||||
ChargingStationParkingRestrictionSchema,
|
||||
} from './types/enums.js';
|
||||
import { PointSchema } from './types/location.js';
|
||||
import { OCPPVersionSchema } from './types/ocpp.message.js';
|
||||
|
||||
export const ChargingStationSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string().max(36),
|
||||
isOnline: z.boolean(),
|
||||
protocol: OCPPVersionSchema.nullable().optional(),
|
||||
latestOcppMessageTimestamp: z.string().datetime().nullable().optional(),
|
||||
chargePointVendor: z.string().max(20).nullable().optional(),
|
||||
chargePointModel: z.string().max(20).nullable().optional(),
|
||||
chargePointSerialNumber: z.string().max(25).nullable().optional(),
|
||||
chargeBoxSerialNumber: z.string().max(25).nullable().optional(),
|
||||
firmwareVersion: z.string().max(50).nullable().optional(),
|
||||
iccid: z.string().max(20).nullable().optional(),
|
||||
imsi: z.string().max(20).nullable().optional(),
|
||||
meterType: z.string().max(25).nullable().optional(),
|
||||
meterSerialNumber: z.string().max(25).nullable().optional(),
|
||||
coordinates: PointSchema.nullable().optional(),
|
||||
floorLevel: z.string().nullable().optional(),
|
||||
parkingRestrictions: z.array(ChargingStationParkingRestrictionSchema).nullable().optional(),
|
||||
capabilities: z.array(ChargingStationCapabilitySchema).nullable().optional(),
|
||||
use16StatusNotification0: z.boolean().default(true).nullable().optional(),
|
||||
locationId: z.number().int().nullable().optional(),
|
||||
networkProfiles: z.any().optional(),
|
||||
evses: z.array(EvseSchema).nullable().optional(),
|
||||
connectors: z.array(ConnectorSchema).nullable().optional(),
|
||||
});
|
||||
|
||||
export const ChargingStationProps = ChargingStationSchema.keyof().enum;
|
||||
|
||||
export type ChargingStationDto = z.infer<typeof ChargingStationSchema>;
|
||||
|
||||
export const ChargingStationCreateSchema = ChargingStationSchema.omit({
|
||||
tenant: true,
|
||||
statusNotifications: true,
|
||||
transactions: true,
|
||||
location: true,
|
||||
networkProfiles: true,
|
||||
evses: true,
|
||||
connectors: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ChargingStationCreate = z.infer<typeof ChargingStationCreateSchema>;
|
||||
|
||||
// OCPI-specific validation (requires evses and connectors)
|
||||
export const ChargingStationOCPISchema = ChargingStationSchema.extend({
|
||||
evses: z.array(EvseSchema).min(1, 'OCPI requires at least one EVSE'),
|
||||
connectors: z.array(ConnectorSchema).min(1, 'OCPI requires at least one connector'),
|
||||
coordinates: PointSchema, // Required for OCPI (not nullable)
|
||||
});
|
||||
|
||||
export type ChargingStationOCPI = z.infer<typeof ChargingStationOCPISchema>;
|
||||
|
||||
export const chargingStationSchemas = {
|
||||
ChargingStation: ChargingStationSchema,
|
||||
ChargingStationCreate: ChargingStationCreateSchema,
|
||||
ChargingStationOCPI: ChargingStationOCPISchema,
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ServerNetworkProfileSchema } from './server.network.profile.dto.js';
|
||||
import { SetNetworkProfileSchema } from './set.network.profile.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const ChargingStationNetworkProfileSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
configurationSlot: z.number().int(),
|
||||
setNetworkProfileId: z.number().int(),
|
||||
setNetworkProfile: SetNetworkProfileSchema,
|
||||
websocketServerConfigId: z.string().optional(),
|
||||
websocketServerConfig: ServerNetworkProfileSchema.optional(),
|
||||
});
|
||||
|
||||
export const ChargingStationNetworkProfileProps = ChargingStationNetworkProfileSchema.keyof().enum;
|
||||
|
||||
export type ChargingStationNetworkProfileDto = z.infer<typeof ChargingStationNetworkProfileSchema>;
|
||||
|
||||
export const ChargingStationNetworkProfileCreateSchema = ChargingStationNetworkProfileSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
setNetworkProfile: true,
|
||||
websocketServerConfig: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ChargingStationNetworkProfileCreate = z.infer<
|
||||
typeof ChargingStationNetworkProfileCreateSchema
|
||||
>;
|
||||
|
||||
export const chargingStationNetworkProfileSchemas = {
|
||||
ChargingStationNetworkProfile: ChargingStationNetworkProfileSchema,
|
||||
ChargingStationNetworkProfileCreate: ChargingStationNetworkProfileCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const ChargingStationSecurityInfoSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
publicKeyFileId: z.string(),
|
||||
});
|
||||
|
||||
export const ChargingStationSecurityInfoProps = ChargingStationSecurityInfoSchema.keyof().enum;
|
||||
|
||||
export type ChargingStationSecurityInfoDto = z.infer<typeof ChargingStationSecurityInfoSchema>;
|
||||
|
||||
export const ChargingStationSecurityInfoCreateSchema = ChargingStationSecurityInfoSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ChargingStationSecurityInfoCreate = z.infer<
|
||||
typeof ChargingStationSecurityInfoCreateSchema
|
||||
>;
|
||||
|
||||
export const chargingStationSecurityInfoSchemas = {
|
||||
ChargingStationSecurityInfo: ChargingStationSecurityInfoSchema,
|
||||
ChargingStationSecurityInfoCreate: ChargingStationSecurityInfoCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ChargingStationSchema } from './charging.station.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { ChargingStationSequenceTypeSchema } from './types/enums.js';
|
||||
|
||||
export const ChargingStationSequenceSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string().max(36),
|
||||
type: ChargingStationSequenceTypeSchema,
|
||||
value: z.number().int().default(0), // BIGINT
|
||||
station: ChargingStationSchema.optional(),
|
||||
});
|
||||
|
||||
export const ChargingStationSequenceProps = ChargingStationSequenceSchema.keyof().enum;
|
||||
|
||||
export type ChargingStationSequenceDto = z.infer<typeof ChargingStationSequenceSchema>;
|
||||
|
||||
export const ChargingStationSequenceCreateSchema = ChargingStationSequenceSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
station: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ChargingStationSequenceCreate = z.infer<typeof ChargingStationSequenceCreateSchema>;
|
||||
|
||||
export const chargingStationSequenceSchemas = {
|
||||
ChargingStationSequence: ChargingStationSequenceSchema,
|
||||
ChargingStationSequenceCreate: ChargingStationSequenceCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { EvseTypeSchema } from './evse.type.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { VariableSchema } from './variable.dto.js';
|
||||
|
||||
export const ComponentSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
name: z.string(),
|
||||
instance: z.string().nullable().optional(),
|
||||
evse: EvseTypeSchema.optional(),
|
||||
evseDatabaseId: z.number().int().nullable().optional(),
|
||||
variables: z.array(VariableSchema).optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export const ComponentProps = ComponentSchema.keyof().enum;
|
||||
|
||||
export type ComponentDto = z.infer<typeof ComponentSchema>;
|
||||
|
||||
export const ComponentCreateSchema = ComponentSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
evse: true,
|
||||
variables: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ComponentCreate = z.infer<typeof ComponentCreateSchema>;
|
||||
|
||||
export const componentSchemas = {
|
||||
Component: ComponentSchema,
|
||||
ComponentCreate: ComponentCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const CompositeScheduleSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
evseId: z.number().int(),
|
||||
duration: z.number().int(),
|
||||
scheduleStart: z.iso.datetime(),
|
||||
chargingRateUnit: z.string(),
|
||||
chargingSchedulePeriod: z.tuple([z.any()]).rest(z.any()),
|
||||
});
|
||||
|
||||
export const CompositeScheduleProps = CompositeScheduleSchema.keyof().enum;
|
||||
|
||||
export type CompositeScheduleDto = z.infer<typeof CompositeScheduleSchema>;
|
||||
|
||||
export const CompositeScheduleCreateSchema = CompositeScheduleSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type CompositeScheduleCreate = z.infer<typeof CompositeScheduleCreateSchema>;
|
||||
|
||||
export const compositeScheduleSchemas = {
|
||||
CompositeSchedule: CompositeScheduleSchema,
|
||||
CompositeScheduleCreate: CompositeScheduleCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { TariffSchema } from './tariff.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import {
|
||||
ConnectorErrorCodeEnumSchema,
|
||||
ConnectorFormatEnumSchema,
|
||||
ConnectorPowerTypeEnumSchema,
|
||||
ConnectorStatusEnumSchema,
|
||||
ConnectorTypeEnumSchema,
|
||||
} from './types/enums.js';
|
||||
|
||||
export const ConnectorSchemaWithoutParent = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
evseId: z.number().int(),
|
||||
connectorId: z.number().int(),
|
||||
evseTypeConnectorId: z.number().int().optional(),
|
||||
status: ConnectorStatusEnumSchema.default('Unknown').nullable().optional(),
|
||||
type: ConnectorTypeEnumSchema.nullable().optional(),
|
||||
format: ConnectorFormatEnumSchema.nullable().optional(),
|
||||
errorCode: ConnectorErrorCodeEnumSchema.default('NoError').nullable().optional(),
|
||||
powerType: ConnectorPowerTypeEnumSchema.nullable().optional(),
|
||||
maximumAmperage: z.number().int().nullable().optional(),
|
||||
maximumVoltage: z.number().int().nullable().optional(),
|
||||
maximumPowerWatts: z.number().int().nullable().optional(),
|
||||
timestamp: z.iso.datetime(),
|
||||
info: z.string().nullable().optional(),
|
||||
vendorId: z.string().nullable().optional(),
|
||||
vendorErrorCode: z.string().nullable().optional(),
|
||||
termsAndConditionsUrl: z.string().nullable().optional(),
|
||||
tariff: TariffSchema.nullable().optional(),
|
||||
});
|
||||
|
||||
export const ConnectorSchema = ConnectorSchemaWithoutParent.extend({
|
||||
tenant: z.any().optional(),
|
||||
evse: z.any().optional(),
|
||||
chargingStation: z.any().optional(),
|
||||
});
|
||||
|
||||
export const ConnectorProps = ConnectorSchema.keyof().enum;
|
||||
|
||||
export type ConnectorDto = z.infer<typeof ConnectorSchema>;
|
||||
|
||||
export const ConnectorCreateSchema = ConnectorSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
evse: true,
|
||||
chargingStation: true,
|
||||
tariff: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ConnectorCreate = z.infer<typeof ConnectorCreateSchema>;
|
||||
|
||||
export const connectorSchemas = {
|
||||
Connector: ConnectorSchema,
|
||||
ConnectorCreate: ConnectorCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ComponentSchema } from './component.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { VariableSchema } from './variable.dto.js';
|
||||
import { EventNotificationEnumSchema, EventTriggerEnumSchema } from './types/enums.js';
|
||||
|
||||
export const EventDataSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
eventId: z.number().int(),
|
||||
trigger: EventTriggerEnumSchema,
|
||||
cause: z.number().int().nullable().optional(),
|
||||
timestamp: z.iso.datetime(),
|
||||
actualValue: z.string(),
|
||||
techCode: z.string().nullable().optional(),
|
||||
techInfo: z.string().nullable().optional(),
|
||||
cleared: z.boolean().nullable().optional(),
|
||||
transactionId: z.string().nullable().optional(),
|
||||
variableMonitoringId: z.number().int().nullable().optional(),
|
||||
eventNotificationType: EventNotificationEnumSchema,
|
||||
variable: VariableSchema,
|
||||
variableId: z.number().int().optional(),
|
||||
component: ComponentSchema,
|
||||
componentId: z.number().int().optional(),
|
||||
});
|
||||
|
||||
export const EventDataProps = EventDataSchema.keyof().enum;
|
||||
|
||||
export type EventDataDto = z.infer<typeof EventDataSchema>;
|
||||
|
||||
export const EventDataCreateSchema = EventDataSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
variable: true,
|
||||
component: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type EventDataCreate = z.infer<typeof EventDataCreateSchema>;
|
||||
|
||||
export const eventDataSchemas = {
|
||||
EventData: EventDataSchema,
|
||||
EventDataCreate: EventDataCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ConnectorSchemaWithoutParent } from './connector.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const EvseSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
evseTypeId: z.number().int().optional(),
|
||||
evseId: z.string(), // eMI3 compliant EVSE ID
|
||||
physicalReference: z.string().nullable().optional(),
|
||||
removed: z.boolean().optional(),
|
||||
connectors: z.array(ConnectorSchemaWithoutParent).nullable().optional(),
|
||||
});
|
||||
|
||||
export const EvseProps = EvseSchema.keyof().enum;
|
||||
|
||||
export type EvseDto = z.infer<typeof EvseSchema>;
|
||||
|
||||
export const EvseCreateSchema = EvseSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
chargingStation: true,
|
||||
connectors: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type EvseCreate = z.infer<typeof EvseCreateSchema>;
|
||||
|
||||
export const evseSchemas = {
|
||||
Evse: EvseSchema,
|
||||
EvseCreate: EvseCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const EvseTypeSchema = BaseSchema.extend({
|
||||
databaseId: z.number().int().optional(),
|
||||
id: z.number().int(),
|
||||
connectorId: z.number().int().nullable().optional(),
|
||||
});
|
||||
|
||||
export const EvseTypeProps = EvseTypeSchema.keyof().enum;
|
||||
|
||||
export type EvseTypeDto = z.infer<typeof EvseTypeSchema>;
|
||||
|
||||
export const EvseTypeCreateSchema = EvseTypeSchema.omit({
|
||||
databaseId: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type EvseTypeCreate = z.infer<typeof EvseTypeCreateSchema>;
|
||||
|
||||
export const evseTypeSchemas = {
|
||||
EvseType: EvseTypeSchema,
|
||||
EvseTypeCreate: EvseTypeCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { CertificateUseEnumSchema, HashAlgorithmEnumSchema } from './types/enums.js';
|
||||
|
||||
export const InstalledCertificateSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string().max(36),
|
||||
hashAlgorithm: HashAlgorithmEnumSchema,
|
||||
issuerNameHash: z.string().nullable().optional(),
|
||||
issuerKeyHash: z.string().nullable().optional(),
|
||||
serialNumber: z.string().nullable().optional(),
|
||||
certificateType: CertificateUseEnumSchema,
|
||||
});
|
||||
|
||||
export const InstalledCertificateProps = InstalledCertificateSchema.keyof().enum;
|
||||
|
||||
export type InstalledCertificateDto = z.infer<typeof InstalledCertificateSchema>;
|
||||
|
||||
export const InstalledCertificateCreateSchema = InstalledCertificateSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type InstalledCertificateCreate = z.infer<typeof InstalledCertificateCreateSchema>;
|
||||
|
||||
export const installedCertificateSchemas = {
|
||||
InstalledCertificate: InstalledCertificateSchema,
|
||||
InstalledCertificateCreate: InstalledCertificateCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { z } from 'zod';
|
||||
import { ChargingStationSchema } from './charging.station.dto.js';
|
||||
import { StatusNotificationSchema } from './status.notification.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const LatestStatusNotificationSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
statusNotificationId: z.string(),
|
||||
chargingStation: ChargingStationSchema.optional(),
|
||||
statusNotification: StatusNotificationSchema.optional(),
|
||||
});
|
||||
|
||||
export const LatestStatusNotificationProps = LatestStatusNotificationSchema.keyof().enum;
|
||||
|
||||
export type LatestStatusNotificationDto = z.infer<typeof LatestStatusNotificationSchema>;
|
||||
|
||||
export const LatestStatusNotificationCreateSchema = LatestStatusNotificationSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
chargingStation: true,
|
||||
statusNotification: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type LatestStatusNotificationCreate = z.infer<typeof LatestStatusNotificationCreateSchema>;
|
||||
|
||||
export const latestStatusNotificationSchemas = {
|
||||
LatestStatusNotification: LatestStatusNotificationSchema,
|
||||
LatestStatusNotificationCreate: LatestStatusNotificationCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod/v4';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { AuthorizationSchema } from './authorization.dto.js';
|
||||
|
||||
export const LocalListAuthorizationSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
allowedConnectorTypes: z.array(z.string()).optional(),
|
||||
disallowedEvseIdPrefixes: z.array(z.string()).optional(),
|
||||
idToken: z.string(),
|
||||
idTokenType: z.string().nullable().optional(),
|
||||
additionalInfo: z.any().nullable().optional(), // JSONB
|
||||
status: z.string(),
|
||||
cacheExpiryDateTime: z.iso.datetime().nullable().optional(),
|
||||
chargingPriority: z.number().int().nullable().optional(),
|
||||
language1: z.string().nullable().optional(),
|
||||
language2: z.string().nullable().optional(),
|
||||
personalMessage: z.any().nullable().optional(),
|
||||
groupAuthorizationId: z.number().int().nullable().optional(),
|
||||
groupAuthorization: z.lazy(() => AuthorizationSchema).optional(),
|
||||
authorizationId: z.number().int().optional(),
|
||||
authorization: z.lazy(() => AuthorizationSchema).optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export type LocalListAuthorizationDto = z.infer<typeof LocalListAuthorizationSchema>;
|
||||
|
||||
export const LocalListAuthorizationCreateSchema = LocalListAuthorizationSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
groupAuthorization: true,
|
||||
authorization: true,
|
||||
sendLocalLists: true,
|
||||
localListVersions: true,
|
||||
});
|
||||
|
||||
export const localListAuthorizationSchemas = {
|
||||
LocalListAuthorization: LocalListAuthorizationSchema,
|
||||
LocalListAuthorizationCreate: LocalListAuthorizationCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod/v4';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { LocalListAuthorizationSchema } from './local.list.authorization.dto.js';
|
||||
|
||||
export const LocalListVersionSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
versionNumber: z.number().int(),
|
||||
localAuthorizationList: z
|
||||
.array(z.lazy(() => LocalListAuthorizationSchema))
|
||||
.nonempty()
|
||||
.nullable()
|
||||
.optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export type LocalListVersionDto = z.infer<typeof LocalListVersionSchema>;
|
||||
export const LocalListVersionCreateSchema = LocalListVersionSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
localAuthorizationList: true,
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ChargingStationSchema } from './charging.station.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { LocationFacilityEnumSchema, LocationParkingEnumSchema } from './types/enums.js';
|
||||
import { LocationHoursSchema, PointSchema } from './types/location.js';
|
||||
|
||||
export const LocationSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
name: z.string(),
|
||||
address: z.string(),
|
||||
city: z.string(),
|
||||
postalCode: z.string(),
|
||||
state: z.string(),
|
||||
country: z.string(),
|
||||
publishUpstream: z.boolean().default(true),
|
||||
timeZone: z.string().default('UTC'),
|
||||
parkingType: LocationParkingEnumSchema.nullable().optional(),
|
||||
facilities: z.array(LocationFacilityEnumSchema).nullable().optional(),
|
||||
openingHours: LocationHoursSchema.nullable().optional(),
|
||||
coordinates: PointSchema,
|
||||
chargingPool: z.array(ChargingStationSchema).nullable().optional(),
|
||||
});
|
||||
|
||||
export const LocationProps = LocationSchema.keyof().enum;
|
||||
|
||||
export type LocationDto = z.infer<typeof LocationSchema>;
|
||||
|
||||
export const LocationCreateSchema = LocationSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
chargingPool: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type LocationCreate = z.infer<typeof LocationCreateSchema>;
|
||||
|
||||
export const locationSchemas = {
|
||||
Location: LocationSchema,
|
||||
LocationCreate: LocationCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ComponentSchema } from './component.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { MessagePriorityEnumSchema } from './types/enums.js';
|
||||
import { MessageStateEnumSchema } from './types/enums.js';
|
||||
import { MessageContentSchema } from './types/message.info.js';
|
||||
|
||||
export const MessageInfoSchema = BaseSchema.extend({
|
||||
databaseId: z.number().int(),
|
||||
ocppConnectionName: z.string(),
|
||||
id: z.number().int(),
|
||||
priority: MessagePriorityEnumSchema,
|
||||
state: MessageStateEnumSchema.nullable().optional(),
|
||||
startDateTime: z.iso.datetime().nullable().optional(),
|
||||
endDateTime: z.iso.datetime().nullable().optional(),
|
||||
transactionId: z.string().nullable().optional(),
|
||||
message: MessageContentSchema,
|
||||
active: z.boolean(),
|
||||
display: ComponentSchema,
|
||||
displayComponentId: z.number().int().nullable().optional(),
|
||||
});
|
||||
|
||||
export const MessageInfoProps = MessageInfoSchema.keyof().enum;
|
||||
|
||||
export type MessageInfoDto = z.infer<typeof MessageInfoSchema>;
|
||||
|
||||
export const MessageInfoCreateSchema = MessageInfoSchema.omit({
|
||||
databaseId: true,
|
||||
tenant: true,
|
||||
display: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type MessageInfoCreate = z.infer<typeof MessageInfoCreateSchema>;
|
||||
|
||||
export const messageInfoSchemas = {
|
||||
MessageInfo: MessageInfoSchema,
|
||||
MessageInfoCreate: MessageInfoCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { SampledValueSchema } from './types/sampled.value.dto.js';
|
||||
|
||||
export const MeterValueSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
transactionEventId: z.number().int().nullable().optional(),
|
||||
transactionDatabaseId: z.number().int().nullable().optional(),
|
||||
sampledValue: z.tuple([SampledValueSchema]).rest(SampledValueSchema),
|
||||
timestamp: z.iso.datetime(),
|
||||
connectorId: z.number().int().optional(),
|
||||
tariffId: z.number().int().nullable().optional(),
|
||||
transactionId: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export const MeterValueProps = MeterValueSchema.keyof().enum;
|
||||
|
||||
export type MeterValueDto = z.infer<typeof MeterValueSchema>;
|
||||
|
||||
export const MeterValueCreateSchema = MeterValueSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type MeterValueCreate = z.infer<typeof MeterValueCreateSchema>;
|
||||
|
||||
export const meterValueSchemas = {
|
||||
MeterValue: MeterValueSchema,
|
||||
MeterValueCreate: MeterValueCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import {
|
||||
MessageOriginSchema,
|
||||
MessageStateSchema,
|
||||
OCPPVersionSchema,
|
||||
} from './types/ocpp.message.js';
|
||||
|
||||
export const OCPPMessageWithoutRequestResponseSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
stationId: z.number().int().optional(),
|
||||
correlationId: z.string().optional(),
|
||||
origin: MessageOriginSchema,
|
||||
state: MessageStateSchema,
|
||||
protocol: OCPPVersionSchema,
|
||||
action: z.string(),
|
||||
message: z.any(), // JSONB
|
||||
timestamp: z.iso.datetime(),
|
||||
});
|
||||
|
||||
const OCPPMessageSchema = OCPPMessageWithoutRequestResponseSchema.extend({
|
||||
requestMessageId: z.number().int().optional(),
|
||||
requestMessage: OCPPMessageWithoutRequestResponseSchema.optional(),
|
||||
responseMessages: OCPPMessageWithoutRequestResponseSchema.array().optional(),
|
||||
});
|
||||
|
||||
export const OCPPMessageProps = OCPPMessageSchema.keyof().enum;
|
||||
|
||||
export type OCPPMessageDto = z.infer<typeof OCPPMessageSchema>;
|
||||
|
||||
export const OCPPMessageCreateSchema = OCPPMessageSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type OCPPMessageCreate = z.infer<typeof OCPPMessageCreateSchema>;
|
||||
|
||||
export const ocppMessageSchemas = {
|
||||
OCPPMessage: OCPPMessageSchema,
|
||||
OCPPMessageCreate: OCPPMessageCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { EvseTypeSchema } from './evse.type.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const ReservationSchema = BaseSchema.extend({
|
||||
databaseId: z.number().int(),
|
||||
id: z.number().int(),
|
||||
ocppConnectionName: z.string(),
|
||||
expiryDateTime: z.iso.datetime(),
|
||||
connectorType: z.string().nullable().optional(),
|
||||
reserveStatus: z.string().nullable().optional(),
|
||||
isActive: z.boolean().default(false),
|
||||
terminatedByTransaction: z.string().nullable().optional(),
|
||||
idToken: z.record(z.string(), z.any()),
|
||||
groupIdToken: z.record(z.string(), z.any()).nullable().optional(),
|
||||
evseId: z.number().int().nullable().optional(),
|
||||
evse: EvseTypeSchema.nullable().optional(),
|
||||
});
|
||||
|
||||
export const ReservationProps = ReservationSchema.keyof().enum;
|
||||
|
||||
export type ReservationDto = z.infer<typeof ReservationSchema>;
|
||||
|
||||
export const ReservationCreateSchema = ReservationSchema.omit({
|
||||
databaseId: true,
|
||||
tenant: true,
|
||||
evse: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ReservationCreate = z.infer<typeof ReservationCreateSchema>;
|
||||
|
||||
export const reservationSchemas = {
|
||||
Reservation: ReservationSchema,
|
||||
ReservationCreate: ReservationCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { SalesTariffEntrySchema } from './types/sales.tariff.js';
|
||||
|
||||
export const SalesTariffSchema = BaseSchema.extend({
|
||||
databaseId: z.number().int(),
|
||||
id: z.number().int(),
|
||||
numEPriceLevels: z.number().int().nullable().optional(),
|
||||
salesTariffDescription: z.string().nullable().optional(),
|
||||
salesTariffEntry: z.tuple([SalesTariffEntrySchema]).rest(SalesTariffEntrySchema), // Non-empty array
|
||||
chargingScheduleDatabaseId: z.number().int(),
|
||||
});
|
||||
|
||||
export const SalesTariffProps = SalesTariffSchema.keyof().enum;
|
||||
|
||||
export type SalesTariffDto = z.infer<typeof SalesTariffSchema>;
|
||||
|
||||
export const SalesTariffCreateSchema = SalesTariffSchema.omit({
|
||||
databaseId: true,
|
||||
tenant: true,
|
||||
chargingSchedule: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type SalesTariffCreate = z.infer<typeof SalesTariffCreateSchema>;
|
||||
|
||||
export const salesTariffSchemas = {
|
||||
SalesTariff: SalesTariffSchema,
|
||||
SalesTariffCreate: SalesTariffCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const SecurityEventSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
type: z.string(),
|
||||
timestamp: z.iso.datetime(),
|
||||
techInfo: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export const SecurityEventProps = SecurityEventSchema.keyof().enum;
|
||||
|
||||
export type SecurityEventDto = z.infer<typeof SecurityEventSchema>;
|
||||
|
||||
export const SecurityEventCreateSchema = SecurityEventSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type SecurityEventCreate = z.infer<typeof SecurityEventCreateSchema>;
|
||||
|
||||
export const securityEventSchemas = {
|
||||
SecurityEvent: SecurityEventSchema,
|
||||
SecurityEventCreate: SecurityEventCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod/v4';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { LocalListAuthorizationSchema } from './local.list.authorization.dto.js';
|
||||
|
||||
export const SendLocalListSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
correlationId: z.string(),
|
||||
versionNumber: z.number().int(),
|
||||
updateType: z.string(),
|
||||
localAuthorizationList: z
|
||||
.array(z.lazy(() => LocalListAuthorizationSchema))
|
||||
.nullable()
|
||||
.optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export type SendLocalListDto = z.infer<typeof SendLocalListSchema>;
|
||||
|
||||
export const SendLocalListCreateSchema = SendLocalListSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
localAuthorizationList: true,
|
||||
});
|
||||
|
||||
export const sendLocalListSchemas = {
|
||||
SendLocalList: SendLocalListSchema,
|
||||
SendLocalListCreate: SendLocalListCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ChargingStationSchema } from './charging.station.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const ServerNetworkProfileSchema = BaseSchema.extend({
|
||||
id: z.string(),
|
||||
host: z.string(),
|
||||
port: z.number().int(),
|
||||
pingInterval: z.number().int(),
|
||||
protocols: z.array(z.string()), // OCPPVersionType[]
|
||||
messageTimeout: z.number().int(),
|
||||
securityProfile: z.number().int(),
|
||||
allowUnknownChargingStations: z.boolean(),
|
||||
tlsKeyFilePath: z.string().optional(),
|
||||
tlsCertificateChainFilePath: z.string().optional(),
|
||||
mtlsCertificateAuthorityKeyFilePath: z.string().optional(),
|
||||
rootCACertificateFilePath: z.string().optional(),
|
||||
chargingStations: z.array(ChargingStationSchema).nullable().optional(),
|
||||
});
|
||||
|
||||
export const ServerNetworkProfileProps = ServerNetworkProfileSchema.keyof().enum;
|
||||
|
||||
export type ServerNetworkProfileDto = z.infer<typeof ServerNetworkProfileSchema>;
|
||||
|
||||
export const ServerNetworkProfileCreateSchema = ServerNetworkProfileSchema.omit({
|
||||
tenant: true,
|
||||
chargingStations: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type ServerNetworkProfileCreate = z.infer<typeof ServerNetworkProfileCreateSchema>;
|
||||
|
||||
export const serverNetworkProfileSchemas = {
|
||||
ServerNetworkProfile: ServerNetworkProfileSchema,
|
||||
ServerNetworkProfileCreate: ServerNetworkProfileCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ServerNetworkProfileSchema } from './server.network.profile.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import {
|
||||
OCPPInterfaceEnumSchema,
|
||||
OCPPTransportEnumSchema,
|
||||
OCPPVersionEnumSchema,
|
||||
} from './types/enums.js';
|
||||
|
||||
export const SetNetworkProfileSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
correlationId: z.string(),
|
||||
websocketServerConfigId: z.string().optional(),
|
||||
websocketServerConfig: ServerNetworkProfileSchema.optional(),
|
||||
configurationSlot: z.number().int(),
|
||||
ocppVersion: OCPPVersionEnumSchema,
|
||||
ocppTransport: OCPPTransportEnumSchema,
|
||||
ocppCsmsUrl: z.string(),
|
||||
messageTimeout: z.number().int(),
|
||||
securityProfile: z.number().int(),
|
||||
ocppInterface: OCPPInterfaceEnumSchema,
|
||||
apn: z.string().optional(), // Stringified JSON
|
||||
vpn: z.string().optional(), // Stringified JSON
|
||||
});
|
||||
|
||||
export const SetNetworkProfileProps = SetNetworkProfileSchema.keyof().enum;
|
||||
|
||||
export type SetNetworkProfileDto = z.infer<typeof SetNetworkProfileSchema>;
|
||||
|
||||
export const SetNetworkProfileCreateSchema = SetNetworkProfileSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
websocketServerConfig: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type SetNetworkProfileCreate = z.infer<typeof SetNetworkProfileCreateSchema>;
|
||||
|
||||
export const setNetworkProfileSchemas = {
|
||||
SetNetworkProfile: SetNetworkProfileSchema,
|
||||
SetNetworkProfileCreate: SetNetworkProfileCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ConnectorSchema } from './connector.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const StartTransactionSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
meterStart: z.number().int(), // Wh
|
||||
timestamp: z.iso.datetime(),
|
||||
reservationId: z.number().int().nullable().optional(),
|
||||
transactionDatabaseId: z.number().int(),
|
||||
connectorDatabaseId: z.number().int(),
|
||||
connector: ConnectorSchema.optional(),
|
||||
});
|
||||
|
||||
export const StartTransactionProps = StartTransactionSchema.keyof().enum;
|
||||
|
||||
export type StartTransactionDto = z.infer<typeof StartTransactionSchema>;
|
||||
|
||||
export const StartTransactionCreateSchema = StartTransactionSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
connector: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type StartTransactionCreate = z.infer<typeof StartTransactionCreateSchema>;
|
||||
|
||||
export const startTransactionSchemas = {
|
||||
StartTransaction: StartTransactionSchema,
|
||||
StartTransactionCreate: StartTransactionCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ChargingStationSchema } from './charging.station.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { ConnectorStatusEnumSchema } from './types/enums.js';
|
||||
|
||||
export const StatusNotificationSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
timestamp: z.iso.datetime().nullable().optional(),
|
||||
connectorStatus: ConnectorStatusEnumSchema,
|
||||
evseId: z.number().int().nullable().optional(),
|
||||
connectorId: z.number().int(),
|
||||
errorCode: z.string().nullable().optional(),
|
||||
info: z.string().nullable().optional(),
|
||||
vendorId: z.string().nullable().optional(),
|
||||
vendorErrorCode: z.string().nullable().optional(),
|
||||
chargingStation: ChargingStationSchema.optional(),
|
||||
});
|
||||
|
||||
export const StatusNotificationProps = StatusNotificationSchema.keyof().enum;
|
||||
|
||||
export type StatusNotificationDto = z.infer<typeof StatusNotificationSchema>;
|
||||
|
||||
export const StatusNotificationCreateSchema = StatusNotificationSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
chargingStation: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type StatusNotificationCreate = z.infer<typeof StatusNotificationCreateSchema>;
|
||||
|
||||
export const statusNotificationSchemas = {
|
||||
StatusNotification: StatusNotificationSchema,
|
||||
StatusNotificationCreate: StatusNotificationCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { MeterValueSchema } from './meter.value.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const StopTransactionSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
transactionDatabaseId: z.number(),
|
||||
meterStop: z.number().int(),
|
||||
timestamp: z.iso.datetime(),
|
||||
reason: z.string().optional(),
|
||||
meterValues: z.array(MeterValueSchema).optional(),
|
||||
idTokenValue: z.string().optional(),
|
||||
idTokenType: z.string().optional(),
|
||||
});
|
||||
|
||||
export const StopTransactionProps = StopTransactionSchema.keyof().enum;
|
||||
|
||||
export type StopTransactionDto = z.infer<typeof StopTransactionSchema>;
|
||||
|
||||
export const StopTransactionCreateSchema = StopTransactionSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
transaction: true,
|
||||
meterValues: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type StopTransactionCreate = z.infer<typeof StopTransactionCreateSchema>;
|
||||
|
||||
export const stopTransactionSchemas = {
|
||||
StopTransaction: StopTransactionSchema,
|
||||
StopTransactionCreate: StopTransactionCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const SubscriptionSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
onConnect: z.boolean().default(false),
|
||||
onClose: z.boolean().default(false),
|
||||
onMessage: z.boolean().default(false),
|
||||
sentMessage: z.boolean().default(false),
|
||||
messageRegexFilter: z.string().nullable().optional(),
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export const SubscriptionProps = SubscriptionSchema.keyof().enum;
|
||||
|
||||
export type SubscriptionDto = z.infer<typeof SubscriptionSchema>;
|
||||
|
||||
export const SubscriptionCreateSchema = SubscriptionSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type SubscriptionCreate = z.infer<typeof SubscriptionCreateSchema>;
|
||||
|
||||
export const subscriptionSchemas = {
|
||||
Subscription: SubscriptionSchema,
|
||||
SubscriptionCreate: SubscriptionCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const TariffSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
currency: z.string().length(3), // CHAR(3)
|
||||
pricePerKwh: z.number().min(0), // DECIMAL
|
||||
pricePerMin: z.number().min(0).nullable().optional(), // DECIMAL
|
||||
pricePerSession: z.number().min(0).nullable().optional(), // DECIMAL
|
||||
authorizationAmount: z.number().min(0).nullable().optional(), // DECIMAL
|
||||
paymentFee: z.number().min(0).nullable().optional(), // DECIMAL
|
||||
taxRate: z.number().min(0).nullable().optional(), // DECIMAL
|
||||
tariffAltText: z.record(z.string(), z.any()).nullable().optional(), // JSONB
|
||||
// OCPP 2.1 TariffType fields
|
||||
tariffId: z.string().nullable().optional(),
|
||||
validFrom: z.string().datetime().nullable().optional(),
|
||||
description: z.array(z.any()).nullable().optional(), // MessageContentType[]
|
||||
energy: z.any().nullable().optional(), // TariffEnergyType
|
||||
chargingTime: z.any().nullable().optional(), // TariffTimeType
|
||||
idleTime: z.any().nullable().optional(), // TariffTimeType
|
||||
fixedFee: z.any().nullable().optional(), // TariffFixedType
|
||||
reservationTime: z.any().nullable().optional(), // TariffTimeType
|
||||
reservationFixed: z.any().nullable().optional(), // TariffFixedType
|
||||
minCost: z.any().nullable().optional(), // PriceType
|
||||
maxCost: z.any().nullable().optional(), // PriceType
|
||||
});
|
||||
|
||||
export const TariffProps = TariffSchema.keyof().enum;
|
||||
|
||||
export type TariffDto = z.infer<typeof TariffSchema>;
|
||||
|
||||
export const TariffCreateSchema = TariffSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type TariffCreate = z.infer<typeof TariffCreateSchema>;
|
||||
|
||||
export const tariffSchemas = {
|
||||
Tariff: TariffSchema,
|
||||
TariffCreate: TariffCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ServerProfileSchema } from './types/ocpi.registration.js';
|
||||
|
||||
export const TenantSchema = z.object({
|
||||
id: z.number().int().optional(),
|
||||
name: z.string(),
|
||||
url: z.string().nullable().optional(),
|
||||
countryCode: z.string().nullable().optional(),
|
||||
partyId: z.string().nullable().optional(),
|
||||
serverProfileOCPI: ServerProfileSchema.nullable().optional(),
|
||||
isUserTenant: z.boolean().default(false),
|
||||
maxChargingStations: z.number().int().nullable().optional(),
|
||||
updatedAt: z.date().optional(),
|
||||
createdAt: z.date().optional(),
|
||||
});
|
||||
|
||||
export const TenantProps = TenantSchema.keyof().enum;
|
||||
|
||||
export type TenantDto = z.infer<typeof TenantSchema>;
|
||||
|
||||
export const TenantCreateSchema = TenantSchema.omit({
|
||||
id: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type TenantCreate = z.infer<typeof TenantCreateSchema>;
|
||||
|
||||
export const TenantUpdateSchema = TenantSchema.partial().omit({
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type TenantUpdate = z.infer<typeof TenantUpdateSchema>;
|
||||
|
||||
export const tenantSchemas = {
|
||||
Tenant: TenantSchema,
|
||||
TenantCreate: TenantCreateSchema,
|
||||
TenantUpdate: TenantUpdateSchema,
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod/v4';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { PartnerProfileSchema } from './types/ocpi.registration.js';
|
||||
|
||||
export const TenantPartnerSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
countryCode: z.string().nullable().optional(),
|
||||
partyId: z.string().nullable().optional(),
|
||||
partnerProfileOCPI: PartnerProfileSchema,
|
||||
});
|
||||
|
||||
export const TenantPartnerProps = TenantPartnerSchema.keyof().enum;
|
||||
|
||||
export type TenantPartnerDto = z.infer<typeof TenantPartnerSchema>;
|
||||
|
||||
export const TenantPartnerCreateSchema = TenantPartnerSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type TenantPartnerCreate = z.infer<typeof TenantPartnerCreateSchema>;
|
||||
|
||||
export const tenantPartnerSchemas = {
|
||||
TenantPartner: TenantPartnerSchema,
|
||||
TenantPartnerCreate: TenantPartnerCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { AuthorizationSchema } from './authorization.dto.js';
|
||||
import { ChargingStationSchema } from './charging.station.dto.js';
|
||||
import { ConnectorSchema } from './connector.dto.js';
|
||||
import { EvseSchema } from './evse.dto.js';
|
||||
import { LocationSchema } from './location.dto.js';
|
||||
import { MeterValueSchema } from './meter.value.dto.js';
|
||||
import { StartTransactionSchema } from './start.transaction.dto.js';
|
||||
import { StopTransactionSchema } from './stop.transaction.dto.js';
|
||||
import { TariffSchema } from './tariff.dto.js';
|
||||
import { TransactionEventSchema } from './transaction.event.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { TransactionLimitSchema } from './types/transaction.type.js';
|
||||
|
||||
export const TransactionSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
transactionId: z.string(),
|
||||
ocppConnectionName: z.string(),
|
||||
stationId: z.int(),
|
||||
isActive: z.boolean(),
|
||||
locationId: z.number().int().optional(),
|
||||
location: LocationSchema.optional(),
|
||||
station: ChargingStationSchema,
|
||||
evseId: z.number().int().optional(),
|
||||
evse: EvseSchema.nullable().optional(),
|
||||
connectorId: z.number().int().optional(),
|
||||
connector: ConnectorSchema.nullable().optional(),
|
||||
authorizationId: z.number().int().optional(),
|
||||
authorization: AuthorizationSchema.optional(),
|
||||
tariffId: z.number().int().optional(),
|
||||
tariff: TariffSchema.optional(),
|
||||
transactionEvents: z.array(TransactionEventSchema).optional(),
|
||||
meterValues: z.array(MeterValueSchema).optional(),
|
||||
startTransaction: StartTransactionSchema.optional(),
|
||||
stopTransaction: StopTransactionSchema.optional(),
|
||||
chargingState: z.string().nullable().optional(),
|
||||
timeSpentCharging: z.number().int().nullable().optional(), // BIGINT
|
||||
meterStart: z.number().int().nullable().optional(),
|
||||
totalKwh: z.number().nullable().optional(), // DECIMAL
|
||||
stoppedReason: z.string().nullable().optional(),
|
||||
remoteStartId: z.number().int().nullable().optional(),
|
||||
totalCost: z.number().optional(), // DECIMAL
|
||||
startTime: z.iso.datetime().optional(),
|
||||
endTime: z.iso.datetime().optional(),
|
||||
transactionLimit: TransactionLimitSchema.optional().nullable(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export const TransactionProps = TransactionSchema.keyof().enum;
|
||||
|
||||
export type TransactionDto = z.infer<typeof TransactionSchema>;
|
||||
|
||||
export const TransactionCreateSchema = TransactionSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
location: true,
|
||||
station: true,
|
||||
evse: true,
|
||||
connector: true,
|
||||
authorization: true,
|
||||
tariff: true,
|
||||
transactionEvents: true,
|
||||
meterValues: true,
|
||||
startTransaction: true,
|
||||
stopTransaction: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type TransactionCreate = z.infer<typeof TransactionCreateSchema>;
|
||||
|
||||
export const transactionSchemas = {
|
||||
Transaction: TransactionSchema,
|
||||
TransactionCreate: TransactionCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { EvseTypeSchema } from './evse.type.dto.js';
|
||||
import { MeterValueSchema } from './meter.value.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { TransactionEventEnumSchema, TriggerReasonEnumSchema } from './types/enums.js';
|
||||
import { TransactionTypeSchema } from './types/transaction.type.js';
|
||||
|
||||
export const TransactionEventSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
eventType: TransactionEventEnumSchema,
|
||||
meterValue: z.tuple([MeterValueSchema]).rest(MeterValueSchema).optional(), // Non-empty array
|
||||
timestamp: z.iso.datetime(),
|
||||
triggerReason: TriggerReasonEnumSchema,
|
||||
seqNo: z.number().int(),
|
||||
offline: z.boolean().default(false).nullable().optional(),
|
||||
numberOfPhasesUsed: z.number().int().nullable().optional(),
|
||||
cableMaxCurrent: z.number().nullable().optional(), // DECIMAL
|
||||
reservationId: z.number().int().nullable().optional(),
|
||||
transactionDatabaseId: z.number().int().optional(),
|
||||
transactionInfo: TransactionTypeSchema.optional(),
|
||||
evseId: z.number().int().nullable().optional(),
|
||||
evse: EvseTypeSchema.omit({ tenantId: true }).optional(), // TenantId omitted so that raw OCPP data can be stored
|
||||
idTokenValue: z.string().nullable().optional(),
|
||||
idTokenType: z.string().nullable().optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export const TransactionEventProps = TransactionEventSchema.keyof().enum;
|
||||
|
||||
export type TransactionEventDto = z.infer<typeof TransactionEventSchema>;
|
||||
|
||||
export const TransactionEventCreateSchema = TransactionEventSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
meterValue: true,
|
||||
transaction: true,
|
||||
evse: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type TransactionEventCreate = z.infer<typeof TransactionEventCreateSchema>;
|
||||
|
||||
export const transactionEventSchemas = {
|
||||
TransactionEvent: TransactionEventSchema,
|
||||
TransactionEventCreate: TransactionEventCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { AuthorizationStatusEnumSchema } from './enums.js';
|
||||
|
||||
export const AdditionalInfoSchema = z.object({
|
||||
id: z.number().int().optional(),
|
||||
additionalIdToken: z.string(),
|
||||
type: z.string(),
|
||||
});
|
||||
|
||||
export type AdditionalInfo = z.infer<typeof AdditionalInfoSchema>;
|
||||
|
||||
export const RealTimeAuthLastAttemptSchema = z.object({
|
||||
timestamp: z.iso.datetime(),
|
||||
result: AuthorizationStatusEnumSchema,
|
||||
ocppConnectionName: z.string(),
|
||||
evseId: z.number().nullable().optional(),
|
||||
connectorId: z.number(),
|
||||
});
|
||||
|
||||
export type RealTimeAuthLastAttempt = z.infer<typeof RealTimeAuthLastAttemptSchema>;
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod/v4';
|
||||
import { TenantSchema } from '@interfaces/dto/tenant.dto.js';
|
||||
|
||||
export const BaseSchema = z.object({
|
||||
tenantId: z.number().int().optional(),
|
||||
tenant: TenantSchema.optional(),
|
||||
updatedAt: z.date().optional(),
|
||||
createdAt: z.date().optional(),
|
||||
});
|
||||
|
||||
export const BaseProps = BaseSchema.keyof().enum;
|
||||
|
||||
export type BaseDto = z.infer<typeof BaseSchema>;
|
||||
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ACChargingParametersSchema = z.object({
|
||||
energyAmount: z.number(),
|
||||
evMinCurrent: z.number(),
|
||||
evMaxCurrent: z.number(),
|
||||
evMaxVoltage: z.number(),
|
||||
});
|
||||
|
||||
export type ACChargingParametersType = z.infer<typeof ACChargingParametersSchema>;
|
||||
|
||||
export const DCChargingParametersSchema = z.object({
|
||||
evMaxCurrent: z.number(),
|
||||
evMaxVoltage: z.number(),
|
||||
energyAmount: z.number().nullable().optional(),
|
||||
evMaxPower: z.number().nullable().optional(),
|
||||
stateOfCharge: z.number().nullable().optional(),
|
||||
evEnergyCapacity: z.number().nullable().optional(),
|
||||
fullSoC: z.number().nullable().optional(),
|
||||
bulkSoC: z.number().nullable().optional(),
|
||||
});
|
||||
|
||||
export type DCChargingParametersType = z.infer<typeof DCChargingParametersSchema>;
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { OCPP2_0_1 } from '@ocpp/model/index.js';
|
||||
|
||||
export const AttributeEnumSchema = z.enum(OCPP2_0_1.AttributeEnumType);
|
||||
export const DataEnumSchema = z.enum(OCPP2_0_1.DataEnumType);
|
||||
export const MutabilityEnumSchema = z.enum(OCPP2_0_1.MutabilityEnumType);
|
||||
@@ -0,0 +1,716 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// ============================================================================
|
||||
// Schemas
|
||||
// ============================================================================
|
||||
|
||||
export const AsyncJobNameSchema = z.enum(['FETCH_OCPI_TOKENS']);
|
||||
|
||||
export const AsyncJobActionSchema = z.enum(['RESUME', 'STOP']);
|
||||
|
||||
export const AttributeEnumSchema = z.enum(['Actual', 'Target', 'MinSet', 'MaxSet']);
|
||||
|
||||
export const AuthorizationStatusEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'Blocked',
|
||||
'ConcurrentTx',
|
||||
'Expired',
|
||||
'Invalid',
|
||||
'NoCredit',
|
||||
'NotAllowedTypeEVSE',
|
||||
'NotAtThisLocation',
|
||||
'NotAtThisTime',
|
||||
'Unknown',
|
||||
]);
|
||||
|
||||
export const AuthorizationWhitelistEnumSchema = z.enum(['Never', 'Allowed', 'AllowedOffline']);
|
||||
|
||||
export const AuthorizeCertificateStatusEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'SignatureError',
|
||||
'CertificateExpired',
|
||||
'CertificateRevoked',
|
||||
'NoCertificateAvailable',
|
||||
'CertChainError',
|
||||
'ContractCancelled',
|
||||
]);
|
||||
|
||||
export const CancelReservationStatusEnumSchema = z.enum(['Accepted', 'Rejected']);
|
||||
|
||||
export const ChargingProfileStatusEnumSchema = z.enum(['Accepted', 'Rejected']);
|
||||
|
||||
export const ClearChargingProfileStatusEnumSchema = z.enum(['Accepted', 'Unknown']);
|
||||
|
||||
export const CertificateSigningUseEnumSchema = z.enum([
|
||||
'ChargingStationCertificate',
|
||||
'V2GCertificate',
|
||||
'V2G20Certificate',
|
||||
]);
|
||||
|
||||
export const CertificateUseEnumSchema = z.enum([
|
||||
'V2GRootCertificate',
|
||||
'MORootCertificate',
|
||||
'CSMSRootCertificate',
|
||||
'V2GCertificateChain',
|
||||
'ManufacturerRootCertificate',
|
||||
'OEMRootCertificate',
|
||||
]);
|
||||
|
||||
export const ChargingLimitSourceEnumSchema = z.enum(['EMS', 'Other', 'SO', 'CSO']);
|
||||
|
||||
export const ChargingProfileKindEnumSchema = z.enum(['Absolute', 'Recurring', 'Relative']);
|
||||
|
||||
export const ChargingProfilePurposeEnumSchema = z.enum([
|
||||
'ChargingStationExternalConstraints',
|
||||
'ChargingStationMaxProfile',
|
||||
'TxDefaultProfile',
|
||||
'TxProfile',
|
||||
]);
|
||||
|
||||
export const ChargingRateUnitEnumSchema = z.enum(['W', 'A']);
|
||||
|
||||
export const ChargingStateEnumSchema = z.enum([
|
||||
'Charging',
|
||||
'EVConnected',
|
||||
'SuspendedEV',
|
||||
'SuspendedEVSE',
|
||||
'Idle',
|
||||
]);
|
||||
|
||||
export const ChargingStationCapabilitySchema = z.enum([
|
||||
'ChargingProfileCapable',
|
||||
'ChargingPreferencesCapable',
|
||||
'ChipCardSupport',
|
||||
'ContactlessCardSupport',
|
||||
'CreditCardPayable',
|
||||
'DebitCardPayable',
|
||||
'PEDTerminal',
|
||||
'RemoteStartStopCapable',
|
||||
'Reservable',
|
||||
'RFIDReader',
|
||||
'StartSessionConnectorRequired',
|
||||
'TokenGroupCapable',
|
||||
'UnlockCapable',
|
||||
]);
|
||||
|
||||
export const ChargingStationParkingRestrictionSchema = z.enum([
|
||||
'EVOnly',
|
||||
'Plugged',
|
||||
'Disabled',
|
||||
'Customers',
|
||||
'Motorcycles',
|
||||
]);
|
||||
|
||||
export const ChargingStationSequenceTypeSchema = z.enum([
|
||||
'customerInformation',
|
||||
'getBaseReport',
|
||||
'getChargingProfiles',
|
||||
'getDisplayMessages',
|
||||
'getLog',
|
||||
'getMonitoringReport',
|
||||
'getReport',
|
||||
'publishFirmware',
|
||||
'remoteStartId',
|
||||
'updateFirmware',
|
||||
'transactionId',
|
||||
]);
|
||||
|
||||
export const ClearMessageStatusEnumSchema = z.enum(['Accepted', 'Unknown', 'Rejected']);
|
||||
|
||||
export const ConnectorErrorCodeEnumSchema = z.enum([
|
||||
'ConnectorLockFailure',
|
||||
'EVCommunicationError',
|
||||
'GroundFailure',
|
||||
'HighTemperature',
|
||||
'InternalError',
|
||||
'LocalListConflict',
|
||||
'NoError',
|
||||
'OtherError',
|
||||
'OverCurrentFailure',
|
||||
'PowerMeterFailure',
|
||||
'PowerSwitchFailure',
|
||||
'ReaderFailure',
|
||||
'ResetFailure',
|
||||
'UnderVoltage',
|
||||
'OverVoltage',
|
||||
'WeakSignal',
|
||||
]);
|
||||
|
||||
export const ConnectorFormatEnumSchema = z.enum(['Socket', 'Cable']);
|
||||
|
||||
export const ConnectorPowerTypeEnumSchema = z.enum([
|
||||
'AC1Phase',
|
||||
'AC2Phase',
|
||||
'AC2PhaseSplit',
|
||||
'AC3Phase',
|
||||
'DC',
|
||||
]);
|
||||
|
||||
export const ConnectorStatusEnumSchema = z.enum([
|
||||
'Available',
|
||||
'Occupied',
|
||||
'Preparing',
|
||||
'Charging',
|
||||
'SuspendedEVSE',
|
||||
'SuspendedEV',
|
||||
'Finishing',
|
||||
'Reserved',
|
||||
'Unavailable',
|
||||
'Faulted',
|
||||
'Unknown',
|
||||
]);
|
||||
|
||||
export const ConnectorTypeEnumSchema = z.enum([
|
||||
'CHAdeMO',
|
||||
'ChaoJi',
|
||||
'DomesticA',
|
||||
'DomesticB',
|
||||
'DomesticC',
|
||||
'DomesticD',
|
||||
'DomesticE',
|
||||
'DomesticF',
|
||||
'DomesticG',
|
||||
'DomesticH',
|
||||
'DomesticI',
|
||||
'DomesticJ',
|
||||
'DomesticK',
|
||||
'DomesticL',
|
||||
'DomesticM',
|
||||
'DomesticN',
|
||||
'DomesticO',
|
||||
'GBTAC',
|
||||
'GBTDC',
|
||||
'IEC603092Single16',
|
||||
'IEC603092Three16',
|
||||
'IEC603092Three32',
|
||||
'IEC603092Three64',
|
||||
'IEC62196T1',
|
||||
'IEC62196T1COMBO',
|
||||
'IEC62196T2',
|
||||
'IEC62196T2COMBO',
|
||||
'IEC62196T3A',
|
||||
'IEC62196T3C',
|
||||
'NEMA520',
|
||||
'NEMA630',
|
||||
'NEMA650',
|
||||
'NEMA1030',
|
||||
'NEMA1050',
|
||||
'NEMA1430',
|
||||
'NEMA1450',
|
||||
'PantographBottomUp',
|
||||
'PantographTopDown',
|
||||
'TeslaR',
|
||||
'TeslaS',
|
||||
]);
|
||||
|
||||
export const DataEnumSchema = z.enum([
|
||||
'string',
|
||||
'decimal',
|
||||
'integer',
|
||||
'dateTime',
|
||||
'boolean',
|
||||
'OptionList',
|
||||
'SequenceList',
|
||||
'MemberList',
|
||||
'passwordString',
|
||||
]);
|
||||
|
||||
export const DataTransferEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'Rejected',
|
||||
'UnknownMessageId',
|
||||
'UnknownVendorId',
|
||||
]);
|
||||
|
||||
export const DataTransferStatusSchema = z.enum([
|
||||
'Accepted',
|
||||
'Rejected',
|
||||
'UnknownMessageId',
|
||||
'UnknownVendorId',
|
||||
]);
|
||||
|
||||
export const DeleteCertificateStatusEnumSchema = z.enum(['Accepted', 'Failed', 'NotFound']);
|
||||
|
||||
export const DisplayMessageStatusEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'NotSupportedMessageFormat',
|
||||
'Rejected',
|
||||
'NotSupportedPriority',
|
||||
'NotSupportedState',
|
||||
'UnknownTransaction',
|
||||
'LanguageNotSupported',
|
||||
]);
|
||||
|
||||
export const EventNotificationEnumSchema = z.enum([
|
||||
'HardWiredNotification',
|
||||
'HardWiredMonitor',
|
||||
'PreconfiguredMonitor',
|
||||
'CustomMonitor',
|
||||
]);
|
||||
|
||||
export const EventTriggerEnumSchema = z.enum(['Alerting', 'Delta', 'Periodic']);
|
||||
|
||||
export const EnergyTransferModeEnumSchema = z.enum([
|
||||
'DC',
|
||||
'AC_single_phase',
|
||||
'AC_two_phase',
|
||||
'AC_three_phase',
|
||||
]);
|
||||
|
||||
export const CostKindEnumSchema = z.enum([
|
||||
'CarbonDioxideEmission',
|
||||
'RelativePricePercentage',
|
||||
'RenewableGenerationPercentage',
|
||||
]);
|
||||
|
||||
export const GetCertificateStatusEnumSchema = z.enum(['Accepted', 'Failed']);
|
||||
|
||||
export const GenericDeviceModelStatusEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'Rejected',
|
||||
'NotSupported',
|
||||
'EmptyResultSet',
|
||||
]);
|
||||
|
||||
export const GenericStatusEnumSchema = z.enum(['Accepted', 'Failed', 'Rejected']);
|
||||
|
||||
export const GetInstalledCertificateStatusEnumSchema = z.enum(['Accepted', 'NotFound']);
|
||||
|
||||
export const HashAlgorithmEnumSchema = z.enum(['SHA256', 'SHA384', 'SHA512']);
|
||||
|
||||
export const IdTokenEnumSchema = z.enum([
|
||||
'Central',
|
||||
'DirectPayment',
|
||||
'eMAID',
|
||||
'EVCCID',
|
||||
'ISO14443',
|
||||
'ISO15693',
|
||||
'KeyCode',
|
||||
'Local',
|
||||
'MacAddress',
|
||||
'NoAuthorization',
|
||||
'Other',
|
||||
'VIN',
|
||||
]);
|
||||
|
||||
export const InstallCertificateStatusEnumSchema = z.enum(['Accepted', 'Rejected', 'Failed']);
|
||||
|
||||
export const InstallCertificateUseEnumSchema = z.enum([
|
||||
'V2GRootCertificate',
|
||||
'MORootCertificate',
|
||||
'ManufacturerRootCertificate',
|
||||
'CSMSRootCertificate',
|
||||
'OEMRootCertificate',
|
||||
]);
|
||||
|
||||
export const Iso15118EVCertificateStatusEnumSchema = z.enum(['Accepted', 'Failed']);
|
||||
|
||||
export const LocationEnumSchema = z.enum(['Body', 'Cable', 'EV', 'Inlet', 'Outlet']);
|
||||
|
||||
export const LocationFacilityEnumSchema = z.enum([
|
||||
'Hotel',
|
||||
'Restaurant',
|
||||
'Cafe',
|
||||
'Mall',
|
||||
'Supermarket',
|
||||
'Sport',
|
||||
'RecreationArea',
|
||||
'Nature',
|
||||
'Museum',
|
||||
'BikeSharing',
|
||||
'BusStop',
|
||||
'TaxiStand',
|
||||
'TramStop',
|
||||
'MetroStation',
|
||||
'TrainStation',
|
||||
'Airport',
|
||||
'ParkingLot',
|
||||
'CarpoolParking',
|
||||
'FuelStation',
|
||||
'Wifi',
|
||||
]);
|
||||
|
||||
export const LocationParkingEnumSchema = z.enum([
|
||||
'AlongMotorway',
|
||||
'ParkingGarage',
|
||||
'ParkingLot',
|
||||
'OnDriveway',
|
||||
'OnStreet',
|
||||
'UndergroundGarage',
|
||||
]);
|
||||
|
||||
export const MessageFormatEnumSchema = z.enum(['ASCII', 'HTML', 'URI', 'UTF8']);
|
||||
|
||||
export const MonitorEnumSchema = z.enum([
|
||||
'UpperThreshold',
|
||||
'LowerThreshold',
|
||||
'Delta',
|
||||
'Periodic',
|
||||
'PeriodicClockAligned',
|
||||
'TargetDelta',
|
||||
'TargetDeltaRelative',
|
||||
]);
|
||||
|
||||
export const MessagePriorityEnumSchema = z.enum(['AlwaysFront', 'InFront', 'NormalCycle']);
|
||||
|
||||
export const MessageStateEnumSchema = z.enum(['Charging', 'Faulted', 'Idle', 'Unavailable']);
|
||||
|
||||
export const MutabilityEnumSchema = z.enum(['ReadOnly', 'WriteOnly', 'ReadWrite']);
|
||||
|
||||
export const MeasurandEnumSchema = z.enum([
|
||||
'Current.Export',
|
||||
'Current.Import',
|
||||
'Current.Offered',
|
||||
'Energy.Active.Export.Register',
|
||||
'Energy.Active.Import.Register',
|
||||
'Energy.Reactive.Export.Register',
|
||||
'Energy.Reactive.Import.Register',
|
||||
'Energy.Active.Export.Interval',
|
||||
'Energy.Active.Import.Interval',
|
||||
'Energy.Active.Net',
|
||||
'Energy.Reactive.Export.Interval',
|
||||
'Energy.Reactive.Import.Interval',
|
||||
'Energy.Reactive.Net',
|
||||
'Energy.Apparent.Net',
|
||||
'Energy.Apparent.Import',
|
||||
'Energy.Apparent.Export',
|
||||
'Frequency',
|
||||
'Power.Active.Export',
|
||||
'Power.Active.Import',
|
||||
'Power.Factor',
|
||||
'Power.Offered',
|
||||
'Power.Reactive.Export',
|
||||
'Power.Reactive.Import',
|
||||
'RPM',
|
||||
'SoC',
|
||||
'Temperature',
|
||||
'Voltage',
|
||||
]);
|
||||
|
||||
export const NotifyEVChargingNeedsStatusEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'Rejected',
|
||||
'Processing',
|
||||
'NoChargingProfile',
|
||||
]);
|
||||
|
||||
export const MonitoringCriterionEnumSchema = z.enum([
|
||||
'ThresholdMonitoring',
|
||||
'DeltaMonitoring',
|
||||
'PeriodicMonitoring',
|
||||
]);
|
||||
|
||||
export const OCPIVersionNumberSchema = z.enum(['2.2.1']);
|
||||
|
||||
export const OCPPInterfaceEnumSchema = z.enum([
|
||||
'Wired0',
|
||||
'Wired1',
|
||||
'Wired2',
|
||||
'Wired3',
|
||||
'Wireless0',
|
||||
'Wireless1',
|
||||
'Wireless2',
|
||||
'Wireless3',
|
||||
]);
|
||||
|
||||
export const OCPPTransportEnumSchema = z.enum(['JSON', 'SOAP']);
|
||||
|
||||
export const OCPPVersionEnumSchema = z.enum(['OCPP12', 'OCPP15', 'OCPP16', 'OCPP20']);
|
||||
|
||||
export const PhaseEnumSchema = z.enum([
|
||||
'L1',
|
||||
'L2',
|
||||
'L3',
|
||||
'N',
|
||||
'L1-N',
|
||||
'L2-N',
|
||||
'L3-N',
|
||||
'L1-L2',
|
||||
'L2-L3',
|
||||
'L3-L1',
|
||||
]);
|
||||
|
||||
export const ReadingContextEnumSchema = z.enum([
|
||||
'Interruption.Begin',
|
||||
'Interruption.End',
|
||||
'Other',
|
||||
'Sample.Clock',
|
||||
'Sample.Periodic',
|
||||
'Transaction.Begin',
|
||||
'Transaction.End',
|
||||
'Trigger',
|
||||
]);
|
||||
|
||||
export const ReasonEnumSchema = z.enum([
|
||||
'DeAuthorized',
|
||||
'EmergencyStop',
|
||||
'EnergyLimitReached',
|
||||
'EVDisconnected',
|
||||
'GroundFault',
|
||||
'ImmediateReset',
|
||||
'Local',
|
||||
'LocalOutOfCredit',
|
||||
'MasterPass',
|
||||
'Other',
|
||||
'OvercurrentFault',
|
||||
'PowerLoss',
|
||||
'PowerQuality',
|
||||
'Reboot',
|
||||
'Remote',
|
||||
'SOCLimitReached',
|
||||
'StoppedByEV',
|
||||
'TimeLimitReached',
|
||||
'Timeout',
|
||||
]);
|
||||
|
||||
export const RecurrencyKindEnumSchema = z.enum(['Daily', 'Weekly']);
|
||||
|
||||
export const RegistrationStatusEnumSchema = z.enum(['Accepted', 'Pending', 'Rejected']);
|
||||
|
||||
export const RequestStartStopStatusEnumSchema = z.enum(['Accepted', 'Rejected']);
|
||||
|
||||
export const ReservationUpdateStatusEnumSchema = z.enum(['Expired', 'Removed', 'NoTransaction']);
|
||||
|
||||
export const ReserveNowStatusEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'Faulted',
|
||||
'Occupied',
|
||||
'Rejected',
|
||||
'Unavailable',
|
||||
]);
|
||||
|
||||
export const ResetEnumSchema = z.enum(['Immediate', 'OnIdle', 'ImmediateAndResume']);
|
||||
|
||||
export const SendLocalListStatusEnumSchema = z.enum(['Accepted', 'Failed', 'VersionMismatch']);
|
||||
|
||||
export const SetMonitoringStatusEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'UnknownComponent',
|
||||
'UnknownVariable',
|
||||
'UnsupportedMonitorType',
|
||||
'Rejected',
|
||||
'Duplicate',
|
||||
]);
|
||||
|
||||
export const SetNetworkProfileStatusEnumSchema = z.enum(['Accepted', 'Rejected', 'Failed']);
|
||||
|
||||
export const SetVariableStatusEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'Rejected',
|
||||
'UnknownComponent',
|
||||
'UnknownVariable',
|
||||
'NotSupportedAttributeType',
|
||||
'RebootRequired',
|
||||
]);
|
||||
|
||||
export const TransactionEventEnumSchema = z.enum(['Ended', 'Started', 'Updated']);
|
||||
|
||||
export const TriggerReasonEnumSchema = z.enum([
|
||||
'Authorized',
|
||||
'CablePluggedIn',
|
||||
'ChargingRateChanged',
|
||||
'ChargingStateChanged',
|
||||
'Deauthorized',
|
||||
'EnergyLimitReached',
|
||||
'EVCommunicationLost',
|
||||
'EVConnectTimeout',
|
||||
'MeterValueClock',
|
||||
'MeterValuePeriodic',
|
||||
'TimeLimitReached',
|
||||
'Trigger',
|
||||
'UnlockCommand',
|
||||
'StopAuthorized',
|
||||
'EVDeparted',
|
||||
'EVDetected',
|
||||
'RemoteStop',
|
||||
'RemoteStart',
|
||||
'AbnormalCondition',
|
||||
'SignedDataReceived',
|
||||
'ResetCommand',
|
||||
]);
|
||||
|
||||
export const TariffSetStatusEnumSchema = z.enum([
|
||||
'Accepted',
|
||||
'Rejected',
|
||||
'TooManyElements',
|
||||
'ConditionNotSupported',
|
||||
'DuplicateTariffId',
|
||||
]);
|
||||
|
||||
export const UpdateEnumSchema = z.enum(['Differential', 'Full']);
|
||||
|
||||
// ============================================================================
|
||||
// Enum Exports
|
||||
// ============================================================================
|
||||
|
||||
export const AsyncJobNameEnum = AsyncJobNameSchema.enum;
|
||||
export const AsyncJobActionEnum = AsyncJobActionSchema.enum;
|
||||
export const AttributeEnum = AttributeEnumSchema.enum;
|
||||
export const AuthorizationStatusEnum = AuthorizationStatusEnumSchema.enum;
|
||||
export const AuthorizationWhitelistEnum = AuthorizationWhitelistEnumSchema.enum;
|
||||
export const AuthorizeCertificateStatusEnum = AuthorizeCertificateStatusEnumSchema.enum;
|
||||
export const CancelReservationStatusEnum = CancelReservationStatusEnumSchema.enum;
|
||||
export const CertificateSigningUseEnum = CertificateSigningUseEnumSchema.enum;
|
||||
export const ChargingProfileStatusEnum = ChargingProfileStatusEnumSchema.enum;
|
||||
export const ClearChargingProfileStatusEnum = ClearChargingProfileStatusEnumSchema.enum;
|
||||
export const CertificateUseEnum = CertificateUseEnumSchema.enum;
|
||||
export const ChargingStateEnum = ChargingStateEnumSchema.enum;
|
||||
export const ChargingStationCapabilityEnum = ChargingStationCapabilitySchema.enum;
|
||||
export const ChargingStationParkingRestrictionEnum = ChargingStationParkingRestrictionSchema.enum;
|
||||
export const ChargingStationSequenceTypeEnum = ChargingStationSequenceTypeSchema.enum;
|
||||
export const ClearMessageStatusEnum = ClearMessageStatusEnumSchema.enum;
|
||||
export const ConnectorErrorCodeEnum = ConnectorErrorCodeEnumSchema.enum;
|
||||
export const ConnectorFormatEnum = ConnectorFormatEnumSchema.enum;
|
||||
export const ConnectorPowerTypeEnum = ConnectorPowerTypeEnumSchema.enum;
|
||||
export const ConnectorStatusEnum = ConnectorStatusEnumSchema.enum;
|
||||
export const ConnectorTypeEnum = ConnectorTypeEnumSchema.enum;
|
||||
export const ChargingProfileKindEnum = ChargingProfileKindEnumSchema.enum;
|
||||
export const ChargingProfilePurposeEnum = ChargingProfilePurposeEnumSchema.enum;
|
||||
export const ChargingRateUnitEnum = ChargingRateUnitEnumSchema.enum;
|
||||
export const ChargingLimitSourceEnum = ChargingLimitSourceEnumSchema.enum;
|
||||
export const CostKindEnum = CostKindEnumSchema.enum;
|
||||
export const DataEnum = DataEnumSchema.enum;
|
||||
export const DataTransferEnum = DataTransferEnumSchema.enum;
|
||||
export const DataTransferStatusEnum = DataTransferStatusSchema.enum;
|
||||
export const DeleteCertificateStatusEnum = DeleteCertificateStatusEnumSchema.enum;
|
||||
export const DisplayMessageStatusEnum = DisplayMessageStatusEnumSchema.enum;
|
||||
export const EnergyTransferModeEnum = EnergyTransferModeEnumSchema.enum;
|
||||
export const EventNotificationEnum = EventNotificationEnumSchema.enum;
|
||||
export const EventTriggerEnum = EventTriggerEnumSchema.enum;
|
||||
export const GenericDeviceModelStatusEnum = GenericDeviceModelStatusEnumSchema.enum;
|
||||
export const GetCertificateStatusEnum = GetCertificateStatusEnumSchema.enum;
|
||||
export const GenericStatusEnum = GenericStatusEnumSchema.enum;
|
||||
export const GetInstalledCertificateStatusEnum = GetInstalledCertificateStatusEnumSchema.enum;
|
||||
export const HashAlgorithmEnum = HashAlgorithmEnumSchema.enum;
|
||||
export const IdTokenEnum = IdTokenEnumSchema.enum;
|
||||
export const InstallCertificateStatusEnum = InstallCertificateStatusEnumSchema.enum;
|
||||
export const InstallCertificateUseEnum = InstallCertificateUseEnumSchema.enum;
|
||||
export const Iso15118EVCertificateStatusEnum = Iso15118EVCertificateStatusEnumSchema.enum;
|
||||
export const LocationEnum = LocationEnumSchema.enum;
|
||||
export const LocationFacilityEnum = LocationFacilityEnumSchema.enum;
|
||||
export const LocationParkingEnum = LocationParkingEnumSchema.enum;
|
||||
export const MeasurandEnum = MeasurandEnumSchema.enum;
|
||||
export const MessageFormatEnum = MessageFormatEnumSchema.enum;
|
||||
export const MutabilityEnum = MutabilityEnumSchema.enum;
|
||||
export const MonitorEnum = MonitorEnumSchema.enum;
|
||||
export const MessagePriorityEnum = MessagePriorityEnumSchema.enum;
|
||||
export const MessageStateEnum = MessageStateEnumSchema.enum;
|
||||
export const MonitoringCriterionEnum = MonitoringCriterionEnumSchema.enum;
|
||||
export const NotifyEVChargingNeedsStatusEnum = NotifyEVChargingNeedsStatusEnumSchema.enum;
|
||||
export const OCPIVersionNumberEnum = OCPIVersionNumberSchema.enum;
|
||||
export const OCPPInterfaceEnum = OCPPInterfaceEnumSchema.enum;
|
||||
export const OCPPTransportEnum = OCPPTransportEnumSchema.enum;
|
||||
export const OCPPVersionEnum = OCPPVersionEnumSchema.enum;
|
||||
export const PhaseEnum = PhaseEnumSchema.enum;
|
||||
export const ReadingContextEnum = ReadingContextEnumSchema.enum;
|
||||
export const RecurrencyKindEnum = RecurrencyKindEnumSchema.enum;
|
||||
export const ReasonEnum = ReasonEnumSchema.enum;
|
||||
export const RegistrationStatusEnum = RegistrationStatusEnumSchema.enum;
|
||||
export const RequestStartStopStatusEnum = RequestStartStopStatusEnumSchema.enum;
|
||||
export const ReservationUpdateStatusEnum = ReservationUpdateStatusEnumSchema.enum;
|
||||
export const ReserveNowStatusEnum = ReserveNowStatusEnumSchema.enum;
|
||||
export const ResetEnum = ResetEnumSchema.enum;
|
||||
export const SendLocalListStatusEnum = SendLocalListStatusEnumSchema.enum;
|
||||
export const SetMonitoringStatusEnum = SetMonitoringStatusEnumSchema.enum;
|
||||
export const SetNetworkProfileStatusEnum = SetNetworkProfileStatusEnumSchema.enum;
|
||||
export const SetVariableStatusEnum = SetVariableStatusEnumSchema.enum;
|
||||
export const TariffSetStatusEnum = TariffSetStatusEnumSchema.enum;
|
||||
export const TransactionEventEnum = TransactionEventEnumSchema.enum;
|
||||
export const TriggerReasonEnum = TriggerReasonEnumSchema.enum;
|
||||
export const UpdateEnum = UpdateEnumSchema.enum;
|
||||
|
||||
// ============================================================================
|
||||
// Type Exports
|
||||
// ============================================================================
|
||||
|
||||
export type AsyncJobNameEnumType = z.infer<typeof AsyncJobNameSchema>;
|
||||
export type AsyncJobActionEnumType = z.infer<typeof AsyncJobActionSchema>;
|
||||
export type AttributeEnumType = z.infer<typeof AttributeEnumSchema>;
|
||||
export type AuthorizationStatusEnumType = z.infer<typeof AuthorizationStatusEnumSchema>;
|
||||
export type AuthorizationWhitelistEnumType = z.infer<typeof AuthorizationWhitelistEnumSchema>;
|
||||
export type AuthorizeCertificateStatusEnumType = z.infer<
|
||||
typeof AuthorizeCertificateStatusEnumSchema
|
||||
>;
|
||||
export type CancelReservationStatusEnumType = z.infer<typeof CancelReservationStatusEnumSchema>;
|
||||
export type CertificateSigningUseEnumType = z.infer<typeof CertificateSigningUseEnumSchema>;
|
||||
export type ChargingProfileStatusEnumType = z.infer<typeof ChargingProfileStatusEnumSchema>;
|
||||
export type ClearChargingProfileStatusEnumType = z.infer<
|
||||
typeof ClearChargingProfileStatusEnumSchema
|
||||
>;
|
||||
export type CertificateUseEnumType = z.infer<typeof CertificateUseEnumSchema>;
|
||||
export type ChargingStateEnumType = z.infer<typeof ChargingStateEnumSchema>;
|
||||
export type ChargingStationCapabilityEnumType = z.infer<typeof ChargingStationCapabilitySchema>;
|
||||
export type ChargingStationParkingRestrictionEnumType = z.infer<
|
||||
typeof ChargingStationParkingRestrictionSchema
|
||||
>;
|
||||
export type ClearMessageStatusEnumType = z.infer<typeof ClearMessageStatusEnumSchema>;
|
||||
export type ChargingStationSequenceTypeEnumType = z.infer<typeof ChargingStationSequenceTypeSchema>;
|
||||
export type ConnectorErrorCodeEnumType = z.infer<typeof ConnectorErrorCodeEnumSchema>;
|
||||
export type ConnectorFormatEnumType = z.infer<typeof ConnectorFormatEnumSchema>;
|
||||
export type ConnectorPowerTypeEnumType = z.infer<typeof ConnectorPowerTypeEnumSchema>;
|
||||
export type ConnectorStatusEnumType = z.infer<typeof ConnectorStatusEnumSchema>;
|
||||
export type ConnectorTypeEnumType = z.infer<typeof ConnectorTypeEnumSchema>;
|
||||
export type ChargingProfileKindEnumType = z.infer<typeof ChargingProfileKindEnumSchema>;
|
||||
export type ChargingProfilePurposeEnumType = z.infer<typeof ChargingProfilePurposeEnumSchema>;
|
||||
export type ChargingRateUnitEnumType = z.infer<typeof ChargingRateUnitEnumSchema>;
|
||||
export type ChargingLimitSourceEnumType = z.infer<typeof ChargingLimitSourceEnumSchema>;
|
||||
export type CostKindEnumType = z.infer<typeof CostKindEnumSchema>;
|
||||
export type DataEnumType = z.infer<typeof DataEnumSchema>;
|
||||
export type DataTransferEnumType = z.infer<typeof DataTransferEnumSchema>;
|
||||
export type DataTransferStatusType = z.infer<typeof DataTransferStatusSchema>;
|
||||
export type DeleteCertificateStatusEnumType = z.infer<typeof DeleteCertificateStatusEnumSchema>;
|
||||
export type DisplayMessageStatusEnumType = z.infer<typeof DisplayMessageStatusEnumSchema>;
|
||||
export type EnergyTransferModeEnumType = z.infer<typeof EnergyTransferModeEnumSchema>;
|
||||
export type EventTriggerEnumType = z.infer<typeof EventTriggerEnumSchema>;
|
||||
export type EventNotificationEnumType = z.infer<typeof EventNotificationEnumSchema>;
|
||||
export type GenericDeviceModelStatusEnumType = z.infer<typeof GenericDeviceModelStatusEnumSchema>;
|
||||
export type GenericStatusEnumType = z.infer<typeof GenericStatusEnumSchema>;
|
||||
export type GetCertificateStatusEnumType = z.infer<typeof GetCertificateStatusEnumSchema>;
|
||||
export type GetInstalledCertificateStatusEnumType = z.infer<
|
||||
typeof GetInstalledCertificateStatusEnumSchema
|
||||
>;
|
||||
export type HashAlgorithmEnumType = z.infer<typeof HashAlgorithmEnumSchema>;
|
||||
export type IdTokenEnumType = z.infer<typeof IdTokenEnumSchema>;
|
||||
export type InstallCertificateStatusEnumType = z.infer<typeof InstallCertificateStatusEnumSchema>;
|
||||
export type InstallCertificateUseEnumType = z.infer<typeof InstallCertificateUseEnumSchema>;
|
||||
export type Iso15118EVCertificateStatusEnumType = z.infer<
|
||||
typeof Iso15118EVCertificateStatusEnumSchema
|
||||
>;
|
||||
export type LocationEnumType = z.infer<typeof LocationEnumSchema>;
|
||||
export type LocationFacilityEnumType = z.infer<typeof LocationFacilityEnumSchema>;
|
||||
export type LocationParkingEnumType = z.infer<typeof LocationParkingEnumSchema>;
|
||||
export type MeasurandEnumType = z.infer<typeof MeasurandEnumSchema>;
|
||||
export type MessageFormatEnumType = z.infer<typeof MessageFormatEnumSchema>;
|
||||
export type MutabilityEnumType = z.infer<typeof MutabilityEnumSchema>;
|
||||
export type MonitorEnumType = z.infer<typeof MonitorEnumSchema>;
|
||||
export type MessagePriorityEnumType = z.infer<typeof MessagePriorityEnumSchema>;
|
||||
export type MessageStateEnumType = z.infer<typeof MessageStateEnumSchema>;
|
||||
export type MonitoringCriterionEnumType = z.infer<typeof MonitoringCriterionEnumSchema>;
|
||||
export type NotifyEVChargingNeedsStatusEnumType = z.infer<
|
||||
typeof NotifyEVChargingNeedsStatusEnumSchema
|
||||
>;
|
||||
export type OCPIVersionNumberEnumType = z.infer<typeof OCPIVersionNumberSchema>;
|
||||
export type OCPPInterfaceEnumType = z.infer<typeof OCPPInterfaceEnumSchema>;
|
||||
export type OCPPTransportEnumType = z.infer<typeof OCPPTransportEnumSchema>;
|
||||
export type OCPPVersionEnumType = z.infer<typeof OCPPVersionEnumSchema>;
|
||||
export type PhaseEnumType = z.infer<typeof PhaseEnumSchema>;
|
||||
export type ReadingContextEnumType = z.infer<typeof ReadingContextEnumSchema>;
|
||||
export type ReasonEnumType = z.infer<typeof ReasonEnumSchema>;
|
||||
export type RecurrencyKindEnumType = z.infer<typeof RecurrencyKindEnumSchema>;
|
||||
export type RegistrationStatusEnumType = z.infer<typeof RegistrationStatusEnumSchema>;
|
||||
export type RequestStartStopStatusEnumType = z.infer<typeof RequestStartStopStatusEnumSchema>;
|
||||
export type ReservationUpdateStatusEnumType = z.infer<typeof ReservationUpdateStatusEnumSchema>;
|
||||
export type ReserveNowStatusEnumType = z.infer<typeof ReserveNowStatusEnumSchema>;
|
||||
export type ResetEnumType = z.infer<typeof ResetEnumSchema>;
|
||||
export type SendLocalListStatusEnumType = z.infer<typeof SendLocalListStatusEnumSchema>;
|
||||
export type SetMonitoringStatusEnumType = z.infer<typeof SetMonitoringStatusEnumSchema>;
|
||||
export type SetNetworkProfileStatusEnumType = z.infer<typeof SetNetworkProfileStatusEnumSchema>;
|
||||
export type SetVariableStatusEnumType = z.infer<typeof SetVariableStatusEnumSchema>;
|
||||
export type TariffSetStatusEnumType = z.infer<typeof TariffSetStatusEnumSchema>;
|
||||
export type TransactionEventEnumType = z.infer<typeof TransactionEventEnumSchema>;
|
||||
export type TriggerReasonEnumType = z.infer<typeof TriggerReasonEnumSchema>;
|
||||
export type UpdateEnumType = z.infer<typeof UpdateEnumSchema>;
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export class LocationHours {
|
||||
regularHours?: LocationRegularHours[] | null;
|
||||
twentyfourSeven!: boolean;
|
||||
exceptionalOpenings?: LocationExceptionalPeriod[] | null;
|
||||
exceptionalClosings?: LocationExceptionalPeriod[] | null;
|
||||
}
|
||||
|
||||
export class LocationRegularHours {
|
||||
weekday!: number;
|
||||
periodBegin!: string;
|
||||
periodEnd!: string;
|
||||
}
|
||||
|
||||
export class LocationExceptionalPeriod {
|
||||
periodBegin!: Date;
|
||||
periodEnd!: Date;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
export const PointSchema = z.object({
|
||||
type: z.literal('Point'),
|
||||
coordinates: z.array(z.number()), // [longitude, latitude], doesn't restrict to 2 elements bc Point type doesn't
|
||||
});
|
||||
|
||||
export type Point = z.infer<typeof PointSchema>;
|
||||
|
||||
export const LocationHoursSchema = z.any();
|
||||
|
||||
export const StatusInfoSchema = z.object({
|
||||
reasonCode: z.string(),
|
||||
additionalInfo: z.string().nullable().optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export type StatusInfo = z.infer<typeof StatusInfoSchema>;
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { MessageFormatEnumSchema } from './enums.js';
|
||||
|
||||
export const MessageContentSchema = z.object({
|
||||
format: MessageFormatEnumSchema,
|
||||
language: z.string().nullable().optional(),
|
||||
content: z.string(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export type MessageContent = z.infer<typeof MessageContentSchema>;
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { OCPIVersionNumberSchema } from './enums.js';
|
||||
|
||||
export const ImageSchema = z.object({
|
||||
url: z.string(),
|
||||
type: z.string(),
|
||||
category: z.string(),
|
||||
width: z.number().int().optional(),
|
||||
height: z.number().int().optional(),
|
||||
});
|
||||
|
||||
export const BusinessDetailsSchema = z.object({
|
||||
name: z.string(),
|
||||
website: z.string().optional(),
|
||||
logo: ImageSchema.optional(),
|
||||
});
|
||||
|
||||
export const CredentialRoleSchema = z.object({
|
||||
role: z.enum(['CPO', 'EMSP', 'HUB', 'NAP', 'NSP', 'SCSP']),
|
||||
businessDetails: BusinessDetailsSchema,
|
||||
});
|
||||
|
||||
export const VersionSchema = z.object({
|
||||
version: OCPIVersionNumberSchema,
|
||||
versionDetailsUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
export const EndpointSchema = z.object({
|
||||
identifier: z.string(),
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export const CredentialsSchema = z.object({
|
||||
versionsUrl: z.string(),
|
||||
token: z.string().optional(),
|
||||
certificateRef: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ServerProfileSchema = z.object({
|
||||
credentialsRole: CredentialRoleSchema,
|
||||
versionDetails: z.array(VersionSchema),
|
||||
versionEndpoints: z.record(z.string(), z.array(EndpointSchema)),
|
||||
});
|
||||
|
||||
export const PartnerProfileSchema = z.object({
|
||||
version: VersionSchema,
|
||||
serverCredentials: CredentialsSchema,
|
||||
roles: z.array(CredentialRoleSchema).optional(),
|
||||
credentials: CredentialsSchema.optional(),
|
||||
endpoints: z.array(EndpointSchema).optional(),
|
||||
});
|
||||
|
||||
export type Image = z.infer<typeof ImageSchema>;
|
||||
export type BusinessDetails = z.infer<typeof BusinessDetailsSchema>;
|
||||
export type CredentialRole = z.infer<typeof CredentialRoleSchema>;
|
||||
export type Version = z.infer<typeof VersionSchema>;
|
||||
export type Endpoint = z.infer<typeof EndpointSchema>;
|
||||
export type Credentials = z.infer<typeof CredentialsSchema>;
|
||||
export type ServerProfile = z.infer<typeof ServerProfileSchema>;
|
||||
export type PartnerProfile = z.infer<typeof PartnerProfileSchema>;
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { OCPP_CallAction, OCPPVersion } from '@ocpp/rpc/message.js';
|
||||
import { MessageOrigin, MessageState } from '@interfaces/messages/internal-types.js';
|
||||
|
||||
export const CallActionSchema = z.enum(OCPP_CallAction);
|
||||
export const MessageOriginSchema = z.enum(MessageOrigin);
|
||||
export const MessageStateSchema = z.enum(MessageState);
|
||||
export const OCPPVersionSchema = z.enum(OCPPVersion);
|
||||
|
||||
export type CallActionEnumType = z.infer<typeof CallActionSchema>;
|
||||
export type MessageOriginEnumType = z.infer<typeof MessageOriginSchema>;
|
||||
export type MessageStateEnumType = z.infer<typeof MessageStateSchema>;
|
||||
export type OCPPVersionEnumType = z.infer<typeof OCPPVersionSchema>;
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { CostKindEnumSchema } from './enums.js';
|
||||
|
||||
export const RelativeTimeIntervalSchema = z.object({
|
||||
start: z.number().int(),
|
||||
duration: z.number().int().nullable().optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export const CostSchema = z.object({
|
||||
costKind: CostKindEnumSchema,
|
||||
amount: z.number(),
|
||||
amountMultiplier: z.number().int().nullable().optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export const ConsumptionCostSchema = z.object({
|
||||
startValue: z.number(),
|
||||
cost: z.union([
|
||||
z.tuple([CostSchema]),
|
||||
z.tuple([CostSchema, CostSchema]),
|
||||
z.tuple([CostSchema, CostSchema, CostSchema]),
|
||||
]),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export const SalesTariffEntrySchema = z.object({
|
||||
relativeTimeInterval: RelativeTimeIntervalSchema,
|
||||
ePriceLevel: z.number().int().nullable().optional(),
|
||||
consumptionCost: z
|
||||
.union([
|
||||
z.tuple([ConsumptionCostSchema]),
|
||||
z.tuple([ConsumptionCostSchema, ConsumptionCostSchema]),
|
||||
z.tuple([ConsumptionCostSchema, ConsumptionCostSchema, ConsumptionCostSchema]),
|
||||
])
|
||||
.nullable()
|
||||
.optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export type SalesTariffEntry = z.infer<typeof SalesTariffEntrySchema>;
|
||||
export type RelativeTimeInterval = z.infer<typeof RelativeTimeIntervalSchema>;
|
||||
export type Cost = z.infer<typeof CostSchema>;
|
||||
export type ConsumptionCost = z.infer<typeof ConsumptionCostSchema>;
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
LocationEnumSchema,
|
||||
MeasurandEnumSchema,
|
||||
PhaseEnumSchema,
|
||||
ReadingContextEnumSchema,
|
||||
} from './enums.js';
|
||||
|
||||
export const UnitOfMeasureSchema = z.object({
|
||||
unit: z.string().nullable().optional(),
|
||||
multiplier: z.number().nullable().optional(),
|
||||
});
|
||||
|
||||
export const SignedMeterValueSchema = z.object({
|
||||
signedMeterData: z.string(),
|
||||
signingMethod: z.string(),
|
||||
encodingMethod: z.string(),
|
||||
publicKey: z.string(),
|
||||
});
|
||||
|
||||
export const SampledValueSchema = z.object({
|
||||
value: z.number(),
|
||||
context: ReadingContextEnumSchema.nullable().optional(),
|
||||
measurand: MeasurandEnumSchema.nullable().optional(),
|
||||
phase: PhaseEnumSchema.nullable().optional(),
|
||||
location: LocationEnumSchema.nullable().optional(),
|
||||
signedMeterValue: SignedMeterValueSchema.nullable().optional(),
|
||||
unitOfMeasure: UnitOfMeasureSchema.nullable().optional(),
|
||||
});
|
||||
|
||||
export type SampledValue = z.infer<typeof SampledValueSchema>;
|
||||
export type UnitOfMeasure = z.infer<typeof UnitOfMeasureSchema>;
|
||||
export type SignedMeterValue = z.infer<typeof SignedMeterValueSchema>;
|
||||
@@ -0,0 +1,110 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod/v4';
|
||||
import { MessageFormatEnumSchema } from './enums.js';
|
||||
|
||||
const customData = z.any().nullable().optional();
|
||||
|
||||
export const TaxRateSchema = z.object({
|
||||
type: z.string(),
|
||||
tax: z.number(),
|
||||
stack: z.number().int().nullable().optional(),
|
||||
customData,
|
||||
});
|
||||
|
||||
const taxRates = TaxRateSchema.array().min(1).max(5).nullable().optional();
|
||||
|
||||
export const TariffConditionsSchema = z.object({
|
||||
startTimeOfDay: z.string().nullable().optional(),
|
||||
endTimeOfDay: z.string().nullable().optional(),
|
||||
dayOfWeek: z.string().array().min(1).max(7).nullable().optional(),
|
||||
validFromDate: z.string().nullable().optional(),
|
||||
validToDate: z.string().nullable().optional(),
|
||||
evseKind: z.string().nullable().optional(),
|
||||
minEnergy: z.number().nullable().optional(),
|
||||
maxEnergy: z.number().nullable().optional(),
|
||||
minCurrent: z.number().nullable().optional(),
|
||||
maxCurrent: z.number().nullable().optional(),
|
||||
minPower: z.number().nullable().optional(),
|
||||
maxPower: z.number().nullable().optional(),
|
||||
minTime: z.number().nullable().optional(),
|
||||
maxTime: z.number().nullable().optional(),
|
||||
minChargingTime: z.number().nullable().optional(),
|
||||
maxChargingTime: z.number().nullable().optional(),
|
||||
minIdleTime: z.number().nullable().optional(),
|
||||
maxIdleTime: z.number().nullable().optional(),
|
||||
customData,
|
||||
});
|
||||
|
||||
export const TariffConditionsFixedSchema = z.object({
|
||||
startTimeOfDay: z.string().nullable().optional(),
|
||||
endTimeOfDay: z.string().nullable().optional(),
|
||||
dayOfWeek: z.string().array().min(1).max(7).nullable().optional(),
|
||||
validFromDate: z.string().nullable().optional(),
|
||||
validToDate: z.string().nullable().optional(),
|
||||
evseKind: z.string().nullable().optional(),
|
||||
paymentBrand: z.string().nullable().optional(),
|
||||
paymentRecognition: z.string().nullable().optional(),
|
||||
customData,
|
||||
});
|
||||
|
||||
export const TariffMessageContentSchema = z.object({
|
||||
format: MessageFormatEnumSchema,
|
||||
language: z.string().nullable().optional(),
|
||||
content: z.string(),
|
||||
customData,
|
||||
});
|
||||
|
||||
export const TariffEnergyPriceSchema = z.object({
|
||||
priceKwh: z.number(),
|
||||
conditions: TariffConditionsSchema.nullable().optional(),
|
||||
customData,
|
||||
});
|
||||
|
||||
export const TariffEnergySchema = z.object({
|
||||
prices: TariffEnergyPriceSchema.array().min(1),
|
||||
taxRates,
|
||||
customData,
|
||||
});
|
||||
|
||||
export const TariffTimePriceSchema = z.object({
|
||||
priceMinute: z.number(),
|
||||
conditions: TariffConditionsSchema.nullable().optional(),
|
||||
customData,
|
||||
});
|
||||
|
||||
export const TariffTimeSchema = z.object({
|
||||
prices: TariffTimePriceSchema.array().min(1),
|
||||
taxRates,
|
||||
customData,
|
||||
});
|
||||
|
||||
export const TariffFixedPriceSchema = z.object({
|
||||
priceFixed: z.number(),
|
||||
conditions: TariffConditionsFixedSchema.nullable().optional(),
|
||||
customData,
|
||||
});
|
||||
|
||||
export const TariffFixedSchema = z.object({
|
||||
prices: TariffFixedPriceSchema.array().min(1),
|
||||
taxRates,
|
||||
customData,
|
||||
});
|
||||
|
||||
export const PriceSchema = z.object({
|
||||
exclTax: z.number().nullable().optional(),
|
||||
inclTax: z.number().nullable().optional(),
|
||||
taxRates,
|
||||
customData,
|
||||
});
|
||||
|
||||
export type TaxRateType = z.infer<typeof TaxRateSchema>;
|
||||
export type TariffConditionsType = z.infer<typeof TariffConditionsSchema>;
|
||||
export type TariffConditionsFixedType = z.infer<typeof TariffConditionsFixedSchema>;
|
||||
export type MessageContentType = z.infer<typeof TariffMessageContentSchema>;
|
||||
export type TariffEnergyType = z.infer<typeof TariffEnergySchema>;
|
||||
export type TariffTimeType = z.infer<typeof TariffTimeSchema>;
|
||||
export type TariffFixedType = z.infer<typeof TariffFixedSchema>;
|
||||
export type PriceType = z.infer<typeof PriceSchema>;
|
||||
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ChargingStateEnumSchema, ReasonEnumSchema } from './enums.js';
|
||||
|
||||
export const TransactionLimitSchema = z.object({
|
||||
maxCost: z.number().optional().nullable(),
|
||||
maxEnergy: z.number().optional().nullable(),
|
||||
maxTime: z.number().int().optional().nullable(),
|
||||
maxSoC: z.number().int().min(0).max(100).optional().nullable(),
|
||||
});
|
||||
|
||||
export type TransactionLimit = z.infer<typeof TransactionLimitSchema>;
|
||||
|
||||
export const TransactionTypeSchema = z.object({
|
||||
transactionId: z.string(),
|
||||
chargingState: ChargingStateEnumSchema.nullable().optional(),
|
||||
timeSpentCharging: z.number().int().nullable().optional(),
|
||||
stoppedReason: ReasonEnumSchema.nullable().optional(),
|
||||
remoteStartId: z.number().int().nullable().optional(),
|
||||
tariffId: z.string().optional().nullable(),
|
||||
transactionLimit: TransactionLimitSchema.optional().nullable(),
|
||||
});
|
||||
|
||||
export type TransactionType = z.infer<typeof TransactionTypeSchema>;
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
export const AddressSchema = z.object({
|
||||
name: z.string(),
|
||||
address1: z.string(),
|
||||
address2: z.string().nullable().optional(),
|
||||
city: z.string(),
|
||||
postalCode: z.string().nullable().optional(),
|
||||
country: z.string(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export type AddressType = z.infer<typeof AddressSchema>;
|
||||
@@ -0,0 +1,55 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ChargingStationSchema } from './charging.station.dto.js';
|
||||
import { ComponentSchema } from './component.dto.js';
|
||||
import { EvseTypeSchema } from './evse.type.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { AttributeEnumSchema, DataEnumSchema, MutabilityEnumSchema } from './types/device.model.js';
|
||||
import { VariableSchema } from './variable.dto.js';
|
||||
|
||||
export const VariableAttributeSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
ocppConnectionName: z.string(),
|
||||
chargingStation: ChargingStationSchema,
|
||||
type: AttributeEnumSchema.nullable().optional(),
|
||||
dataType: DataEnumSchema,
|
||||
value: z.string().max(4000).nullable().optional(),
|
||||
mutability: MutabilityEnumSchema.nullable().optional(),
|
||||
persistent: z.boolean().default(false).nullable().optional(),
|
||||
constant: z.boolean().default(false).nullable().optional(),
|
||||
generatedAt: z.iso.datetime(),
|
||||
variable: VariableSchema,
|
||||
variableId: z.number().int().nullable().optional(),
|
||||
component: ComponentSchema,
|
||||
componentId: z.number().int().nullable().optional(),
|
||||
evse: EvseTypeSchema.optional(),
|
||||
evseDatabaseId: z.number().int().nullable().optional(),
|
||||
bootConfigId: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export const VariableAttributeProps = VariableAttributeSchema.keyof().enum;
|
||||
|
||||
export type VariableAttributeDto = z.infer<typeof VariableAttributeSchema>;
|
||||
|
||||
export const VariableAttributeCreateSchema = VariableAttributeSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
chargingStation: true,
|
||||
variable: true,
|
||||
component: true,
|
||||
evse: true,
|
||||
statuses: true,
|
||||
bootConfig: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type VariableAttributeCreate = z.infer<typeof VariableAttributeCreateSchema>;
|
||||
|
||||
export const variableAttributeSchemas = {
|
||||
VariableAttribute: VariableAttributeSchema,
|
||||
VariableAttributeCreate: VariableAttributeCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { DataEnumSchema } from './types/device.model.js';
|
||||
import { VariableSchema } from './variable.dto.js';
|
||||
|
||||
export const VariableCharacteristicsSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
unit: z.string().nullable().optional(),
|
||||
dataType: DataEnumSchema,
|
||||
minLimit: z.number().nullable().optional(), // DECIMAL in DB
|
||||
maxLimit: z.number().nullable().optional(), // DECIMAL in DB
|
||||
valuesList: z.string().max(4000).nullable().optional(),
|
||||
supportsMonitoring: z.boolean(),
|
||||
variable: VariableSchema,
|
||||
variableId: z.number().int().nullable().optional(),
|
||||
});
|
||||
|
||||
export const VariableCharacteristicsProps = VariableCharacteristicsSchema.keyof().enum;
|
||||
|
||||
export type VariableCharacteristicsDto = z.infer<typeof VariableCharacteristicsSchema>;
|
||||
|
||||
export const VariableCharacteristicsCreateSchema = VariableCharacteristicsSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
variable: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type VariableCharacteristicsCreate = z.infer<typeof VariableCharacteristicsCreateSchema>;
|
||||
|
||||
export const variableCharacteristicsSchemas = {
|
||||
VariableCharacteristics: VariableCharacteristicsSchema,
|
||||
VariableCharacteristicsCreate: VariableCharacteristicsCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
|
||||
export const VariableSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
name: z.string(),
|
||||
instance: z.string().nullable().optional(),
|
||||
customData: z.any().nullable().optional(),
|
||||
});
|
||||
|
||||
export const VariableProps = VariableSchema.keyof().enum;
|
||||
|
||||
export type VariableDto = z.infer<typeof VariableSchema>;
|
||||
|
||||
export const VariableCreateSchema = VariableSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
components: true,
|
||||
variableAttributes: true,
|
||||
variableCharacteristics: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type VariableCreate = z.infer<typeof VariableCreateSchema>;
|
||||
|
||||
export const variableSchemas = {
|
||||
Variable: VariableSchema,
|
||||
VariableCreate: VariableCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { ComponentSchema } from './component.dto.js';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { VariableSchema } from './variable.dto.js';
|
||||
import { EventNotificationEnumSchema, MonitorEnumSchema } from './types/enums.js';
|
||||
|
||||
export const VariableMonitoringSchema = BaseSchema.extend({
|
||||
databaseId: z.number().int(),
|
||||
id: z.number().int(),
|
||||
ocppConnectionName: z.string(),
|
||||
transaction: z.boolean(),
|
||||
value: z.number().int(),
|
||||
type: MonitorEnumSchema,
|
||||
severity: z.number().int(),
|
||||
variable: VariableSchema,
|
||||
variableId: z.number().int().nullable().optional(),
|
||||
component: ComponentSchema,
|
||||
componentId: z.number().int().nullable().optional(),
|
||||
eventNotificationType: EventNotificationEnumSchema.nullable().optional(),
|
||||
});
|
||||
|
||||
export const VariableMonitoringProps = VariableMonitoringSchema.keyof().enum;
|
||||
|
||||
export type VariableMonitoringDto = z.infer<typeof VariableMonitoringSchema>;
|
||||
|
||||
export const VariableMonitoringCreateSchema = VariableMonitoringSchema.omit({
|
||||
databaseId: true,
|
||||
tenant: true,
|
||||
variable: true,
|
||||
component: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type VariableMonitoringCreate = z.infer<typeof VariableMonitoringCreateSchema>;
|
||||
|
||||
export const variableMonitoringSchemas = {
|
||||
VariableMonitoring: VariableMonitoringSchema,
|
||||
VariableMonitoringCreate: VariableMonitoringCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { StatusInfoSchema } from './types/location.js';
|
||||
import { VariableMonitoringSchema } from './variable.monitoring.dto.js';
|
||||
|
||||
export const VariableMonitoringStatusSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
status: z.string(),
|
||||
statusInfo: StatusInfoSchema.nullable().optional(),
|
||||
variable: VariableMonitoringSchema,
|
||||
variableMonitoringId: z.number().int().nullable().optional(),
|
||||
});
|
||||
|
||||
export const VariableMonitoringStatusProps = VariableMonitoringStatusSchema.keyof().enum;
|
||||
|
||||
export type VariableMonitoringStatusDto = z.infer<typeof VariableMonitoringStatusSchema>;
|
||||
|
||||
export const VariableMonitoringStatusCreateSchema = VariableMonitoringStatusSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
variable: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type VariableMonitoringStatusCreate = z.infer<typeof VariableMonitoringStatusCreateSchema>;
|
||||
|
||||
export const variableMonitoringStatusSchemas = {
|
||||
VariableMonitoringStatus: VariableMonitoringStatusSchema,
|
||||
VariableMonitoringStatusCreate: VariableMonitoringStatusCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { z } from 'zod';
|
||||
import { BaseSchema } from './types/base.dto.js';
|
||||
import { StatusInfoSchema } from './types/location.js';
|
||||
import { VariableAttributeSchema } from './variable.attribute.dto.js';
|
||||
|
||||
export const VariableStatusSchema = BaseSchema.extend({
|
||||
id: z.number().int().optional(),
|
||||
value: z.string().max(4000),
|
||||
status: z.string(),
|
||||
statusInfo: StatusInfoSchema.nullable().optional(),
|
||||
variable: VariableAttributeSchema,
|
||||
variableAttributeId: z.number().int().nullable().optional(),
|
||||
});
|
||||
|
||||
export const VariableStatusProps = VariableStatusSchema.keyof().enum;
|
||||
|
||||
export type VariableStatusDto = z.infer<typeof VariableStatusSchema>;
|
||||
|
||||
export const VariableStatusCreateSchema = VariableStatusSchema.omit({
|
||||
id: true,
|
||||
tenant: true,
|
||||
variable: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type VariableStatusCreate = z.infer<typeof VariableStatusCreateSchema>;
|
||||
|
||||
export const variableStatusSchemas = {
|
||||
VariableStatus: VariableStatusSchema,
|
||||
VariableStatusCreate: VariableStatusCreateSchema,
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export interface IFileAccess {
|
||||
getFileURL(): string;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Buffer } from 'node:buffer';
|
||||
|
||||
export interface IFileStorage {
|
||||
/**
|
||||
*
|
||||
* @param fileName Name of the file
|
||||
* @param content File content
|
||||
* @param filePath The path of the file, if not in root. Used as the bucket name for S3.
|
||||
*
|
||||
* @returns The ID of the file
|
||||
*/
|
||||
saveFile(fileName: string, content: Buffer, filePath?: string): Promise<string>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id The ID of the file
|
||||
* @param filePath The path of the file, if not included in the ID. Used as the bucket name for S3.
|
||||
*
|
||||
* @returns The file content
|
||||
*/
|
||||
getFile(id: string, filePath?: string): Promise<string | undefined>;
|
||||
|
||||
/**
|
||||
* Checks whether a file or directory exists at the given path.
|
||||
*
|
||||
* @param path The file or directory path. For object storage (S3, GCP), treated as an object key or prefix.
|
||||
*/
|
||||
exists(path: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Creates a directory at the given path.
|
||||
* For object storage backends (S3, GCP) this may be a no-op since directories are implicit.
|
||||
*
|
||||
* @param path The directory path to create
|
||||
* @param options Optional options, e.g. { recursive: true }
|
||||
*/
|
||||
createDirectory(path: string, options?: { recursive?: boolean }): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes a file or directory at the given path.
|
||||
* For object storage backends (S3, GCP), recursive removal deletes all objects sharing the path prefix.
|
||||
*
|
||||
* @param path The path to remove
|
||||
* @param options Optional options, e.g. { recursive: true, force: true }
|
||||
*/
|
||||
deleteFile(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export type { IFileAccess } from './fileAccess.js';
|
||||
export type { IFileStorage } from './fileStorage.js';
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import type { ILogObj } from 'tslog';
|
||||
import { Logger } from 'tslog';
|
||||
import type { IConnectionManager } from './IConnectionManager.js';
|
||||
|
||||
/**
|
||||
* Abstract base class for managing a message transport connection.
|
||||
*
|
||||
* Implementations are responsible for establishing, maintaining, and closing
|
||||
* connections to a specific transport backend (e.g. RabbitMQ, Kafka).
|
||||
*
|
||||
* Emits:
|
||||
* - `connected` when a connection is established (with the connection object as argument)
|
||||
* - `disconnected` when the connection is lost
|
||||
* - `error` on connection errors
|
||||
*
|
||||
* @template TConnection The transport-specific connection type returned by {@link connect}.
|
||||
*/
|
||||
export abstract class AbstractConnectionManager<TConnection = unknown>
|
||||
extends EventEmitter
|
||||
implements IConnectionManager
|
||||
{
|
||||
protected _logger: Logger<ILogObj>;
|
||||
|
||||
public state: string = 'disconnected';
|
||||
|
||||
constructor(logger?: Logger<ILogObj>) {
|
||||
super();
|
||||
this._logger = logger
|
||||
? logger.getSubLogger({ name: this.constructor.name })
|
||||
: new Logger<ILogObj>({ name: this.constructor.name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a connection to the transport backend.
|
||||
* Implementations should handle in-progress connection attempts and
|
||||
* return the existing connection if already connected.
|
||||
*/
|
||||
abstract connect(): Promise<TConnection>;
|
||||
|
||||
/**
|
||||
* Gracefully closes the connection.
|
||||
*/
|
||||
abstract close(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns true if there is an active connection.
|
||||
*/
|
||||
abstract isConnected(): boolean;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { ILogObj } from 'tslog';
|
||||
import { Logger } from 'tslog';
|
||||
import type { IMessage } from './Message.js';
|
||||
import type { IMessageHandler } from './MessageHandler.js';
|
||||
import { OcppError } from '@ocpp/rpc/message.js';
|
||||
import type { OcppRequest, OcppResponse } from '@ocpp/internal-types.js';
|
||||
import type { CallAction } from '@ocpp/rpc/message.js';
|
||||
import type { IModule } from '@interfaces/modules/Module.js';
|
||||
import type { HandlerProperties } from '@interfaces/messages/internal-types.js';
|
||||
|
||||
/**
|
||||
* Abstract class implementing {@link IMessageHandler}.
|
||||
*/
|
||||
export abstract class AbstractMessageHandler implements IMessageHandler {
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
|
||||
protected _module?: IModule;
|
||||
protected _logger: Logger<ILogObj>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param config The system configuration.
|
||||
* @param logger [Optional] The logger to use.
|
||||
*/
|
||||
constructor(logger?: Logger<ILogObj>, module?: IModule) {
|
||||
this._module = module;
|
||||
this._logger = logger
|
||||
? logger.getSubLogger({ name: this.constructor.name })
|
||||
: new Logger<ILogObj>({ name: this.constructor.name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter & Setter
|
||||
*/
|
||||
|
||||
get module(): IModule | undefined {
|
||||
return this._module;
|
||||
}
|
||||
set module(value: IModule | undefined) {
|
||||
this._module = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods
|
||||
*/
|
||||
|
||||
async handle(
|
||||
message: IMessage<OcppRequest | OcppResponse | OcppError>,
|
||||
props?: HandlerProperties,
|
||||
): Promise<void> {
|
||||
await this._module?.handle(message, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Methods
|
||||
*/
|
||||
|
||||
abstract subscribe(
|
||||
identifier: string,
|
||||
actions?: CallAction[],
|
||||
filter?: { [k: string]: string },
|
||||
): Promise<boolean>;
|
||||
abstract unsubscribe(identifier: string): Promise<boolean>;
|
||||
abstract shutdown(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ILogObj } from 'tslog';
|
||||
import { Logger } from 'tslog';
|
||||
|
||||
export abstract class AbstractMessageSender {
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
|
||||
protected _logger: Logger<ILogObj>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param logger [Optional] The logger to use.
|
||||
*/
|
||||
constructor(logger?: Logger<ILogObj>) {
|
||||
this._logger = logger
|
||||
? logger.getSubLogger({ name: this.constructor.name })
|
||||
: new Logger<ILogObj>({ name: this.constructor.name });
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user