setup unifi wlans
This commit is contained in:
@@ -76,4 +76,14 @@ Purpose: make AI coding agents immediately productive in this repository by desc
|
||||
3. `API_ROUTES.md` — comprehensive documentation of all API routes, including method, path, auth requirements, permissions, request/response examples.
|
||||
Always verify that new routes have their required permissions listed in `PermissionNodes.ts`, that `PERMISSIONS.md` tables match the TS file exactly, and that `API_ROUTES.md` includes full documentation for every mounted route. Run through all three files at the end of any route or permission change to catch discrepancies.
|
||||
|
||||
- **Field-level permission gating (processObjectValuePerms)**: Some routes use `processObjectValuePerms` from `src/modules/permission-utils/processObjectPermissions.ts` to filter response objects on a per-field basis. When this pattern is used, every key of the response object becomes a permission node in the form `<scope>.<field>` (e.g., `unifi.site.wifi.read.passphrase`). Only fields whose corresponding permission the user holds are included in the response.
|
||||
|
||||
**When documenting a route that uses field-level gating, you must:**
|
||||
1. Note in `API_ROUTES.md` that the route uses field-level gating, explain the behaviour, and list every `<scope>.<field>` permission node in a collapsible table.
|
||||
2. Add a `unifi.site.wifi.read`-style parent permission node in `PermissionNodes.ts` with a `fieldLevelPermissions` array listing every `<scope>.<field>` node.
|
||||
3. Add matching rows/notes to `PERMISSIONS.md` including the full list of field-level nodes.
|
||||
|
||||
**Current routes using field-level gating:**
|
||||
- `GET /v1/unifi/site/:id/wifi` — scope `unifi.site.wifi.read`, gates every field on the `WlanConf` object.
|
||||
|
||||
If anything here is unclear or you'd like more examples (e.g., a walk-through editing a controller + manager + test run), tell me which area to expand and I'll iterate.
|
||||
|
||||
+1353
-5
File diff suppressed because it is too large
Load Diff
+51
-10
@@ -27,21 +27,25 @@ The permission validator supports special tokens for flexible permission managem
|
||||
| ------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `company.fetch` | Fetch a single company | [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts) |
|
||||
| `company.fetch.address` | View company address information (requires `company.fetch` as well) | [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts) |
|
||||
| `company.fetch.contacts` | View all company contacts (requires `company.fetch` as well) | [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts) |
|
||||
| `company.fetch.many` | Fetch multiple companies | [src/api/companies/fetchAll.ts](src/api/companies/fetchAll.ts) |
|
||||
| `company.fetch.configurations` | Fetch company configurations (requires `company.fetch` as well) | [src/api/companies/[id]/configurations.ts](src/api/companies/[id]/configurations.ts) |
|
||||
|
||||
### Credential Permissions
|
||||
|
||||
| Permission Node | Description | Used In |
|
||||
| ------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `credential.create` | Create a new credential | [src/api/credentials/create.ts](src/api/credentials/create.ts) |
|
||||
| `credential.fetch` | Fetch a single credential | [src/api/credentials/fetch.ts](src/api/credentials/fetch.ts) |
|
||||
| `credential.fetch.many` | Fetch multiple credentials | [src/api/credentials/fetchByCompany.ts](src/api/credentials/fetchByCompany.ts) |
|
||||
| `credential.update` | Update a credential | [src/api/credentials/update.ts](src/api/credentials/update.ts) |
|
||||
| `credential.delete` | Delete a credential | [src/api/credentials/delete.ts](src/api/credentials/delete.ts) |
|
||||
| `credential.fields.fetch` | Fetch credential fields (requires `credential.fetch` as well) | [src/api/credentials/fetchFields.ts](src/api/credentials/fetchFields.ts) |
|
||||
| `credential.fields.update` | Update credential fields (requires `credential.update` as well) | [src/api/credentials/updateFields.ts](src/api/credentials/updateFields.ts) |
|
||||
| `credential.secure_values.read` | Read secure values of a credential (requires `credential.fetch` as well) | [src/api/credentials/readSecureValues.ts](src/api/credentials/readSecureValues.ts), [src/api/credentials/readSecureValue.ts](src/api/credentials/readSecureValue.ts) |
|
||||
| Permission Node | Description | Used In |
|
||||
| ----------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `credential.create` | Create a new credential | [src/api/credentials/create.ts](src/api/credentials/create.ts) |
|
||||
| `credential.fetch` | Fetch a single credential | [src/api/credentials/fetch.ts](src/api/credentials/fetch.ts) |
|
||||
| `credential.fetch.many` | Fetch multiple credentials | [src/api/credentials/fetchByCompany.ts](src/api/credentials/fetchByCompany.ts) |
|
||||
| `credential.update` | Update a credential | [src/api/credentials/update.ts](src/api/credentials/update.ts) |
|
||||
| `credential.delete` | Delete a credential | [src/api/credentials/delete.ts](src/api/credentials/delete.ts) |
|
||||
| `credential.fields.fetch` | Fetch credential fields (requires `credential.fetch` as well) | [src/api/credentials/fetchFields.ts](src/api/credentials/fetchFields.ts) |
|
||||
| `credential.fields.update` | Update credential fields (requires `credential.update` as well) | [src/api/credentials/updateFields.ts](src/api/credentials/updateFields.ts) |
|
||||
| `credential.secure_values.read` | Read secure values of a credential (requires `credential.fetch` as well) | [src/api/credentials/readSecureValues.ts](src/api/credentials/readSecureValues.ts), [src/api/credentials/readSecureValue.ts](src/api/credentials/readSecureValue.ts) |
|
||||
| `credential.sub_credentials.fetch` | Fetch sub-credentials of a parent credential (requires `credential.fetch` as well) | [src/api/credentials/fetchSubCredentials.ts](src/api/credentials/fetchSubCredentials.ts) |
|
||||
| `credential.sub_credentials.create` | Create a sub-credential on a parent credential (requires `credential.fetch` as well) | [src/api/credentials/addSubCredential.ts](src/api/credentials/addSubCredential.ts) |
|
||||
| `credential.sub_credentials.delete` | Remove a sub-credential from a parent credential (requires `credential.fetch` as well) | [src/api/credentials/removeSubCredential.ts](src/api/credentials/removeSubCredential.ts) |
|
||||
|
||||
### Credential Type Permissions
|
||||
|
||||
@@ -111,6 +115,43 @@ Admin-specific UI permissions that control visibility and data loading for admin
|
||||
- **Combine with API permissions**: A user with an admin UI permission should also have the corresponding API permission (e.g., `role.list`) to actually load data.
|
||||
- **Use wildcards for flexibility**: Grant `ui.navigation.*.view` to allow all navigation sections.
|
||||
|
||||
### UniFi Permissions
|
||||
|
||||
Permissions for accessing and managing UniFi network infrastructure. The `unifi.access` permission is a gate permission required for **all** UniFi routes.
|
||||
|
||||
| Permission Node | Description | Used In | Dependencies |
|
||||
| ------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- |
|
||||
| `unifi.access` | Gate permission for the entire UniFi API — required for all | [src/api/unifi/sites/fetchAll.ts](src/api/unifi/sites/fetchAll.ts), [src/api/unifi/sites/sync.ts](src/api/unifi/sites/sync.ts), [src/api/unifi/site/fetch.ts](src/api/unifi/site/fetch.ts), [src/api/unifi/site/overview.ts](src/api/unifi/site/overview.ts), [src/api/unifi/site/devices.ts](src/api/unifi/site/devices.ts), [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts), [src/api/unifi/site/wifi/update.ts](src/api/unifi/site/wifi/update.ts), [src/api/unifi/site/networks.ts](src/api/unifi/site/networks.ts), [src/api/unifi/site/link.ts](src/api/unifi/site/link.ts), [src/api/unifi/site/unlink.ts](src/api/unifi/site/unlink.ts), [src/api/companies/[id]/unifiSites.ts](src/api/companies/[id]/unifiSites.ts), [src/api/unifi/sites/create.ts](src/api/unifi/sites/create.ts) | |
|
||||
| `unifi.sites.create` | Create a new site on the UniFi controller | [src/api/unifi/sites/create.ts](src/api/unifi/sites/create.ts) | `unifi.access` |
|
||||
| `unifi.sites.fetch` | Fetch a single UniFi site | [src/api/unifi/site/fetch.ts](src/api/unifi/site/fetch.ts) | `unifi.access` |
|
||||
| `unifi.sites.fetch.many` | Fetch all UniFi sites | [src/api/unifi/sites/fetchAll.ts](src/api/unifi/sites/fetchAll.ts) | `unifi.access` |
|
||||
| `unifi.sites.sync` | Sync sites from the UniFi controller into the database | [src/api/unifi/sites/sync.ts](src/api/unifi/sites/sync.ts) | `unifi.access` |
|
||||
| `unifi.sites.link` | Link or unlink a UniFi site to/from a company | [src/api/unifi/site/link.ts](src/api/unifi/site/link.ts), [src/api/unifi/site/unlink.ts](src/api/unifi/site/unlink.ts) | `unifi.access` |
|
||||
| `unifi.site.overview` | View live site overview from the UniFi controller | [src/api/unifi/site/overview.ts](src/api/unifi/site/overview.ts) | `unifi.access` |
|
||||
| `unifi.site.devices` | View live device list from the UniFi controller | [src/api/unifi/site/devices.ts](src/api/unifi/site/devices.ts) | `unifi.access` |
|
||||
| `unifi.site.wifi` | View WiFi networks (WLANs) from the UniFi controller | [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts) | `unifi.access` |
|
||||
| `unifi.site.wifi.read` | Field-level gate for WiFi response data (see note below) | [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts) | `unifi.access`, `unifi.site.wifi` |
|
||||
| `unifi.site.wifi.read.<field>` | Read a specific field from WiFi response (e.g. `unifi.site.wifi.read.passphrase`) | [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts) | `unifi.access`, `unifi.site.wifi`, `unifi.site.wifi.read` |
|
||||
| `unifi.site.wifi.update` | Update a WiFi network on the UniFi controller | [src/api/unifi/site/wifi/update.ts](src/api/unifi/site/wifi/update.ts) | `unifi.access`, `unifi.site.wifi` |
|
||||
|
||||
#### Field-Level Permission Gating (`unifi.site.wifi.read`)
|
||||
|
||||
The WiFi fetch route uses `processObjectValuePerms` to filter each WLAN object on a per-field basis. For every key on the `WlanConf` response object, the system checks `unifi.site.wifi.read.<key>`. Only fields the user has permission for are included in the response. Use `unifi.site.wifi.read.*` to grant access to all fields.
|
||||
|
||||
**Available field-level nodes:**
|
||||
|
||||
`unifi.site.wifi.read.id`, `unifi.site.wifi.read.name`, `unifi.site.wifi.read.siteId`, `unifi.site.wifi.read.enabled`, `unifi.site.wifi.read.security`, `unifi.site.wifi.read.wpaMode`, `unifi.site.wifi.read.wpaEnc`, `unifi.site.wifi.read.wpa3Support`, `unifi.site.wifi.read.wpa3Transition`, `unifi.site.wifi.read.wpa3FastRoaming`, `unifi.site.wifi.read.wpa3Enhanced192`, `unifi.site.wifi.read.passphrase`, `unifi.site.wifi.read.passphraseAutogenerated`, `unifi.site.wifi.read.hideSSID`, `unifi.site.wifi.read.isGuest`, `unifi.site.wifi.read.band`, `unifi.site.wifi.read.bands`, `unifi.site.wifi.read.networkconfId`, `unifi.site.wifi.read.usergroupId`, `unifi.site.wifi.read.apGroupIds`, `unifi.site.wifi.read.apGroupMode`, `unifi.site.wifi.read.pmfMode`, `unifi.site.wifi.read.groupRekey`, `unifi.site.wifi.read.dtimMode`, `unifi.site.wifi.read.dtimNg`, `unifi.site.wifi.read.dtimNa`, `unifi.site.wifi.read.dtim6e`, `unifi.site.wifi.read.l2Isolation`, `unifi.site.wifi.read.fastRoamingEnabled`, `unifi.site.wifi.read.bssTransition`, `unifi.site.wifi.read.uapsdEnabled`, `unifi.site.wifi.read.iappEnabled`, `unifi.site.wifi.read.proxyArp`, `unifi.site.wifi.read.mcastenhanceEnabled`, `unifi.site.wifi.read.macFilterEnabled`, `unifi.site.wifi.read.macFilterPolicy`, `unifi.site.wifi.read.macFilterList`, `unifi.site.wifi.read.radiusDasEnabled`, `unifi.site.wifi.read.radiusMacAuthEnabled`, `unifi.site.wifi.read.radiusMacaclFormat`, `unifi.site.wifi.read.minrateSettingPreference`, `unifi.site.wifi.read.minrateNgEnabled`, `unifi.site.wifi.read.minrateNgDataRateKbps`, `unifi.site.wifi.read.minrateNgAdvertisingRates`, `unifi.site.wifi.read.minrateNaEnabled`, `unifi.site.wifi.read.minrateNaDataRateKbps`, `unifi.site.wifi.read.minrateNaAdvertisingRates`, `unifi.site.wifi.read.settingPreference`, `unifi.site.wifi.read.no2ghzOui`, `unifi.site.wifi.read.privatePreSharedKeysEnabled`, `unifi.site.wifi.read.privatePreSharedKeys`, `unifi.site.wifi.read.saeGroups`, `unifi.site.wifi.read.saePsk`, `unifi.site.wifi.read.schedule`, `unifi.site.wifi.read.scheduleWithDuration`, `unifi.site.wifi.read.bcFilterList`, `unifi.site.wifi.read.externalId`
|
||||
| `unifi.site.networks` | View network configurations from the UniFi controller | [src/api/unifi/site/networks.ts](src/api/unifi/site/networks.ts) | `unifi.access` |
|
||||
| `unifi.site.wlan-groups` | View WLAN groups (AP broadcasting groups) from the UniFi controller for a site | [src/api/unifi/site/wlanGroups.ts](src/api/unifi/site/wlanGroups.ts), [src/api/unifi/site/wlanGroupsCreate.ts](src/api/unifi/site/wlanGroupsCreate.ts) | `unifi.access` |
|
||||
| `unifi.site.wlan-groups.create` | Create a new WLAN group (AP broadcasting group) on the UniFi controller | [src/api/unifi/site/wlanGroupsCreate.ts](src/api/unifi/site/wlanGroupsCreate.ts) | `unifi.access`, `unifi.site.wlan-groups` |
|
||||
| `unifi.site.access-points` | View access points (UAPs only) from the UniFi controller for a site | [src/api/unifi/site/accessPoints.ts](src/api/unifi/site/accessPoints.ts) | `unifi.access` |
|
||||
| `unifi.site.ap-groups` | View AP groups — shows which APs are grouped together for SSID broadcasting | [src/api/unifi/site/apGroups.ts](src/api/unifi/site/apGroups.ts) | `unifi.access` |
|
||||
| `unifi.site.wifi-limits` | View WiFi SSID limits per AP per radio band | [src/api/unifi/site/wifiLimits.ts](src/api/unifi/site/wifiLimits.ts) | `unifi.access` |
|
||||
| `unifi.site.speed-profiles` | View speed limit profiles (user groups) from the UniFi controller | [src/api/unifi/site/speedProfilesFetchAll.ts](src/api/unifi/site/speedProfilesFetchAll.ts), [src/api/unifi/site/speedProfilesCreate.ts](src/api/unifi/site/speedProfilesCreate.ts) | `unifi.access` |
|
||||
| `unifi.site.speed-profiles.create` | Create a new speed limit profile (user group) on the UniFi controller | [src/api/unifi/site/speedProfilesCreate.ts](src/api/unifi/site/speedProfilesCreate.ts) | `unifi.access`, `unifi.site.speed-profiles` |
|
||||
| `unifi.site.wifi.ppsk` | View private pre-shared keys (PPSKs) for a specific WiFi network | [src/api/unifi/site/wifi/ppskFetchAll.ts](src/api/unifi/site/wifi/ppskFetchAll.ts), [src/api/unifi/site/wifi/ppskCreate.ts](src/api/unifi/site/wifi/ppskCreate.ts) | `unifi.access`, `unifi.site.wifi` |
|
||||
| `unifi.site.wifi.ppsk.create` | Create a private pre-shared key on a specific WiFi network | [src/api/unifi/site/wifi/ppskCreate.ts](src/api/unifi/site/wifi/ppskCreate.ts) | `unifi.access`, `unifi.site.wifi`, `unifi.site.wifi.ppsk` |
|
||||
|
||||
## Permission Issuers
|
||||
|
||||
Permissions can be issued by different sources:
|
||||
|
||||
@@ -32,6 +32,11 @@ export type User = Prisma.UserModel
|
||||
*
|
||||
*/
|
||||
export type Role = Prisma.RoleModel
|
||||
/**
|
||||
* Model UnifiSite
|
||||
*
|
||||
*/
|
||||
export type UnifiSite = Prisma.UnifiSiteModel
|
||||
/**
|
||||
* Model Company
|
||||
*
|
||||
|
||||
@@ -54,6 +54,11 @@ export type User = Prisma.UserModel
|
||||
*
|
||||
*/
|
||||
export type Role = Prisma.RoleModel
|
||||
/**
|
||||
* Model UnifiSite
|
||||
*
|
||||
*/
|
||||
export type UnifiSite = Prisma.UnifiSiteModel
|
||||
/**
|
||||
* Model Company
|
||||
*
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -387,6 +387,7 @@ export const ModelName = {
|
||||
Session: 'Session',
|
||||
User: 'User',
|
||||
Role: 'Role',
|
||||
UnifiSite: 'UnifiSite',
|
||||
Company: 'Company',
|
||||
CredentialType: 'CredentialType',
|
||||
SecureValue: 'SecureValue',
|
||||
@@ -406,7 +407,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
omit: GlobalOmitOptions
|
||||
}
|
||||
meta: {
|
||||
modelProps: "session" | "user" | "role" | "company" | "credentialType" | "secureValue" | "credential"
|
||||
modelProps: "session" | "user" | "role" | "unifiSite" | "company" | "credentialType" | "secureValue" | "credential"
|
||||
txIsolationLevel: TransactionIsolationLevel
|
||||
}
|
||||
model: {
|
||||
@@ -632,6 +633,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
}
|
||||
}
|
||||
}
|
||||
UnifiSite: {
|
||||
payload: Prisma.$UnifiSitePayload<ExtArgs>
|
||||
fields: Prisma.UnifiSiteFieldRefs
|
||||
operations: {
|
||||
findUnique: {
|
||||
args: Prisma.UnifiSiteFindUniqueArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload> | null
|
||||
}
|
||||
findUniqueOrThrow: {
|
||||
args: Prisma.UnifiSiteFindUniqueOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload>
|
||||
}
|
||||
findFirst: {
|
||||
args: Prisma.UnifiSiteFindFirstArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload> | null
|
||||
}
|
||||
findFirstOrThrow: {
|
||||
args: Prisma.UnifiSiteFindFirstOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload>
|
||||
}
|
||||
findMany: {
|
||||
args: Prisma.UnifiSiteFindManyArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload>[]
|
||||
}
|
||||
create: {
|
||||
args: Prisma.UnifiSiteCreateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload>
|
||||
}
|
||||
createMany: {
|
||||
args: Prisma.UnifiSiteCreateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
createManyAndReturn: {
|
||||
args: Prisma.UnifiSiteCreateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload>[]
|
||||
}
|
||||
delete: {
|
||||
args: Prisma.UnifiSiteDeleteArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload>
|
||||
}
|
||||
update: {
|
||||
args: Prisma.UnifiSiteUpdateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload>
|
||||
}
|
||||
deleteMany: {
|
||||
args: Prisma.UnifiSiteDeleteManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateMany: {
|
||||
args: Prisma.UnifiSiteUpdateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateManyAndReturn: {
|
||||
args: Prisma.UnifiSiteUpdateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload>[]
|
||||
}
|
||||
upsert: {
|
||||
args: Prisma.UnifiSiteUpsertArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$UnifiSitePayload>
|
||||
}
|
||||
aggregate: {
|
||||
args: Prisma.UnifiSiteAggregateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.AggregateUnifiSite>
|
||||
}
|
||||
groupBy: {
|
||||
args: Prisma.UnifiSiteGroupByArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.UnifiSiteGroupByOutputType>[]
|
||||
}
|
||||
count: {
|
||||
args: Prisma.UnifiSiteCountArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.UnifiSiteCountAggregateOutputType> | number
|
||||
}
|
||||
}
|
||||
}
|
||||
Company: {
|
||||
payload: Prisma.$CompanyPayload<ExtArgs>
|
||||
fields: Prisma.CompanyFieldRefs
|
||||
@@ -1009,6 +1084,18 @@ export const RoleScalarFieldEnum = {
|
||||
export type RoleScalarFieldEnum = (typeof RoleScalarFieldEnum)[keyof typeof RoleScalarFieldEnum]
|
||||
|
||||
|
||||
export const UnifiSiteScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
siteId: 'siteId',
|
||||
companyId: 'companyId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
|
||||
export type UnifiSiteScalarFieldEnum = (typeof UnifiSiteScalarFieldEnum)[keyof typeof UnifiSiteScalarFieldEnum]
|
||||
|
||||
|
||||
export const CompanyScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
@@ -1051,6 +1138,7 @@ export const CredentialScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
notes: 'notes',
|
||||
subCredentialOfId: 'subCredentialOfId',
|
||||
typeId: 'typeId',
|
||||
fields: 'fields',
|
||||
companyId: 'companyId',
|
||||
@@ -1281,6 +1369,7 @@ export type GlobalOmitConfig = {
|
||||
session?: Prisma.SessionOmit
|
||||
user?: Prisma.UserOmit
|
||||
role?: Prisma.RoleOmit
|
||||
unifiSite?: Prisma.UnifiSiteOmit
|
||||
company?: Prisma.CompanyOmit
|
||||
credentialType?: Prisma.CredentialTypeOmit
|
||||
secureValue?: Prisma.SecureValueOmit
|
||||
|
||||
@@ -54,6 +54,7 @@ export const ModelName = {
|
||||
Session: 'Session',
|
||||
User: 'User',
|
||||
Role: 'Role',
|
||||
UnifiSite: 'UnifiSite',
|
||||
Company: 'Company',
|
||||
CredentialType: 'CredentialType',
|
||||
SecureValue: 'SecureValue',
|
||||
@@ -118,6 +119,18 @@ export const RoleScalarFieldEnum = {
|
||||
export type RoleScalarFieldEnum = (typeof RoleScalarFieldEnum)[keyof typeof RoleScalarFieldEnum]
|
||||
|
||||
|
||||
export const UnifiSiteScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
siteId: 'siteId',
|
||||
companyId: 'companyId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
|
||||
export type UnifiSiteScalarFieldEnum = (typeof UnifiSiteScalarFieldEnum)[keyof typeof UnifiSiteScalarFieldEnum]
|
||||
|
||||
|
||||
export const CompanyScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
@@ -160,6 +173,7 @@ export const CredentialScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
notes: 'notes',
|
||||
subCredentialOfId: 'subCredentialOfId',
|
||||
typeId: 'typeId',
|
||||
fields: 'fields',
|
||||
companyId: 'companyId',
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
export type * from './models/Session.ts'
|
||||
export type * from './models/User.ts'
|
||||
export type * from './models/Role.ts'
|
||||
export type * from './models/UnifiSite.ts'
|
||||
export type * from './models/Company.ts'
|
||||
export type * from './models/CredentialType.ts'
|
||||
export type * from './models/SecureValue.ts'
|
||||
|
||||
@@ -225,6 +225,7 @@ export type CompanyWhereInput = {
|
||||
createdAt?: Prisma.DateTimeFilter<"Company"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string
|
||||
credentials?: Prisma.CredentialListRelationFilter
|
||||
unifiSites?: Prisma.UnifiSiteListRelationFilter
|
||||
}
|
||||
|
||||
export type CompanyOrderByWithRelationInput = {
|
||||
@@ -235,6 +236,7 @@ export type CompanyOrderByWithRelationInput = {
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
credentials?: Prisma.CredentialOrderByRelationAggregateInput
|
||||
unifiSites?: Prisma.UnifiSiteOrderByRelationAggregateInput
|
||||
}
|
||||
|
||||
export type CompanyWhereUniqueInput = Prisma.AtLeast<{
|
||||
@@ -248,6 +250,7 @@ export type CompanyWhereUniqueInput = Prisma.AtLeast<{
|
||||
createdAt?: Prisma.DateTimeFilter<"Company"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string
|
||||
credentials?: Prisma.CredentialListRelationFilter
|
||||
unifiSites?: Prisma.UnifiSiteListRelationFilter
|
||||
}, "id" | "cw_CompanyId" | "cw_Identifier">
|
||||
|
||||
export type CompanyOrderByWithAggregationInput = {
|
||||
@@ -284,6 +287,7 @@ export type CompanyCreateInput = {
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
credentials?: Prisma.CredentialCreateNestedManyWithoutCompanyInput
|
||||
unifiSites?: Prisma.UnifiSiteCreateNestedManyWithoutCompanyInput
|
||||
}
|
||||
|
||||
export type CompanyUncheckedCreateInput = {
|
||||
@@ -294,6 +298,7 @@ export type CompanyUncheckedCreateInput = {
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
credentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutCompanyInput
|
||||
unifiSites?: Prisma.UnifiSiteUncheckedCreateNestedManyWithoutCompanyInput
|
||||
}
|
||||
|
||||
export type CompanyUpdateInput = {
|
||||
@@ -304,6 +309,7 @@ export type CompanyUpdateInput = {
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
credentials?: Prisma.CredentialUpdateManyWithoutCompanyNestedInput
|
||||
unifiSites?: Prisma.UnifiSiteUpdateManyWithoutCompanyNestedInput
|
||||
}
|
||||
|
||||
export type CompanyUncheckedUpdateInput = {
|
||||
@@ -314,6 +320,7 @@ export type CompanyUncheckedUpdateInput = {
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
credentials?: Prisma.CredentialUncheckedUpdateManyWithoutCompanyNestedInput
|
||||
unifiSites?: Prisma.UnifiSiteUncheckedUpdateManyWithoutCompanyNestedInput
|
||||
}
|
||||
|
||||
export type CompanyCreateManyInput = {
|
||||
@@ -343,6 +350,11 @@ export type CompanyUncheckedUpdateManyInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
export type CompanyNullableScalarRelationFilter = {
|
||||
is?: Prisma.CompanyWhereInput | null
|
||||
isNot?: Prisma.CompanyWhereInput | null
|
||||
}
|
||||
|
||||
export type CompanyCountOrderByAggregateInput = {
|
||||
id?: Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
@@ -383,6 +395,22 @@ export type CompanyScalarRelationFilter = {
|
||||
isNot?: Prisma.CompanyWhereInput
|
||||
}
|
||||
|
||||
export type CompanyCreateNestedOneWithoutUnifiSitesInput = {
|
||||
create?: Prisma.XOR<Prisma.CompanyCreateWithoutUnifiSitesInput, Prisma.CompanyUncheckedCreateWithoutUnifiSitesInput>
|
||||
connectOrCreate?: Prisma.CompanyCreateOrConnectWithoutUnifiSitesInput
|
||||
connect?: Prisma.CompanyWhereUniqueInput
|
||||
}
|
||||
|
||||
export type CompanyUpdateOneWithoutUnifiSitesNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.CompanyCreateWithoutUnifiSitesInput, Prisma.CompanyUncheckedCreateWithoutUnifiSitesInput>
|
||||
connectOrCreate?: Prisma.CompanyCreateOrConnectWithoutUnifiSitesInput
|
||||
upsert?: Prisma.CompanyUpsertWithoutUnifiSitesInput
|
||||
disconnect?: Prisma.CompanyWhereInput | boolean
|
||||
delete?: Prisma.CompanyWhereInput | boolean
|
||||
connect?: Prisma.CompanyWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.CompanyUpdateToOneWithWhereWithoutUnifiSitesInput, Prisma.CompanyUpdateWithoutUnifiSitesInput>, Prisma.CompanyUncheckedUpdateWithoutUnifiSitesInput>
|
||||
}
|
||||
|
||||
export type IntFieldUpdateOperationsInput = {
|
||||
set?: number
|
||||
increment?: number
|
||||
@@ -405,6 +433,62 @@ export type CompanyUpdateOneRequiredWithoutCredentialsNestedInput = {
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.CompanyUpdateToOneWithWhereWithoutCredentialsInput, Prisma.CompanyUpdateWithoutCredentialsInput>, Prisma.CompanyUncheckedUpdateWithoutCredentialsInput>
|
||||
}
|
||||
|
||||
export type CompanyCreateWithoutUnifiSitesInput = {
|
||||
id?: string
|
||||
name: string
|
||||
cw_CompanyId: number
|
||||
cw_Identifier: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
credentials?: Prisma.CredentialCreateNestedManyWithoutCompanyInput
|
||||
}
|
||||
|
||||
export type CompanyUncheckedCreateWithoutUnifiSitesInput = {
|
||||
id?: string
|
||||
name: string
|
||||
cw_CompanyId: number
|
||||
cw_Identifier: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
credentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutCompanyInput
|
||||
}
|
||||
|
||||
export type CompanyCreateOrConnectWithoutUnifiSitesInput = {
|
||||
where: Prisma.CompanyWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.CompanyCreateWithoutUnifiSitesInput, Prisma.CompanyUncheckedCreateWithoutUnifiSitesInput>
|
||||
}
|
||||
|
||||
export type CompanyUpsertWithoutUnifiSitesInput = {
|
||||
update: Prisma.XOR<Prisma.CompanyUpdateWithoutUnifiSitesInput, Prisma.CompanyUncheckedUpdateWithoutUnifiSitesInput>
|
||||
create: Prisma.XOR<Prisma.CompanyCreateWithoutUnifiSitesInput, Prisma.CompanyUncheckedCreateWithoutUnifiSitesInput>
|
||||
where?: Prisma.CompanyWhereInput
|
||||
}
|
||||
|
||||
export type CompanyUpdateToOneWithWhereWithoutUnifiSitesInput = {
|
||||
where?: Prisma.CompanyWhereInput
|
||||
data: Prisma.XOR<Prisma.CompanyUpdateWithoutUnifiSitesInput, Prisma.CompanyUncheckedUpdateWithoutUnifiSitesInput>
|
||||
}
|
||||
|
||||
export type CompanyUpdateWithoutUnifiSitesInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
cw_CompanyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
credentials?: Prisma.CredentialUpdateManyWithoutCompanyNestedInput
|
||||
}
|
||||
|
||||
export type CompanyUncheckedUpdateWithoutUnifiSitesInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
cw_CompanyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
credentials?: Prisma.CredentialUncheckedUpdateManyWithoutCompanyNestedInput
|
||||
}
|
||||
|
||||
export type CompanyCreateWithoutCredentialsInput = {
|
||||
id?: string
|
||||
name: string
|
||||
@@ -412,6 +496,7 @@ export type CompanyCreateWithoutCredentialsInput = {
|
||||
cw_Identifier: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
unifiSites?: Prisma.UnifiSiteCreateNestedManyWithoutCompanyInput
|
||||
}
|
||||
|
||||
export type CompanyUncheckedCreateWithoutCredentialsInput = {
|
||||
@@ -421,6 +506,7 @@ export type CompanyUncheckedCreateWithoutCredentialsInput = {
|
||||
cw_Identifier: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
unifiSites?: Prisma.UnifiSiteUncheckedCreateNestedManyWithoutCompanyInput
|
||||
}
|
||||
|
||||
export type CompanyCreateOrConnectWithoutCredentialsInput = {
|
||||
@@ -446,6 +532,7 @@ export type CompanyUpdateWithoutCredentialsInput = {
|
||||
cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
unifiSites?: Prisma.UnifiSiteUpdateManyWithoutCompanyNestedInput
|
||||
}
|
||||
|
||||
export type CompanyUncheckedUpdateWithoutCredentialsInput = {
|
||||
@@ -455,6 +542,7 @@ export type CompanyUncheckedUpdateWithoutCredentialsInput = {
|
||||
cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
unifiSites?: Prisma.UnifiSiteUncheckedUpdateManyWithoutCompanyNestedInput
|
||||
}
|
||||
|
||||
|
||||
@@ -464,10 +552,12 @@ export type CompanyUncheckedUpdateWithoutCredentialsInput = {
|
||||
|
||||
export type CompanyCountOutputType = {
|
||||
credentials: number
|
||||
unifiSites: number
|
||||
}
|
||||
|
||||
export type CompanyCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
credentials?: boolean | CompanyCountOutputTypeCountCredentialsArgs
|
||||
unifiSites?: boolean | CompanyCountOutputTypeCountUnifiSitesArgs
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,6 +577,13 @@ export type CompanyCountOutputTypeCountCredentialsArgs<ExtArgs extends runtime.T
|
||||
where?: Prisma.CredentialWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* CompanyCountOutputType without action
|
||||
*/
|
||||
export type CompanyCountOutputTypeCountUnifiSitesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
where?: Prisma.UnifiSiteWhereInput
|
||||
}
|
||||
|
||||
|
||||
export type CompanySelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
id?: boolean
|
||||
@@ -496,6 +593,7 @@ export type CompanySelect<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs>
|
||||
unifiSites?: boolean | Prisma.Company$unifiSitesArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["company"]>
|
||||
|
||||
@@ -529,6 +627,7 @@ export type CompanySelectScalar = {
|
||||
export type CompanyOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "cw_CompanyId" | "cw_Identifier" | "createdAt" | "updatedAt", ExtArgs["result"]["company"]>
|
||||
export type CompanyInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs>
|
||||
unifiSites?: boolean | Prisma.Company$unifiSitesArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}
|
||||
export type CompanyIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
||||
@@ -538,6 +637,7 @@ export type $CompanyPayload<ExtArgs extends runtime.Types.Extensions.InternalArg
|
||||
name: "Company"
|
||||
objects: {
|
||||
credentials: Prisma.$CredentialPayload<ExtArgs>[]
|
||||
unifiSites: Prisma.$UnifiSitePayload<ExtArgs>[]
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: string
|
||||
@@ -941,6 +1041,7 @@ readonly fields: CompanyFieldRefs;
|
||||
export interface Prisma__CompanyClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||
credentials<T extends Prisma.Company$credentialsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Company$credentialsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$CredentialPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
unifiSites<T extends Prisma.Company$unifiSitesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Company$unifiSitesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$UnifiSitePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
@@ -1387,6 +1488,30 @@ export type Company$credentialsArgs<ExtArgs extends runtime.Types.Extensions.Int
|
||||
distinct?: Prisma.CredentialScalarFieldEnum | Prisma.CredentialScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Company.unifiSites
|
||||
*/
|
||||
export type Company$unifiSitesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the UnifiSite
|
||||
*/
|
||||
select?: Prisma.UnifiSiteSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the UnifiSite
|
||||
*/
|
||||
omit?: Prisma.UnifiSiteOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.UnifiSiteInclude<ExtArgs> | null
|
||||
where?: Prisma.UnifiSiteWhereInput
|
||||
orderBy?: Prisma.UnifiSiteOrderByWithRelationInput | Prisma.UnifiSiteOrderByWithRelationInput[]
|
||||
cursor?: Prisma.UnifiSiteWhereUniqueInput
|
||||
take?: number
|
||||
skip?: number
|
||||
distinct?: Prisma.UnifiSiteScalarFieldEnum | Prisma.UnifiSiteScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Company without action
|
||||
*/
|
||||
|
||||
@@ -28,6 +28,7 @@ export type CredentialMinAggregateOutputType = {
|
||||
id: string | null
|
||||
name: string | null
|
||||
notes: string | null
|
||||
subCredentialOfId: string | null
|
||||
typeId: string | null
|
||||
companyId: string | null
|
||||
createdAt: Date | null
|
||||
@@ -38,6 +39,7 @@ export type CredentialMaxAggregateOutputType = {
|
||||
id: string | null
|
||||
name: string | null
|
||||
notes: string | null
|
||||
subCredentialOfId: string | null
|
||||
typeId: string | null
|
||||
companyId: string | null
|
||||
createdAt: Date | null
|
||||
@@ -48,6 +50,7 @@ export type CredentialCountAggregateOutputType = {
|
||||
id: number
|
||||
name: number
|
||||
notes: number
|
||||
subCredentialOfId: number
|
||||
typeId: number
|
||||
fields: number
|
||||
companyId: number
|
||||
@@ -61,6 +64,7 @@ export type CredentialMinAggregateInputType = {
|
||||
id?: true
|
||||
name?: true
|
||||
notes?: true
|
||||
subCredentialOfId?: true
|
||||
typeId?: true
|
||||
companyId?: true
|
||||
createdAt?: true
|
||||
@@ -71,6 +75,7 @@ export type CredentialMaxAggregateInputType = {
|
||||
id?: true
|
||||
name?: true
|
||||
notes?: true
|
||||
subCredentialOfId?: true
|
||||
typeId?: true
|
||||
companyId?: true
|
||||
createdAt?: true
|
||||
@@ -81,6 +86,7 @@ export type CredentialCountAggregateInputType = {
|
||||
id?: true
|
||||
name?: true
|
||||
notes?: true
|
||||
subCredentialOfId?: true
|
||||
typeId?: true
|
||||
fields?: true
|
||||
companyId?: true
|
||||
@@ -165,6 +171,7 @@ export type CredentialGroupByOutputType = {
|
||||
id: string
|
||||
name: string
|
||||
notes: string | null
|
||||
subCredentialOfId: string | null
|
||||
typeId: string
|
||||
fields: runtime.JsonValue
|
||||
companyId: string
|
||||
@@ -197,11 +204,14 @@ export type CredentialWhereInput = {
|
||||
id?: Prisma.StringFilter<"Credential"> | string
|
||||
name?: Prisma.StringFilter<"Credential"> | string
|
||||
notes?: Prisma.StringNullableFilter<"Credential"> | string | null
|
||||
subCredentialOfId?: Prisma.StringNullableFilter<"Credential"> | string | null
|
||||
typeId?: Prisma.StringFilter<"Credential"> | string
|
||||
fields?: Prisma.JsonFilter<"Credential">
|
||||
companyId?: Prisma.StringFilter<"Credential"> | string
|
||||
createdAt?: Prisma.DateTimeFilter<"Credential"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"Credential"> | Date | string
|
||||
subCredentialOf?: Prisma.XOR<Prisma.CredentialNullableScalarRelationFilter, Prisma.CredentialWhereInput> | null
|
||||
subCredentials?: Prisma.CredentialListRelationFilter
|
||||
type?: Prisma.XOR<Prisma.CredentialTypeScalarRelationFilter, Prisma.CredentialTypeWhereInput>
|
||||
company?: Prisma.XOR<Prisma.CompanyScalarRelationFilter, Prisma.CompanyWhereInput>
|
||||
securevalues?: Prisma.SecureValueListRelationFilter
|
||||
@@ -211,11 +221,14 @@ export type CredentialOrderByWithRelationInput = {
|
||||
id?: Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
notes?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
subCredentialOfId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
typeId?: Prisma.SortOrder
|
||||
fields?: Prisma.SortOrder
|
||||
companyId?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
subCredentialOf?: Prisma.CredentialOrderByWithRelationInput
|
||||
subCredentials?: Prisma.CredentialOrderByRelationAggregateInput
|
||||
type?: Prisma.CredentialTypeOrderByWithRelationInput
|
||||
company?: Prisma.CompanyOrderByWithRelationInput
|
||||
securevalues?: Prisma.SecureValueOrderByRelationAggregateInput
|
||||
@@ -228,11 +241,14 @@ export type CredentialWhereUniqueInput = Prisma.AtLeast<{
|
||||
NOT?: Prisma.CredentialWhereInput | Prisma.CredentialWhereInput[]
|
||||
name?: Prisma.StringFilter<"Credential"> | string
|
||||
notes?: Prisma.StringNullableFilter<"Credential"> | string | null
|
||||
subCredentialOfId?: Prisma.StringNullableFilter<"Credential"> | string | null
|
||||
typeId?: Prisma.StringFilter<"Credential"> | string
|
||||
fields?: Prisma.JsonFilter<"Credential">
|
||||
companyId?: Prisma.StringFilter<"Credential"> | string
|
||||
createdAt?: Prisma.DateTimeFilter<"Credential"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"Credential"> | Date | string
|
||||
subCredentialOf?: Prisma.XOR<Prisma.CredentialNullableScalarRelationFilter, Prisma.CredentialWhereInput> | null
|
||||
subCredentials?: Prisma.CredentialListRelationFilter
|
||||
type?: Prisma.XOR<Prisma.CredentialTypeScalarRelationFilter, Prisma.CredentialTypeWhereInput>
|
||||
company?: Prisma.XOR<Prisma.CompanyScalarRelationFilter, Prisma.CompanyWhereInput>
|
||||
securevalues?: Prisma.SecureValueListRelationFilter
|
||||
@@ -242,6 +258,7 @@ export type CredentialOrderByWithAggregationInput = {
|
||||
id?: Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
notes?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
subCredentialOfId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
typeId?: Prisma.SortOrder
|
||||
fields?: Prisma.SortOrder
|
||||
companyId?: Prisma.SortOrder
|
||||
@@ -259,6 +276,7 @@ export type CredentialScalarWhereWithAggregatesInput = {
|
||||
id?: Prisma.StringWithAggregatesFilter<"Credential"> | string
|
||||
name?: Prisma.StringWithAggregatesFilter<"Credential"> | string
|
||||
notes?: Prisma.StringNullableWithAggregatesFilter<"Credential"> | string | null
|
||||
subCredentialOfId?: Prisma.StringNullableWithAggregatesFilter<"Credential"> | string | null
|
||||
typeId?: Prisma.StringWithAggregatesFilter<"Credential"> | string
|
||||
fields?: Prisma.JsonWithAggregatesFilter<"Credential">
|
||||
companyId?: Prisma.StringWithAggregatesFilter<"Credential"> | string
|
||||
@@ -273,6 +291,8 @@ export type CredentialCreateInput = {
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentialOf?: Prisma.CredentialCreateNestedOneWithoutSubCredentialsInput
|
||||
subCredentials?: Prisma.CredentialCreateNestedManyWithoutSubCredentialOfInput
|
||||
type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput
|
||||
company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput
|
||||
securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput
|
||||
@@ -282,11 +302,13 @@ export type CredentialUncheckedCreateInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
subCredentialOfId?: string | null
|
||||
typeId: string
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput
|
||||
securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput
|
||||
}
|
||||
|
||||
@@ -297,6 +319,8 @@ export type CredentialUpdateInput = {
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentialOf?: Prisma.CredentialUpdateOneWithoutSubCredentialsNestedInput
|
||||
subCredentials?: Prisma.CredentialUpdateManyWithoutSubCredentialOfNestedInput
|
||||
type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput
|
||||
@@ -306,11 +330,13 @@ export type CredentialUncheckedUpdateInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
typeId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedUpdateManyWithoutSubCredentialOfNestedInput
|
||||
securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput
|
||||
}
|
||||
|
||||
@@ -318,6 +344,7 @@ export type CredentialCreateManyInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
subCredentialOfId?: string | null
|
||||
typeId: string
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId: string
|
||||
@@ -338,6 +365,7 @@ export type CredentialUncheckedUpdateManyInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
typeId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -360,10 +388,16 @@ export type CredentialScalarRelationFilter = {
|
||||
isNot?: Prisma.CredentialWhereInput
|
||||
}
|
||||
|
||||
export type CredentialNullableScalarRelationFilter = {
|
||||
is?: Prisma.CredentialWhereInput | null
|
||||
isNot?: Prisma.CredentialWhereInput | null
|
||||
}
|
||||
|
||||
export type CredentialCountOrderByAggregateInput = {
|
||||
id?: Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
notes?: Prisma.SortOrder
|
||||
subCredentialOfId?: Prisma.SortOrder
|
||||
typeId?: Prisma.SortOrder
|
||||
fields?: Prisma.SortOrder
|
||||
companyId?: Prisma.SortOrder
|
||||
@@ -375,6 +409,7 @@ export type CredentialMaxOrderByAggregateInput = {
|
||||
id?: Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
notes?: Prisma.SortOrder
|
||||
subCredentialOfId?: Prisma.SortOrder
|
||||
typeId?: Prisma.SortOrder
|
||||
companyId?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
@@ -385,6 +420,7 @@ export type CredentialMinOrderByAggregateInput = {
|
||||
id?: Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
notes?: Prisma.SortOrder
|
||||
subCredentialOfId?: Prisma.SortOrder
|
||||
typeId?: Prisma.SortOrder
|
||||
companyId?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
@@ -489,6 +525,64 @@ export type CredentialUpdateOneRequiredWithoutSecurevaluesNestedInput = {
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.CredentialUpdateToOneWithWhereWithoutSecurevaluesInput, Prisma.CredentialUpdateWithoutSecurevaluesInput>, Prisma.CredentialUncheckedUpdateWithoutSecurevaluesInput>
|
||||
}
|
||||
|
||||
export type CredentialCreateNestedOneWithoutSubCredentialsInput = {
|
||||
create?: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialsInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialsInput>
|
||||
connectOrCreate?: Prisma.CredentialCreateOrConnectWithoutSubCredentialsInput
|
||||
connect?: Prisma.CredentialWhereUniqueInput
|
||||
}
|
||||
|
||||
export type CredentialCreateNestedManyWithoutSubCredentialOfInput = {
|
||||
create?: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialOfInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput> | Prisma.CredentialCreateWithoutSubCredentialOfInput[] | Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput[]
|
||||
connectOrCreate?: Prisma.CredentialCreateOrConnectWithoutSubCredentialOfInput | Prisma.CredentialCreateOrConnectWithoutSubCredentialOfInput[]
|
||||
createMany?: Prisma.CredentialCreateManySubCredentialOfInputEnvelope
|
||||
connect?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput = {
|
||||
create?: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialOfInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput> | Prisma.CredentialCreateWithoutSubCredentialOfInput[] | Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput[]
|
||||
connectOrCreate?: Prisma.CredentialCreateOrConnectWithoutSubCredentialOfInput | Prisma.CredentialCreateOrConnectWithoutSubCredentialOfInput[]
|
||||
createMany?: Prisma.CredentialCreateManySubCredentialOfInputEnvelope
|
||||
connect?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type CredentialUpdateOneWithoutSubCredentialsNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialsInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialsInput>
|
||||
connectOrCreate?: Prisma.CredentialCreateOrConnectWithoutSubCredentialsInput
|
||||
upsert?: Prisma.CredentialUpsertWithoutSubCredentialsInput
|
||||
disconnect?: Prisma.CredentialWhereInput | boolean
|
||||
delete?: Prisma.CredentialWhereInput | boolean
|
||||
connect?: Prisma.CredentialWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.CredentialUpdateToOneWithWhereWithoutSubCredentialsInput, Prisma.CredentialUpdateWithoutSubCredentialsInput>, Prisma.CredentialUncheckedUpdateWithoutSubCredentialsInput>
|
||||
}
|
||||
|
||||
export type CredentialUpdateManyWithoutSubCredentialOfNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialOfInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput> | Prisma.CredentialCreateWithoutSubCredentialOfInput[] | Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput[]
|
||||
connectOrCreate?: Prisma.CredentialCreateOrConnectWithoutSubCredentialOfInput | Prisma.CredentialCreateOrConnectWithoutSubCredentialOfInput[]
|
||||
upsert?: Prisma.CredentialUpsertWithWhereUniqueWithoutSubCredentialOfInput | Prisma.CredentialUpsertWithWhereUniqueWithoutSubCredentialOfInput[]
|
||||
createMany?: Prisma.CredentialCreateManySubCredentialOfInputEnvelope
|
||||
set?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
disconnect?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
delete?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
connect?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
update?: Prisma.CredentialUpdateWithWhereUniqueWithoutSubCredentialOfInput | Prisma.CredentialUpdateWithWhereUniqueWithoutSubCredentialOfInput[]
|
||||
updateMany?: Prisma.CredentialUpdateManyWithWhereWithoutSubCredentialOfInput | Prisma.CredentialUpdateManyWithWhereWithoutSubCredentialOfInput[]
|
||||
deleteMany?: Prisma.CredentialScalarWhereInput | Prisma.CredentialScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type CredentialUncheckedUpdateManyWithoutSubCredentialOfNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialOfInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput> | Prisma.CredentialCreateWithoutSubCredentialOfInput[] | Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput[]
|
||||
connectOrCreate?: Prisma.CredentialCreateOrConnectWithoutSubCredentialOfInput | Prisma.CredentialCreateOrConnectWithoutSubCredentialOfInput[]
|
||||
upsert?: Prisma.CredentialUpsertWithWhereUniqueWithoutSubCredentialOfInput | Prisma.CredentialUpsertWithWhereUniqueWithoutSubCredentialOfInput[]
|
||||
createMany?: Prisma.CredentialCreateManySubCredentialOfInputEnvelope
|
||||
set?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
disconnect?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
delete?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
connect?: Prisma.CredentialWhereUniqueInput | Prisma.CredentialWhereUniqueInput[]
|
||||
update?: Prisma.CredentialUpdateWithWhereUniqueWithoutSubCredentialOfInput | Prisma.CredentialUpdateWithWhereUniqueWithoutSubCredentialOfInput[]
|
||||
updateMany?: Prisma.CredentialUpdateManyWithWhereWithoutSubCredentialOfInput | Prisma.CredentialUpdateManyWithWhereWithoutSubCredentialOfInput[]
|
||||
deleteMany?: Prisma.CredentialScalarWhereInput | Prisma.CredentialScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type CredentialCreateWithoutCompanyInput = {
|
||||
id?: string
|
||||
name: string
|
||||
@@ -496,6 +590,8 @@ export type CredentialCreateWithoutCompanyInput = {
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentialOf?: Prisma.CredentialCreateNestedOneWithoutSubCredentialsInput
|
||||
subCredentials?: Prisma.CredentialCreateNestedManyWithoutSubCredentialOfInput
|
||||
type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput
|
||||
securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput
|
||||
}
|
||||
@@ -504,10 +600,12 @@ export type CredentialUncheckedCreateWithoutCompanyInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
subCredentialOfId?: string | null
|
||||
typeId: string
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput
|
||||
securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput
|
||||
}
|
||||
|
||||
@@ -544,6 +642,7 @@ export type CredentialScalarWhereInput = {
|
||||
id?: Prisma.StringFilter<"Credential"> | string
|
||||
name?: Prisma.StringFilter<"Credential"> | string
|
||||
notes?: Prisma.StringNullableFilter<"Credential"> | string | null
|
||||
subCredentialOfId?: Prisma.StringNullableFilter<"Credential"> | string | null
|
||||
typeId?: Prisma.StringFilter<"Credential"> | string
|
||||
fields?: Prisma.JsonFilter<"Credential">
|
||||
companyId?: Prisma.StringFilter<"Credential"> | string
|
||||
@@ -558,6 +657,8 @@ export type CredentialCreateWithoutTypeInput = {
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentialOf?: Prisma.CredentialCreateNestedOneWithoutSubCredentialsInput
|
||||
subCredentials?: Prisma.CredentialCreateNestedManyWithoutSubCredentialOfInput
|
||||
company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput
|
||||
securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput
|
||||
}
|
||||
@@ -566,10 +667,12 @@ export type CredentialUncheckedCreateWithoutTypeInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
subCredentialOfId?: string | null
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput
|
||||
securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput
|
||||
}
|
||||
|
||||
@@ -606,6 +709,8 @@ export type CredentialCreateWithoutSecurevaluesInput = {
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentialOf?: Prisma.CredentialCreateNestedOneWithoutSubCredentialsInput
|
||||
subCredentials?: Prisma.CredentialCreateNestedManyWithoutSubCredentialOfInput
|
||||
type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput
|
||||
company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput
|
||||
}
|
||||
@@ -614,11 +719,13 @@ export type CredentialUncheckedCreateWithoutSecurevaluesInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
subCredentialOfId?: string | null
|
||||
typeId: string
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput
|
||||
}
|
||||
|
||||
export type CredentialCreateOrConnectWithoutSecurevaluesInput = {
|
||||
@@ -644,6 +751,8 @@ export type CredentialUpdateWithoutSecurevaluesInput = {
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentialOf?: Prisma.CredentialUpdateOneWithoutSubCredentialsNestedInput
|
||||
subCredentials?: Prisma.CredentialUpdateManyWithoutSubCredentialOfNestedInput
|
||||
type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
}
|
||||
@@ -652,17 +761,140 @@ export type CredentialUncheckedUpdateWithoutSecurevaluesInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
typeId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedUpdateManyWithoutSubCredentialOfNestedInput
|
||||
}
|
||||
|
||||
export type CredentialCreateWithoutSubCredentialsInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentialOf?: Prisma.CredentialCreateNestedOneWithoutSubCredentialsInput
|
||||
type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput
|
||||
company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput
|
||||
securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput
|
||||
}
|
||||
|
||||
export type CredentialUncheckedCreateWithoutSubCredentialsInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
subCredentialOfId?: string | null
|
||||
typeId: string
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput
|
||||
}
|
||||
|
||||
export type CredentialCreateOrConnectWithoutSubCredentialsInput = {
|
||||
where: Prisma.CredentialWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialsInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialsInput>
|
||||
}
|
||||
|
||||
export type CredentialCreateWithoutSubCredentialOfInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentials?: Prisma.CredentialCreateNestedManyWithoutSubCredentialOfInput
|
||||
type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput
|
||||
company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput
|
||||
securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput
|
||||
}
|
||||
|
||||
export type CredentialUncheckedCreateWithoutSubCredentialOfInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
typeId: string
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput
|
||||
securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput
|
||||
}
|
||||
|
||||
export type CredentialCreateOrConnectWithoutSubCredentialOfInput = {
|
||||
where: Prisma.CredentialWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialOfInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput>
|
||||
}
|
||||
|
||||
export type CredentialCreateManySubCredentialOfInputEnvelope = {
|
||||
data: Prisma.CredentialCreateManySubCredentialOfInput | Prisma.CredentialCreateManySubCredentialOfInput[]
|
||||
skipDuplicates?: boolean
|
||||
}
|
||||
|
||||
export type CredentialUpsertWithoutSubCredentialsInput = {
|
||||
update: Prisma.XOR<Prisma.CredentialUpdateWithoutSubCredentialsInput, Prisma.CredentialUncheckedUpdateWithoutSubCredentialsInput>
|
||||
create: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialsInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialsInput>
|
||||
where?: Prisma.CredentialWhereInput
|
||||
}
|
||||
|
||||
export type CredentialUpdateToOneWithWhereWithoutSubCredentialsInput = {
|
||||
where?: Prisma.CredentialWhereInput
|
||||
data: Prisma.XOR<Prisma.CredentialUpdateWithoutSubCredentialsInput, Prisma.CredentialUncheckedUpdateWithoutSubCredentialsInput>
|
||||
}
|
||||
|
||||
export type CredentialUpdateWithoutSubCredentialsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentialOf?: Prisma.CredentialUpdateOneWithoutSubCredentialsNestedInput
|
||||
type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput
|
||||
}
|
||||
|
||||
export type CredentialUncheckedUpdateWithoutSubCredentialsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
typeId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput
|
||||
}
|
||||
|
||||
export type CredentialUpsertWithWhereUniqueWithoutSubCredentialOfInput = {
|
||||
where: Prisma.CredentialWhereUniqueInput
|
||||
update: Prisma.XOR<Prisma.CredentialUpdateWithoutSubCredentialOfInput, Prisma.CredentialUncheckedUpdateWithoutSubCredentialOfInput>
|
||||
create: Prisma.XOR<Prisma.CredentialCreateWithoutSubCredentialOfInput, Prisma.CredentialUncheckedCreateWithoutSubCredentialOfInput>
|
||||
}
|
||||
|
||||
export type CredentialUpdateWithWhereUniqueWithoutSubCredentialOfInput = {
|
||||
where: Prisma.CredentialWhereUniqueInput
|
||||
data: Prisma.XOR<Prisma.CredentialUpdateWithoutSubCredentialOfInput, Prisma.CredentialUncheckedUpdateWithoutSubCredentialOfInput>
|
||||
}
|
||||
|
||||
export type CredentialUpdateManyWithWhereWithoutSubCredentialOfInput = {
|
||||
where: Prisma.CredentialScalarWhereInput
|
||||
data: Prisma.XOR<Prisma.CredentialUpdateManyMutationInput, Prisma.CredentialUncheckedUpdateManyWithoutSubCredentialOfInput>
|
||||
}
|
||||
|
||||
export type CredentialCreateManyCompanyInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
subCredentialOfId?: string | null
|
||||
typeId: string
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Date | string
|
||||
@@ -676,6 +908,8 @@ export type CredentialUpdateWithoutCompanyInput = {
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentialOf?: Prisma.CredentialUpdateOneWithoutSubCredentialsNestedInput
|
||||
subCredentials?: Prisma.CredentialUpdateManyWithoutSubCredentialOfNestedInput
|
||||
type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput
|
||||
}
|
||||
@@ -684,10 +918,12 @@ export type CredentialUncheckedUpdateWithoutCompanyInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
typeId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedUpdateManyWithoutSubCredentialOfNestedInput
|
||||
securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput
|
||||
}
|
||||
|
||||
@@ -695,6 +931,7 @@ export type CredentialUncheckedUpdateManyWithoutCompanyInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
typeId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -705,6 +942,7 @@ export type CredentialCreateManyTypeInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
subCredentialOfId?: string | null
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId: string
|
||||
createdAt?: Date | string
|
||||
@@ -718,6 +956,8 @@ export type CredentialUpdateWithoutTypeInput = {
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentialOf?: Prisma.CredentialUpdateOneWithoutSubCredentialsNestedInput
|
||||
subCredentials?: Prisma.CredentialUpdateManyWithoutSubCredentialOfNestedInput
|
||||
company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput
|
||||
}
|
||||
@@ -726,10 +966,12 @@ export type CredentialUncheckedUpdateWithoutTypeInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedUpdateManyWithoutSubCredentialOfNestedInput
|
||||
securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput
|
||||
}
|
||||
|
||||
@@ -737,6 +979,55 @@ export type CredentialUncheckedUpdateManyWithoutTypeInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
export type CredentialCreateManySubCredentialOfInput = {
|
||||
id?: string
|
||||
name: string
|
||||
notes?: string | null
|
||||
typeId: string
|
||||
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
|
||||
export type CredentialUpdateWithoutSubCredentialOfInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentials?: Prisma.CredentialUpdateManyWithoutSubCredentialOfNestedInput
|
||||
type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput
|
||||
securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput
|
||||
}
|
||||
|
||||
export type CredentialUncheckedUpdateWithoutSubCredentialOfInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
typeId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
subCredentials?: Prisma.CredentialUncheckedUpdateManyWithoutSubCredentialOfNestedInput
|
||||
securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput
|
||||
}
|
||||
|
||||
export type CredentialUncheckedUpdateManyWithoutSubCredentialOfInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
typeId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
||||
companyId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -749,10 +1040,12 @@ export type CredentialUncheckedUpdateManyWithoutTypeInput = {
|
||||
*/
|
||||
|
||||
export type CredentialCountOutputType = {
|
||||
subCredentials: number
|
||||
securevalues: number
|
||||
}
|
||||
|
||||
export type CredentialCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
subCredentials?: boolean | CredentialCountOutputTypeCountSubCredentialsArgs
|
||||
securevalues?: boolean | CredentialCountOutputTypeCountSecurevaluesArgs
|
||||
}
|
||||
|
||||
@@ -766,6 +1059,13 @@ export type CredentialCountOutputTypeDefaultArgs<ExtArgs extends runtime.Types.E
|
||||
select?: Prisma.CredentialCountOutputTypeSelect<ExtArgs> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* CredentialCountOutputType without action
|
||||
*/
|
||||
export type CredentialCountOutputTypeCountSubCredentialsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
where?: Prisma.CredentialWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* CredentialCountOutputType without action
|
||||
*/
|
||||
@@ -778,11 +1078,14 @@ export type CredentialSelect<ExtArgs extends runtime.Types.Extensions.InternalAr
|
||||
id?: boolean
|
||||
name?: boolean
|
||||
notes?: boolean
|
||||
subCredentialOfId?: boolean
|
||||
typeId?: boolean
|
||||
fields?: boolean
|
||||
companyId?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
|
||||
subCredentials?: boolean | Prisma.Credential$subCredentialsArgs<ExtArgs>
|
||||
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
|
||||
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
|
||||
securevalues?: boolean | Prisma.Credential$securevaluesArgs<ExtArgs>
|
||||
@@ -793,11 +1096,13 @@ export type CredentialSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Ex
|
||||
id?: boolean
|
||||
name?: boolean
|
||||
notes?: boolean
|
||||
subCredentialOfId?: boolean
|
||||
typeId?: boolean
|
||||
fields?: boolean
|
||||
companyId?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
|
||||
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
|
||||
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["credential"]>
|
||||
@@ -806,11 +1111,13 @@ export type CredentialSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Ex
|
||||
id?: boolean
|
||||
name?: boolean
|
||||
notes?: boolean
|
||||
subCredentialOfId?: boolean
|
||||
typeId?: boolean
|
||||
fields?: boolean
|
||||
companyId?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
|
||||
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
|
||||
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["credential"]>
|
||||
@@ -819,6 +1126,7 @@ export type CredentialSelectScalar = {
|
||||
id?: boolean
|
||||
name?: boolean
|
||||
notes?: boolean
|
||||
subCredentialOfId?: boolean
|
||||
typeId?: boolean
|
||||
fields?: boolean
|
||||
companyId?: boolean
|
||||
@@ -826,18 +1134,22 @@ export type CredentialSelectScalar = {
|
||||
updatedAt?: boolean
|
||||
}
|
||||
|
||||
export type CredentialOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "notes" | "typeId" | "fields" | "companyId" | "createdAt" | "updatedAt", ExtArgs["result"]["credential"]>
|
||||
export type CredentialOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "notes" | "subCredentialOfId" | "typeId" | "fields" | "companyId" | "createdAt" | "updatedAt", ExtArgs["result"]["credential"]>
|
||||
export type CredentialInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
|
||||
subCredentials?: boolean | Prisma.Credential$subCredentialsArgs<ExtArgs>
|
||||
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
|
||||
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
|
||||
securevalues?: boolean | Prisma.Credential$securevaluesArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.CredentialCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}
|
||||
export type CredentialIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
|
||||
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
|
||||
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
|
||||
}
|
||||
export type CredentialIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
|
||||
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
|
||||
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
|
||||
}
|
||||
@@ -845,6 +1157,8 @@ export type CredentialIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.E
|
||||
export type $CredentialPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
name: "Credential"
|
||||
objects: {
|
||||
subCredentialOf: Prisma.$CredentialPayload<ExtArgs> | null
|
||||
subCredentials: Prisma.$CredentialPayload<ExtArgs>[]
|
||||
type: Prisma.$CredentialTypePayload<ExtArgs>
|
||||
company: Prisma.$CompanyPayload<ExtArgs>
|
||||
securevalues: Prisma.$SecureValuePayload<ExtArgs>[]
|
||||
@@ -853,6 +1167,7 @@ export type $CredentialPayload<ExtArgs extends runtime.Types.Extensions.Internal
|
||||
id: string
|
||||
name: string
|
||||
notes: string | null
|
||||
subCredentialOfId: string | null
|
||||
typeId: string
|
||||
fields: runtime.JsonValue
|
||||
companyId: string
|
||||
@@ -1252,6 +1567,8 @@ readonly fields: CredentialFieldRefs;
|
||||
*/
|
||||
export interface Prisma__CredentialClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||
subCredentialOf<T extends Prisma.Credential$subCredentialOfArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Credential$subCredentialOfArgs<ExtArgs>>): Prisma.Prisma__CredentialClient<runtime.Types.Result.GetResult<Prisma.$CredentialPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
subCredentials<T extends Prisma.Credential$subCredentialsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Credential$subCredentialsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$CredentialPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
type<T extends Prisma.CredentialTypeDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.CredentialTypeDefaultArgs<ExtArgs>>): Prisma.Prisma__CredentialTypeClient<runtime.Types.Result.GetResult<Prisma.$CredentialTypePayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||
company<T extends Prisma.CompanyDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.CompanyDefaultArgs<ExtArgs>>): Prisma.Prisma__CompanyClient<runtime.Types.Result.GetResult<Prisma.$CompanyPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||
securevalues<T extends Prisma.Credential$securevaluesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Credential$securevaluesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$SecureValuePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
@@ -1287,6 +1604,7 @@ export interface CredentialFieldRefs {
|
||||
readonly id: Prisma.FieldRef<"Credential", 'String'>
|
||||
readonly name: Prisma.FieldRef<"Credential", 'String'>
|
||||
readonly notes: Prisma.FieldRef<"Credential", 'String'>
|
||||
readonly subCredentialOfId: Prisma.FieldRef<"Credential", 'String'>
|
||||
readonly typeId: Prisma.FieldRef<"Credential", 'String'>
|
||||
readonly fields: Prisma.FieldRef<"Credential", 'Json'>
|
||||
readonly companyId: Prisma.FieldRef<"Credential", 'String'>
|
||||
@@ -1687,6 +2005,49 @@ export type CredentialDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.In
|
||||
limit?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Credential.subCredentialOf
|
||||
*/
|
||||
export type Credential$subCredentialOfArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the Credential
|
||||
*/
|
||||
select?: Prisma.CredentialSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the Credential
|
||||
*/
|
||||
omit?: Prisma.CredentialOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.CredentialInclude<ExtArgs> | null
|
||||
where?: Prisma.CredentialWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* Credential.subCredentials
|
||||
*/
|
||||
export type Credential$subCredentialsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the Credential
|
||||
*/
|
||||
select?: Prisma.CredentialSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the Credential
|
||||
*/
|
||||
omit?: Prisma.CredentialOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.CredentialInclude<ExtArgs> | null
|
||||
where?: Prisma.CredentialWhereInput
|
||||
orderBy?: Prisma.CredentialOrderByWithRelationInput | Prisma.CredentialOrderByWithRelationInput[]
|
||||
cursor?: Prisma.CredentialWhereUniqueInput
|
||||
take?: number
|
||||
skip?: number
|
||||
distinct?: Prisma.CredentialScalarFieldEnum | Prisma.CredentialScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Credential.securevalues
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Company" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"cw_CompanyId" INTEGER NOT NULL,
|
||||
"cw_Identifier" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Company_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CredentialType" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"permissionScope" TEXT NOT NULL,
|
||||
"icon" TEXT,
|
||||
"fields" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CredentialType_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SecureValue" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"hash" TEXT NOT NULL,
|
||||
"credentialId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SecureValue_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Credential" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"notes" TEXT,
|
||||
"subCredentialOfId" TEXT,
|
||||
"typeId" TEXT NOT NULL,
|
||||
"fields" JSONB NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Credential_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Company_cw_CompanyId_key" ON "Company"("cw_CompanyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Company_cw_Identifier_key" ON "Company"("cw_Identifier");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CredentialType_name_key" ON "CredentialType"("name");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SecureValue" ADD CONSTRAINT "SecureValue_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "Credential"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Credential" ADD CONSTRAINT "Credential_subCredentialOfId_fkey" FOREIGN KEY ("subCredentialOfId") REFERENCES "Credential"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Credential" ADD CONSTRAINT "Credential_typeId_fkey" FOREIGN KEY ("typeId") REFERENCES "CredentialType"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Credential" ADD CONSTRAINT "Credential_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+20
-3
@@ -55,6 +55,19 @@ model Role {
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model UnifiSite {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
|
||||
siteId String @unique
|
||||
|
||||
companyId String?
|
||||
company Company? @relation(fields: [companyId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Company {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
@@ -63,6 +76,7 @@ model Company {
|
||||
cw_Identifier String @unique
|
||||
|
||||
credentials Credential[]
|
||||
unifiSites UnifiSite[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -97,9 +111,12 @@ model SecureValue {
|
||||
}
|
||||
|
||||
model Credential {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
notes String?
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
notes String?
|
||||
subCredentialOfId String?
|
||||
subCredentialOf Credential? @relation("SubCredentials", fields: [subCredentialOfId], references: [id], onDelete: Cascade)
|
||||
subCredentials Credential[] @relation("SubCredentials")
|
||||
|
||||
typeId String
|
||||
type CredentialType @relation(fields: [typeId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@ -14,6 +14,9 @@ export default createRoute(
|
||||
async (c) => {
|
||||
const company = await companies.fetch(c.req.param("identifier"));
|
||||
const includeAddress = c.req.query("includeAddress") === "true";
|
||||
const includePrimaryContact =
|
||||
c.req.query("includePrimaryContact") === "true";
|
||||
const includeAllContacts = c.req.query("includeAllContacts") === "true";
|
||||
|
||||
// Check for address-specific permission if includeAddress is requested
|
||||
if (includeAddress) {
|
||||
@@ -27,9 +30,25 @@ export default createRoute(
|
||||
}
|
||||
}
|
||||
|
||||
// Check for contacts permission if includeAllContacts is requested
|
||||
if (includeAllContacts) {
|
||||
const user = c.get("user");
|
||||
if (!user || !(await user.hasPermission("company.fetch.contacts"))) {
|
||||
throw new GenericError({
|
||||
name: "InsufficientPermission",
|
||||
message: "You do not have permission to view company contacts.",
|
||||
status: 403,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"Company Fetched Successfully!",
|
||||
company.toJson({ includeAddress }),
|
||||
company.toJson({
|
||||
includeAddress,
|
||||
includePrimaryContact,
|
||||
includeAllContacts,
|
||||
}),
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { companies } from "../../../managers/companies";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/company/companies/:identifier/unifi/sites */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/companies/:identifier/unifi/sites"],
|
||||
async (c) => {
|
||||
const company = await companies.fetch(c.req.param("identifier"));
|
||||
const sites = await unifiSites.fetchByCompany(company.id);
|
||||
const response = apiResponse.successful(
|
||||
"Company UniFi Sites Fetched Successfully!",
|
||||
sites,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["unifi.access", "company.fetch"],
|
||||
}),
|
||||
);
|
||||
@@ -1,6 +1,7 @@
|
||||
import { default as fetchAll } from "./fetchAll";
|
||||
import { default as fetch } from "./[id]/fetch";
|
||||
import { default as configurations } from "./[id]/configurations";
|
||||
import { default as unifiSites } from "./[id]/unifiSites";
|
||||
import { default as count } from "./count";
|
||||
|
||||
export { configurations, count, fetch, fetchAll };
|
||||
export { configurations, count, fetch, fetchAll, unifiSites };
|
||||
|
||||
@@ -15,19 +15,22 @@ export default createRoute(
|
||||
async (c) => {
|
||||
const body = await c.req.json();
|
||||
|
||||
const fieldSchema: z.ZodType<any> = z.lazy(() =>
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
required: z.boolean(),
|
||||
secure: z.boolean(),
|
||||
valueType: z.enum(Object.values(ValueType)),
|
||||
subFields: z.array(fieldSchema).optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
permissionScope: z.string().min(1, "Permission scope is required"),
|
||||
icon: z.string().optional(),
|
||||
fields: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
required: z.boolean(),
|
||||
secure: z.boolean(),
|
||||
valueType: z.enum(Object.values(ValueType)),
|
||||
}),
|
||||
),
|
||||
fields: z.array(fieldSchema),
|
||||
});
|
||||
|
||||
const data = schema.parse(body);
|
||||
|
||||
@@ -16,21 +16,22 @@ export default createRoute(
|
||||
const body = await c.req.json();
|
||||
const credentialType = await credentialTypes.fetch(c.req.param("id"));
|
||||
|
||||
const fieldSchema: z.ZodType<any> = z.lazy(() =>
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
required: z.boolean(),
|
||||
secure: z.boolean(),
|
||||
valueType: z.enum(Object.values(ValueType)),
|
||||
subFields: z.array(fieldSchema).optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().optional(),
|
||||
permissionScope: z.string().optional(),
|
||||
icon: z.string().optional(),
|
||||
fields: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
required: z.boolean(),
|
||||
secure: z.boolean(),
|
||||
valueType: z.enum(Object.values(ValueType)),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
fields: z.array(fieldSchema).optional(),
|
||||
});
|
||||
|
||||
const data = schema.parse(body);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { credentials } from "../../managers/credentials";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { z } from "zod";
|
||||
|
||||
/* POST /v1/credential/credentials/:id/sub-credentials */
|
||||
export default createRoute(
|
||||
"post",
|
||||
["/credentials/:id/sub-credentials"],
|
||||
|
||||
async (c) => {
|
||||
const parentId = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
||||
const schema = z.object({
|
||||
fieldId: z.string().min(1, "Field ID is required"),
|
||||
name: z.string().min(1, "Name is required"),
|
||||
fields: z.array(
|
||||
z.object({
|
||||
fieldId: z.string(),
|
||||
value: z.string(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const data = schema.parse(body);
|
||||
|
||||
const subCredential = await credentials.addSubCredential(
|
||||
parentId,
|
||||
data.fieldId,
|
||||
{
|
||||
name: data.name,
|
||||
fields: data.fields,
|
||||
},
|
||||
);
|
||||
|
||||
const response = apiResponse.created(
|
||||
"Sub-Credential Created Successfully!",
|
||||
subCredential.toJson(),
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["credential.fetch", "credential.sub_credentials.create"],
|
||||
}),
|
||||
);
|
||||
@@ -25,6 +25,22 @@ export default createRoute(
|
||||
value: z.string(),
|
||||
}),
|
||||
),
|
||||
subCredentials: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.array(
|
||||
z.object({
|
||||
name: z.string().min(1, "Sub-credential name is required"),
|
||||
fields: z.array(
|
||||
z.object({
|
||||
fieldId: z.string(),
|
||||
value: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const data = schema.parse(body);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { credentials } from "../../managers/credentials";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
|
||||
/* GET /v1/credential/credentials/:id/sub-credentials */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/credentials/:id/sub-credentials"],
|
||||
|
||||
async (c) => {
|
||||
const parentId = c.req.param("id");
|
||||
|
||||
// Verify the parent credential exists
|
||||
await credentials.fetch(parentId);
|
||||
|
||||
const subCredentials = await credentials.fetchSubCredentials(parentId);
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"Sub-Credentials Fetched Successfully!",
|
||||
subCredentials.map((sc) => sc.toJson()),
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["credential.fetch", "credential.sub_credentials.fetch"],
|
||||
}),
|
||||
);
|
||||
@@ -8,6 +8,9 @@ import { default as readSecureValues } from "./readSecureValues";
|
||||
import { default as readSecureValue } from "./readSecureValue";
|
||||
import { default as deleteCredential } from "./delete";
|
||||
import { default as valueTypes } from "./valueTypes";
|
||||
import { default as fetchSubCredentials } from "./fetchSubCredentials";
|
||||
import { default as addSubCredential } from "./addSubCredential";
|
||||
import { default as removeSubCredential } from "./removeSubCredential";
|
||||
|
||||
export {
|
||||
valueTypes,
|
||||
@@ -20,4 +23,7 @@ export {
|
||||
readSecureValues,
|
||||
readSecureValue,
|
||||
deleteCredential as delete,
|
||||
fetchSubCredentials,
|
||||
addSubCredential,
|
||||
removeSubCredential,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { credentials } from "../../managers/credentials";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
|
||||
/* DELETE /v1/credential/credentials/:id/sub-credentials/:subId */
|
||||
export default createRoute(
|
||||
"delete",
|
||||
["/credentials/:id/sub-credentials/:subId"],
|
||||
|
||||
async (c) => {
|
||||
const parentId = c.req.param("id");
|
||||
const subId = c.req.param("subId");
|
||||
|
||||
await credentials.removeSubCredential(parentId, subId);
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"Sub-Credential Removed Successfully!",
|
||||
null,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["credential.fetch", "credential.sub_credentials.delete"],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Hono } from "hono";
|
||||
import * as unifiRoutes from "../unifi";
|
||||
|
||||
const unifiRouter = new Hono();
|
||||
Object.values(unifiRoutes).map((r) => unifiRouter.route("/", r));
|
||||
|
||||
export default unifiRouter;
|
||||
@@ -54,6 +54,7 @@ v1.route("/credential", require("./routers/credentialRouter").default);
|
||||
v1.route("/credential-type", require("./routers/credentialTypeRouter").default);
|
||||
v1.route("/role", require("./routers/roleRouter").default);
|
||||
v1.route("/permissions", require("./routers/permissionRouter").default);
|
||||
v1.route("/unifi", require("./routers/unifiRouter").default);
|
||||
app.route("/v1", v1);
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { default as fetchAllSites } from "./sites/fetchAll";
|
||||
import { default as syncSites } from "./sites/sync";
|
||||
import { default as createSite } from "./sites/create";
|
||||
import { default as fetchSite } from "./site/fetch";
|
||||
import { default as siteOverview } from "./site/overview";
|
||||
import { default as siteDevices } from "./site/devices";
|
||||
import { default as siteNetworks } from "./site/networks";
|
||||
import { default as siteWifiFetchAll } from "./site/wifi/fetchAll";
|
||||
import { default as siteWifiUpdate } from "./site/wifi/update";
|
||||
import { default as siteWifiPpskFetchAll } from "./site/wifi/ppskFetchAll";
|
||||
import { default as siteWifiPpskCreate } from "./site/wifi/ppskCreate";
|
||||
import { default as siteLink } from "./site/link";
|
||||
import { default as siteUnlink } from "./site/unlink";
|
||||
import { default as siteWlanGroups } from "./site/wlanGroups";
|
||||
import { default as siteWlanGroupsCreate } from "./site/wlanGroupsCreate";
|
||||
import { default as siteAccessPoints } from "./site/accessPoints";
|
||||
import { default as siteApGroups } from "./site/apGroups";
|
||||
import { default as siteWifiLimits } from "./site/wifiLimits";
|
||||
import { default as siteSpeedProfilesFetchAll } from "./site/speedProfilesFetchAll";
|
||||
import { default as siteSpeedProfilesCreate } from "./site/speedProfilesCreate";
|
||||
|
||||
export {
|
||||
fetchAllSites,
|
||||
syncSites,
|
||||
createSite,
|
||||
fetchSite,
|
||||
siteOverview,
|
||||
siteDevices,
|
||||
siteNetworks,
|
||||
siteWifiFetchAll,
|
||||
siteWifiUpdate,
|
||||
siteWifiPpskFetchAll,
|
||||
siteWifiPpskCreate,
|
||||
siteLink,
|
||||
siteUnlink,
|
||||
siteWlanGroups,
|
||||
siteWlanGroupsCreate,
|
||||
siteAccessPoints,
|
||||
siteApGroups,
|
||||
siteWifiLimits,
|
||||
siteSpeedProfilesFetchAll,
|
||||
siteSpeedProfilesCreate,
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id/access-points */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/access-points"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const accessPoints = await unifiSites.getAccessPoints(site.siteId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Access Points Fetched Successfully!",
|
||||
accessPoints,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["unifi.access", "unifi.site.access-points"],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id/ap-groups */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/ap-groups"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const apGroups = await unifiSites.getApGroups(site.siteId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi AP Groups Fetched Successfully!",
|
||||
apGroups,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["unifi.access", "unifi.site.ap-groups"],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id/devices */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/devices"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const devices = await unifiSites.getDevices(site.siteId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Devices Fetched Successfully!",
|
||||
devices,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.site.devices"] }),
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Site Fetched Successfully!",
|
||||
site,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.sites.fetch"] }),
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
import { z } from "zod";
|
||||
|
||||
/* POST /v1/unifi/site/:id/link */
|
||||
export default createRoute(
|
||||
"post",
|
||||
["/site/:id/link"],
|
||||
async (c) => {
|
||||
const siteId = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
const schema = z.object({ companyId: z.string() }).strict();
|
||||
const { companyId } = schema.parse(body);
|
||||
|
||||
const site = await unifiSites.linkToCompany(siteId, companyId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Site Linked to Company Successfully!",
|
||||
site,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.sites.link"] }),
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id/networks */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/networks"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const networks = await unifiSites.getNetworks(site.siteId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Networks Fetched Successfully!",
|
||||
networks,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.site.networks"] }),
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id/overview */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/overview"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const overview = await unifiSites.getSiteOverview(site.siteId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Site Overview Fetched Successfully!",
|
||||
overview,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.site.overview"] }),
|
||||
);
|
||||
@@ -0,0 +1,40 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
import { z } from "zod";
|
||||
|
||||
/* POST /v1/unifi/site/:id/speed-profiles */
|
||||
export default createRoute(
|
||||
"post",
|
||||
["/site/:id/speed-profiles"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
|
||||
const body = await c.req.json();
|
||||
const schema = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
downloadLimitKbps: z.number().optional(),
|
||||
uploadLimitKbps: z.number().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const parsed = schema.parse(body);
|
||||
const profile = await unifiSites.createUserGroup(site.siteId, parsed);
|
||||
|
||||
const response = apiResponse.created(
|
||||
"UniFi Speed Profile Created Successfully!",
|
||||
profile,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: [
|
||||
"unifi.access",
|
||||
"unifi.site.speed-profiles",
|
||||
"unifi.site.speed-profiles.create",
|
||||
],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id/speed-profiles */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/speed-profiles"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const profiles = await unifiSites.getUserGroups(site.siteId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Speed Profiles Fetched Successfully!",
|
||||
profiles,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["unifi.access", "unifi.site.speed-profiles"],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* POST /v1/unifi/site/:id/unlink */
|
||||
export default createRoute(
|
||||
"post",
|
||||
["/site/:id/unlink"],
|
||||
async (c) => {
|
||||
const siteId = c.req.param("id");
|
||||
const site = await unifiSites.unlinkFromCompany(siteId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Site Unlinked from Company Successfully!",
|
||||
site,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.sites.link"] }),
|
||||
);
|
||||
@@ -0,0 +1,31 @@
|
||||
import { createRoute } from "../../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../../middleware/authorization";
|
||||
import { processObjectValuePerms } from "../../../../modules/permission-utils/processObjectPermissions";
|
||||
|
||||
/* GET /v1/unifi/site/:id/wifi */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/wifi"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const wlans = await unifiSites.getWlanConf(site.siteId);
|
||||
|
||||
const processWlans = await Promise.all(
|
||||
wlans.map((wlan) =>
|
||||
processObjectValuePerms(wlan, "unifi.site.wifi.read", c.get("user")),
|
||||
),
|
||||
);
|
||||
|
||||
console.log(processWlans);
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"UniFi WiFi Networks Fetched Successfully!",
|
||||
processWlans,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.site.wifi"] }),
|
||||
);
|
||||
@@ -0,0 +1,47 @@
|
||||
import { createRoute } from "../../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../../middleware/authorization";
|
||||
import { z } from "zod";
|
||||
|
||||
/* POST /v1/unifi/site/:id/wifi/:wlanId/ppsk */
|
||||
export default createRoute(
|
||||
"post",
|
||||
["/site/:id/wifi/:wlanId/ppsk"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const wlanId = c.req.param("wlanId");
|
||||
|
||||
const body = await c.req.json();
|
||||
const schema = z
|
||||
.object({
|
||||
key: z.string().min(8, "PSK must be at least 8 characters"),
|
||||
name: z.string().min(1, "Name is required"),
|
||||
mac: z.string().optional(),
|
||||
vlanId: z.number().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const parsed = schema.parse(body);
|
||||
const ppsks = await unifiSites.createPrivatePSK(
|
||||
site.siteId,
|
||||
wlanId,
|
||||
parsed,
|
||||
);
|
||||
|
||||
const response = apiResponse.created(
|
||||
"UniFi Private PSK Created Successfully!",
|
||||
ppsks,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: [
|
||||
"unifi.access",
|
||||
"unifi.site.wifi",
|
||||
"unifi.site.wifi.ppsk",
|
||||
"unifi.site.wifi.ppsk.create",
|
||||
],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createRoute } from "../../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id/wifi/:wlanId/ppsk */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/wifi/:wlanId/ppsk"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const wlanId = c.req.param("wlanId");
|
||||
const ppsks = await unifiSites.getPrivatePSKs(site.siteId, wlanId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Private PSKs Fetched Successfully!",
|
||||
ppsks,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["unifi.access", "unifi.site.wifi", "unifi.site.wifi.ppsk"],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,117 @@
|
||||
import { createRoute } from "../../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../../middleware/authorization";
|
||||
import { toWlanConfUpdate } from "../../../../modules/unifi-api/unifiTypes";
|
||||
import { z } from "zod";
|
||||
|
||||
/* PATCH /v1/unifi/site/:id/wifi/:wlanId */
|
||||
export default createRoute(
|
||||
"patch",
|
||||
["/site/:id/wifi/:wlanId"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const wlanId = c.req.param("wlanId");
|
||||
|
||||
const body = await c.req.json();
|
||||
const schema = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
passphrase: z.string().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
security: z.enum(["wpapsk", "wpaeap", "open", "osen"]).optional(),
|
||||
wpaMode: z.enum(["wpa2", "wpa3", "wpa2wpa3"]).optional(),
|
||||
wpaEnc: z.enum(["ccmp", "gcmp", "ccmp-gcmp"]).optional(),
|
||||
hideSSID: z.boolean().optional(),
|
||||
macFilterEnabled: z.boolean().optional(),
|
||||
macFilterPolicy: z.enum(["allow", "deny"]).optional(),
|
||||
isGuest: z.boolean().optional(),
|
||||
l2Isolation: z.boolean().optional(),
|
||||
fastRoamingEnabled: z.boolean().optional(),
|
||||
bssTransition: z.boolean().optional(),
|
||||
uapsdEnabled: z.boolean().optional(),
|
||||
groupRekey: z.number().optional(),
|
||||
dtimMode: z.enum(["default", "custom"]).optional(),
|
||||
dtimNg: z.number().optional(),
|
||||
dtimNa: z.number().optional(),
|
||||
minrateNgEnabled: z.boolean().optional(),
|
||||
minrateNaEnabled: z.boolean().optional(),
|
||||
radiusDasEnabled: z.boolean().optional(),
|
||||
radiusMacAuthEnabled: z.boolean().optional(),
|
||||
pmfMode: z.enum(["disabled", "optional", "required"]).optional(),
|
||||
band: z.enum(["both", "2g", "5g"]).optional(),
|
||||
usergroupId: z.string().optional(),
|
||||
proxyArp: z.boolean().optional(),
|
||||
mcastenhanceEnabled: z.boolean().optional(),
|
||||
macFilterList: z.array(z.string()).optional(),
|
||||
no2ghzOui: z.boolean().optional(),
|
||||
apGroupIds: z.array(z.string()).optional(),
|
||||
apGroupMode: z.enum(["all", "groups", "devices"]).optional(),
|
||||
deviceMacs: z.array(z.string()).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const parsed = schema.parse(body);
|
||||
|
||||
// If deviceMacs is provided, manage the devices_ap_group:
|
||||
// - If already in devices mode with a for_wlanconf group, update that group
|
||||
// - Otherwise create a new for_wlanconf group and switch to devices mode
|
||||
if (parsed.deviceMacs && parsed.deviceMacs.length > 0) {
|
||||
const wlans = await unifiSites.getWlanConf(site.siteId);
|
||||
const currentWlan = wlans.find((w) => w.id === wlanId);
|
||||
|
||||
let existingGroupId: string | undefined;
|
||||
if (
|
||||
currentWlan?.apGroupMode === "devices" &&
|
||||
currentWlan.apGroupIds.length > 0
|
||||
) {
|
||||
// Check if the current group is a for_wlanconf group we can update
|
||||
const apGroups = await unifiSites.getApGroups(site.siteId);
|
||||
const currentGroup = apGroups.find(
|
||||
(g) => g.id === currentWlan.apGroupIds[0] && g.forWlanconf,
|
||||
);
|
||||
if (currentGroup) existingGroupId = currentGroup.id;
|
||||
}
|
||||
|
||||
if (existingGroupId) {
|
||||
// Update the existing for_wlanconf group's device list
|
||||
await unifiSites.updateApGroup(
|
||||
site.siteId,
|
||||
existingGroupId,
|
||||
parsed.deviceMacs,
|
||||
);
|
||||
parsed.apGroupMode = "devices";
|
||||
parsed.apGroupIds = [existingGroupId];
|
||||
} else {
|
||||
// Create a new for_wlanconf group
|
||||
const apGroup = await unifiSites.createApGroup(
|
||||
site.siteId,
|
||||
"devices_ap_group",
|
||||
parsed.deviceMacs,
|
||||
true, // for_wlanconf must be true for devices mode
|
||||
);
|
||||
parsed.apGroupMode = "devices";
|
||||
parsed.apGroupIds = [apGroup.id];
|
||||
}
|
||||
}
|
||||
|
||||
// Remove deviceMacs before converting — it's not a UniFi field
|
||||
const { deviceMacs: _, ...updateInput } = parsed;
|
||||
const updates = toWlanConfUpdate(updateInput);
|
||||
|
||||
const result = await unifiSites.updateWlanConf(
|
||||
site.siteId,
|
||||
wlanId,
|
||||
updates,
|
||||
);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi WiFi Network Updated Successfully!",
|
||||
result,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["unifi.access", "unifi.site.wifi", "unifi.site.wifi.update"],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id/wifi-limits */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/wifi-limits"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const limits = await unifiSites.getWifiLimits(site.siteId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi WiFi Limits Fetched Successfully!",
|
||||
limits,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["unifi.access", "unifi.site.wifi-limits"],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/site/:id/wlan-groups */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/site/:id/wlan-groups"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
const wlanGroups = await unifiSites.getWlanGroups(site.siteId);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi WLAN Groups Fetched Successfully!",
|
||||
wlanGroups,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["unifi.access", "unifi.site.wlan-groups"],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
import { z } from "zod";
|
||||
|
||||
/* POST /v1/unifi/site/:id/wlan-groups */
|
||||
export default createRoute(
|
||||
"post",
|
||||
["/site/:id/wlan-groups"],
|
||||
async (c) => {
|
||||
const site = await unifiSites.fetch(c.req.param("id"));
|
||||
|
||||
const body = await c.req.json();
|
||||
const schema = z
|
||||
.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const parsed = schema.parse(body);
|
||||
const group = await unifiSites.createWlanGroup(site.siteId, parsed);
|
||||
|
||||
const response = apiResponse.created(
|
||||
"UniFi WLAN Group Created Successfully!",
|
||||
group,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: [
|
||||
"unifi.access",
|
||||
"unifi.site.wlan-groups",
|
||||
"unifi.site.wlan-groups.create",
|
||||
],
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
import { z } from "zod";
|
||||
|
||||
/* POST /v1/unifi/sites/create */
|
||||
export default createRoute(
|
||||
"post",
|
||||
["/sites/create"],
|
||||
async (c) => {
|
||||
const body = await c.req.json();
|
||||
const schema = z.object({ description: z.string().min(1) }).strict();
|
||||
const { description } = schema.parse(body);
|
||||
|
||||
const site = await unifiSites.createSite(description);
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Site Created Successfully!",
|
||||
site,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.sites.create"] }),
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* GET /v1/unifi/sites */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/sites"],
|
||||
async (c) => {
|
||||
const sites = await unifiSites.fetchAll();
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Sites Fetched Successfully!",
|
||||
sites,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.sites.fetch.many"] }),
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { unifiSites } from "../../../managers/unifiSites";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* POST /v1/unifi/sites/sync */
|
||||
export default createRoute(
|
||||
"post",
|
||||
["/sites/sync"],
|
||||
async (c) => {
|
||||
const sites = await unifiSites.syncSites();
|
||||
const response = apiResponse.successful(
|
||||
"UniFi Sites Synced Successfully!",
|
||||
sites,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["unifi.access", "unifi.sites.sync"] }),
|
||||
);
|
||||
@@ -5,6 +5,7 @@ import * as msal from "@azure/msal-node";
|
||||
import { Server } from "socket.io";
|
||||
import { Server as Engine } from "@socket.io/bun-engine";
|
||||
import axios from "axios";
|
||||
import { UnifiClient } from "./modules/unifi-api/UnifiClient";
|
||||
|
||||
const connectionString = `${process.env.DATABASE_URL}`;
|
||||
const adapter = new PrismaPg({ connectionString });
|
||||
@@ -67,3 +68,13 @@ const connectWiseApi = axios.create({
|
||||
});
|
||||
|
||||
export { connectWiseApi };
|
||||
|
||||
// Unifi API Constants
|
||||
|
||||
export const unifiControllerBaseUrl =
|
||||
process.env.UNIFI_CONTROLLER_BASE_URL || "https://unifi.example.com";
|
||||
export const unifiSite = process.env.UNIFI_SITE || "default";
|
||||
export const unifiUsername = process.env.UNIFI_USERNAME || "admin";
|
||||
export const unifiPassword = process.env.UNIFI_PASSWORD || "";
|
||||
|
||||
export const unifi = new UnifiClient(unifiControllerBaseUrl);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Company } from "../../generated/prisma/client";
|
||||
import { fetchCwCompanyById } from "../modules/cw-utils/fetchCompany";
|
||||
import { fetchCompanyConfigurations } from "../modules/cw-utils/configurations/fetchCompanyConfigurations";
|
||||
import { updateCwInternalCompany } from "../modules/cw-utils/updateCompany";
|
||||
import { Company as CWCompany } from "../types/ConnectWiseTypes";
|
||||
import { Company as CWCompany, Contact } from "../types/ConnectWiseTypes";
|
||||
|
||||
/**
|
||||
* Company Controller
|
||||
@@ -16,9 +16,13 @@ export class CompanyController {
|
||||
public name: string;
|
||||
public readonly cw_Identifier: string;
|
||||
public readonly cw_CompanyId: number;
|
||||
public readonly cw_Data?: CWCompany;
|
||||
public readonly cw_Data?: {
|
||||
company: CWCompany;
|
||||
defaultContact: Contact;
|
||||
allContacts: Contact[];
|
||||
};
|
||||
|
||||
constructor(companyData: Company, cwData?: CWCompany) {
|
||||
constructor(companyData: Company, cwData?: typeof this.cw_Data) {
|
||||
this.id = companyData.id;
|
||||
this.name = companyData.name;
|
||||
this.cw_Identifier = companyData.cw_Identifier;
|
||||
@@ -67,23 +71,66 @@ export class CompanyController {
|
||||
return data;
|
||||
}
|
||||
|
||||
public toJson(opts?: { includeAddress: boolean }) {
|
||||
public toJson(opts?: {
|
||||
includeAddress: boolean;
|
||||
includePrimaryContact: boolean;
|
||||
includeAllContacts?: boolean;
|
||||
}) {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
cw_Identifier: this.cw_Identifier,
|
||||
cw_CompanyId: this.cw_CompanyId,
|
||||
cw_Data: {
|
||||
address: {
|
||||
line1: this.cw_Data?.addressLine1,
|
||||
line2: this.cw_Data?.addressLine2 ?? null,
|
||||
city: this.cw_Data?.city,
|
||||
state: this.cw_Data?.state,
|
||||
zip: this.cw_Data?.zip,
|
||||
country: this.cw_Data?.country
|
||||
? this.cw_Data.country.name
|
||||
: "United States",
|
||||
},
|
||||
address: !opts?.includeAddress
|
||||
? undefined
|
||||
: {
|
||||
line1: this.cw_Data?.company.addressLine1,
|
||||
line2: this.cw_Data?.company.addressLine2 ?? null,
|
||||
city: this.cw_Data?.company.city,
|
||||
state: this.cw_Data?.company.state,
|
||||
zip: this.cw_Data?.company.zip,
|
||||
country: this.cw_Data?.company.country
|
||||
? this.cw_Data.company.country.name
|
||||
: "United States",
|
||||
},
|
||||
primaryContact: !opts?.includePrimaryContact
|
||||
? undefined
|
||||
: {
|
||||
firstName: this.cw_Data?.defaultContact.firstName,
|
||||
lastName: this.cw_Data?.defaultContact.lastName,
|
||||
cwId: this.cw_Data?.defaultContact.id,
|
||||
inactive: this.cw_Data?.defaultContact.inactiveFlag,
|
||||
title: this.cw_Data?.defaultContact.title,
|
||||
phone: this.cw_Data?.defaultContact.defaultPhoneNbr,
|
||||
email: (() => {
|
||||
if (!this.cw_Data?.defaultContact.communicationItems)
|
||||
return null;
|
||||
return (
|
||||
this.cw_Data?.defaultContact.communicationItems.find(
|
||||
(v) => v.type.name === "Email",
|
||||
)?.value ?? null
|
||||
);
|
||||
})(),
|
||||
},
|
||||
allContacts: !opts?.includeAllContacts
|
||||
? undefined
|
||||
: this.cw_Data?.allContacts.map((contact) => ({
|
||||
firstName: contact.firstName,
|
||||
lastName: contact.lastName,
|
||||
cwId: contact.id,
|
||||
inactive: contact.inactiveFlag,
|
||||
title: contact.title,
|
||||
phone: contact.defaultPhoneNbr,
|
||||
email: (() => {
|
||||
if (!contact.communicationItems) return null;
|
||||
return (
|
||||
contact.communicationItems.find(
|
||||
(v) => v.type.name === "Email",
|
||||
)?.value ?? null
|
||||
);
|
||||
})(),
|
||||
})),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,11 +28,13 @@ export class CredentialController {
|
||||
public notes: string | null;
|
||||
public readonly typeId: string;
|
||||
public readonly companyId: string;
|
||||
public readonly subCredentialOfId: string | null;
|
||||
public fields: any;
|
||||
|
||||
private _type: CredentialType;
|
||||
private _company: Company;
|
||||
private _secureValues: SecureValue[];
|
||||
private _subCredentials: CredentialController[];
|
||||
|
||||
public readonly createdAt: Date;
|
||||
public updatedAt: Date;
|
||||
@@ -42,6 +44,11 @@ export class CredentialController {
|
||||
type: CredentialType;
|
||||
company: Company;
|
||||
securevalues: SecureValue[];
|
||||
subCredentials?: (Credential & {
|
||||
type: CredentialType;
|
||||
company: Company;
|
||||
securevalues: SecureValue[];
|
||||
})[];
|
||||
},
|
||||
) {
|
||||
this.id = credentialData.id;
|
||||
@@ -49,13 +56,69 @@ export class CredentialController {
|
||||
this.notes = credentialData.notes;
|
||||
this.typeId = credentialData.typeId;
|
||||
this.companyId = credentialData.companyId;
|
||||
this.subCredentialOfId = credentialData.subCredentialOfId;
|
||||
this._type = credentialData.type;
|
||||
this._company = credentialData.company;
|
||||
this._secureValues = credentialData.securevalues;
|
||||
this.fields = (() => {
|
||||
let fields = credentialData.fields as Record<string, any>;
|
||||
this._subCredentials = (credentialData.subCredentials ?? []).map(
|
||||
(sc) => new CredentialController(sc),
|
||||
);
|
||||
this.fields = this._buildFields(credentialData);
|
||||
this.createdAt = credentialData.createdAt;
|
||||
this.updatedAt = credentialData.updatedAt;
|
||||
}
|
||||
|
||||
return (this._type.fields! as any).map((f: any) => ({
|
||||
/**
|
||||
* Build Fields
|
||||
*
|
||||
* Maps raw credential data into a structured fields array.
|
||||
* - Regular credentials: maps through the type's field definitions.
|
||||
* - Multi-credential fields: returns sub-credential references and subField definitions.
|
||||
* - Sub-credentials: returns raw field data (validated against subFields, not the type's top-level fields).
|
||||
*/
|
||||
private _buildFields(credentialData: Credential) {
|
||||
const raw = credentialData.fields as Record<string, any>;
|
||||
const typeFields = this._type.fields as any as CredentialTypeField[];
|
||||
|
||||
// Sub-credentials: their fields don't match the type's top-level definitions,
|
||||
// so we return a simple id/value list built from raw JSON + secure values.
|
||||
if (credentialData.subCredentialOfId) {
|
||||
const result: any[] = [];
|
||||
|
||||
// Collect field IDs that have secure values
|
||||
const secureFieldIds = new Set(this._secureValues.map((sv) => sv.name));
|
||||
|
||||
// Non-secure fields from JSON
|
||||
Object.entries(raw).forEach(([fieldId, value]) => {
|
||||
if (!secureFieldIds.has(fieldId)) {
|
||||
result.push({ id: fieldId, value, secure: false });
|
||||
}
|
||||
});
|
||||
|
||||
// Secure value references
|
||||
this._secureValues.forEach((sv) => {
|
||||
result.push({ id: sv.name, value: `secure-${sv.id}`, secure: true });
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Regular (parent) credential: map through type field definitions
|
||||
return typeFields.map((f: any) => {
|
||||
if (f.valueType === ValueType.MULTI_CREDENTIAL) {
|
||||
const subCredIds: string[] = raw[f.id] ?? [];
|
||||
return {
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
secure: false,
|
||||
required: f.required,
|
||||
valueType: f.valueType,
|
||||
subFields: f.subFields ?? [],
|
||||
subCredentialIds: subCredIds,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
secure: f.secure,
|
||||
@@ -63,13 +126,9 @@ export class CredentialController {
|
||||
valueType: f.valueType as ValueType,
|
||||
value: f.secure
|
||||
? `secure-${this._secureValues.find((sv) => sv.name === f.id)?.id}`
|
||||
: fields[f.id],
|
||||
}));
|
||||
|
||||
return fields;
|
||||
})();
|
||||
this.createdAt = credentialData.createdAt;
|
||||
this.updatedAt = credentialData.updatedAt;
|
||||
: raw[f.id],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,6 +210,19 @@ export class CredentialController {
|
||||
fieldsObject[field.fieldId] = field.value;
|
||||
});
|
||||
|
||||
// Preserve multi-credential field values (sub-credential ID arrays)
|
||||
const currentFields = (await prisma.credential.findFirst({
|
||||
where: { id: this.id },
|
||||
select: { fields: true },
|
||||
}))!.fields as Record<string, any>;
|
||||
|
||||
const typeFields = this._type.fields as any as CredentialTypeField[];
|
||||
typeFields.forEach((f) => {
|
||||
if (f.valueType === ValueType.MULTI_CREDENTIAL && currentFields[f.id]) {
|
||||
fieldsObject[f.id] = currentFields[f.id];
|
||||
}
|
||||
});
|
||||
|
||||
// Update the credential with non-secure fields
|
||||
const updatedCredential = await prisma.credential.update({
|
||||
where: { id: this.id },
|
||||
@@ -313,13 +385,14 @@ export class CredentialController {
|
||||
* @param opts - Options to change the output
|
||||
* @returns - An object that is JSON friendly
|
||||
*/
|
||||
toJson(opts?: { includeSecureValues?: boolean }) {
|
||||
toJson(opts?: { includeSecureValues?: boolean }): Record<string, any> {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
notes: this.notes,
|
||||
typeId: this.typeId,
|
||||
companyId: this.companyId,
|
||||
subCredentialOfId: this.subCredentialOfId ?? undefined,
|
||||
fields: this.fields,
|
||||
type: {
|
||||
id: this._type.id,
|
||||
@@ -331,6 +404,10 @@ export class CredentialController {
|
||||
id: this._company.id,
|
||||
name: this._company.name,
|
||||
},
|
||||
subCredentials:
|
||||
this._subCredentials.length > 0
|
||||
? this._subCredentials.map((sc) => sc.toJson(opts))
|
||||
: undefined,
|
||||
secureFieldIds: opts?.includeSecureValues
|
||||
? this._secureValues.map((sv) => sv.name)
|
||||
: undefined,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { UnifiSite } from "../../generated/prisma/client";
|
||||
|
||||
/**
|
||||
* UniFi Site Controller
|
||||
*
|
||||
* Handles formatting and presentation of UniFi site data.
|
||||
*/
|
||||
export class UnifiSiteController {
|
||||
public readonly id: string;
|
||||
public readonly name: string;
|
||||
public readonly siteId: string;
|
||||
public readonly companyId: string | null;
|
||||
|
||||
constructor(site: UnifiSite) {
|
||||
this.id = site.id;
|
||||
this.name = site.name;
|
||||
this.siteId = site.siteId;
|
||||
this.companyId = site.companyId;
|
||||
}
|
||||
|
||||
public toJson() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
siteId: this.siteId,
|
||||
companyId: this.companyId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { refresh } from "./api/auth";
|
||||
import app from "./api/server";
|
||||
import { engine, PORT } from "./constants";
|
||||
import { unifiSites } from "./managers/unifiSites";
|
||||
import { refreshCompanies } from "./modules/cw-utils/refreshCompanies";
|
||||
import { events, setupEventDebugger } from "./modules/globalEvents";
|
||||
|
||||
@@ -13,6 +14,11 @@ setInterval(() => {
|
||||
return refreshCompanies();
|
||||
}, 60 * 1000);
|
||||
|
||||
await unifiSites.syncSites();
|
||||
setInterval(() => {
|
||||
return unifiSites.syncSites();
|
||||
}, 60 * 1000);
|
||||
|
||||
Bun.serve({
|
||||
port: PORT,
|
||||
websocket: engine.handler().websocket,
|
||||
|
||||
@@ -12,10 +12,21 @@ export const companies = {
|
||||
|
||||
if (!search) throw new Error("Unknown company.");
|
||||
|
||||
const freshCwData = await connectWiseApi.get(
|
||||
const freshCwData: { data: Company } = await connectWiseApi.get(
|
||||
`/company/companies/${search.cw_CompanyId}`,
|
||||
);
|
||||
return new CompanyController(search, freshCwData.data);
|
||||
const defaultContactData = await connectWiseApi.get(
|
||||
(freshCwData.data as Company).defaultContact._info.contact_href,
|
||||
);
|
||||
const allContactsData = await connectWiseApi.get(
|
||||
`${freshCwData.data._info.contacts_href}&pageSize=1000`,
|
||||
);
|
||||
|
||||
return new CompanyController(search, {
|
||||
company: freshCwData.data,
|
||||
defaultContact: defaultContactData.data,
|
||||
allContacts: allContactsData.data,
|
||||
});
|
||||
},
|
||||
|
||||
async count() {
|
||||
|
||||
@@ -79,8 +79,6 @@ export const credentialTypes = {
|
||||
});
|
||||
}
|
||||
|
||||
console.log(data.fields);
|
||||
|
||||
const credentialType = await prisma.credentialType.create({
|
||||
data: {
|
||||
name: data.name,
|
||||
|
||||
+288
-26
@@ -4,10 +4,28 @@ import { fieldValidator } from "../modules/credentials/fieldValidator";
|
||||
import {
|
||||
CredentialField,
|
||||
CredentialTypeField,
|
||||
ValueType,
|
||||
} from "../modules/credentials/credentialTypeDefs";
|
||||
import { generateSecureValue } from "../modules/credentials/generateSecureValue";
|
||||
import GenericError from "../Errors/GenericError";
|
||||
|
||||
/**
|
||||
* Standard include clause used by every credential query.
|
||||
* Includes the credential type, company, secure values, and one level of sub-credentials.
|
||||
*/
|
||||
const credentialInclude = {
|
||||
type: true,
|
||||
company: true,
|
||||
securevalues: true,
|
||||
subCredentials: {
|
||||
include: {
|
||||
type: true,
|
||||
company: true,
|
||||
securevalues: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const credentials = {
|
||||
/**
|
||||
* Fetch Credential
|
||||
@@ -20,11 +38,7 @@ export const credentials = {
|
||||
async fetch(id: string): Promise<CredentialController> {
|
||||
const credential = await prisma.credential.findFirst({
|
||||
where: { id },
|
||||
include: {
|
||||
type: true,
|
||||
company: true,
|
||||
securevalues: true,
|
||||
},
|
||||
include: credentialInclude,
|
||||
});
|
||||
|
||||
if (!credential) {
|
||||
@@ -42,19 +56,17 @@ export const credentials = {
|
||||
/**
|
||||
* Fetch Credentials by Company
|
||||
*
|
||||
* Fetch all credentials associated with a specific company.
|
||||
* Fetch all top-level credentials associated with a specific company.
|
||||
* Sub-credentials are excluded from the top-level list and instead
|
||||
* included nested under their parent credential.
|
||||
*
|
||||
* @param companyId - The company ID to fetch credentials for
|
||||
* @returns {Promise<CredentialController[]>} - Array of credential controllers
|
||||
*/
|
||||
async fetchByCompany(companyId: string): Promise<CredentialController[]> {
|
||||
const credentialsList = await prisma.credential.findMany({
|
||||
where: { companyId },
|
||||
include: {
|
||||
type: true,
|
||||
company: true,
|
||||
securevalues: true,
|
||||
},
|
||||
where: { companyId, subCredentialOfId: null },
|
||||
include: credentialInclude,
|
||||
});
|
||||
|
||||
return credentialsList.map((cred) => new CredentialController(cred));
|
||||
@@ -68,6 +80,10 @@ export const credentials = {
|
||||
* the credential type, encrypting secure fields, and inserting everything
|
||||
* into the database atomically.
|
||||
*
|
||||
* When the credential type contains multi-credential fields, pass
|
||||
* `subCredentials` keyed by the multi-credential field ID. Each entry
|
||||
* is an array of sub-credential objects with their own name and fields.
|
||||
*
|
||||
* @param data - The credential data to create
|
||||
* @returns {Promise<CredentialController>} - The created credential controller
|
||||
*/
|
||||
@@ -80,6 +96,10 @@ export const credentials = {
|
||||
fieldId: string;
|
||||
value: string;
|
||||
}[];
|
||||
subCredentials?: Record<
|
||||
string,
|
||||
{ name: string; fields: { fieldId: string; value: string }[] }[]
|
||||
>;
|
||||
}): Promise<CredentialController> {
|
||||
// Fetch the credential type to get acceptable fields
|
||||
const credentialType = await prisma.credentialType.findFirst({
|
||||
@@ -95,22 +115,31 @@ export const credentials = {
|
||||
});
|
||||
}
|
||||
|
||||
// Validate the fields against acceptable fields
|
||||
const acceptableFields = (
|
||||
credentialType.fields! as any as CredentialTypeField[]
|
||||
).map((f: { id: string; name: string; secure: boolean }) => ({
|
||||
const typeFields = credentialType.fields! as any as CredentialTypeField[];
|
||||
|
||||
// Validate the fields against acceptable fields (exclude multi-credential fields
|
||||
// from value validation since they don't carry a direct value).
|
||||
const acceptableFields = typeFields.map((f) => ({
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
secure: f.secure,
|
||||
required: f.required,
|
||||
valueType: f.valueType,
|
||||
subFields: f.subFields,
|
||||
})) as CredentialTypeField[];
|
||||
|
||||
const validatedFields = await fieldValidator(
|
||||
data.fields as any as CredentialField[],
|
||||
acceptableFields,
|
||||
);
|
||||
|
||||
// Separate secure and non-secure fields
|
||||
const secureFields = validatedFields.filter((f) => f.secure);
|
||||
const nonSecureFields = validatedFields.filter((f) => !f.secure);
|
||||
// Separate secure, non-secure, and multi-credential fields
|
||||
const secureFields = validatedFields.filter(
|
||||
(f) => f.secure && !f.isMultiCredential,
|
||||
);
|
||||
const nonSecureFields = validatedFields.filter(
|
||||
(f) => !f.secure && !f.isMultiCredential,
|
||||
);
|
||||
|
||||
// Build fields object for non-secure fields
|
||||
const fieldsObject: Record<string, any> = {};
|
||||
@@ -118,6 +147,13 @@ export const credentials = {
|
||||
fieldsObject[field.fieldId] = field.value;
|
||||
});
|
||||
|
||||
// Initialise multi-credential field slots with empty arrays
|
||||
typeFields
|
||||
.filter((f) => f.valueType === ValueType.MULTI_CREDENTIAL)
|
||||
.forEach((f) => {
|
||||
fieldsObject[f.id] = [];
|
||||
});
|
||||
|
||||
// Encrypt secure field values
|
||||
const secureValueData = secureFields.map((field) => {
|
||||
const { encrypted, hash } = generateSecureValue(field.value);
|
||||
@@ -128,7 +164,7 @@ export const credentials = {
|
||||
};
|
||||
});
|
||||
|
||||
// Create credential and all secure values in a transaction
|
||||
// Create the parent credential first
|
||||
const credential = await prisma.credential.create({
|
||||
data: {
|
||||
name: data.name,
|
||||
@@ -140,20 +176,246 @@ export const credentials = {
|
||||
create: secureValueData,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
type: true,
|
||||
company: true,
|
||||
securevalues: true,
|
||||
},
|
||||
});
|
||||
|
||||
return new CredentialController(credential);
|
||||
// Create inline sub-credentials when provided
|
||||
if (data.subCredentials) {
|
||||
for (const [fieldId, subCredDataList] of Object.entries(
|
||||
data.subCredentials,
|
||||
)) {
|
||||
const fieldDef = typeFields.find((f) => f.id === fieldId);
|
||||
|
||||
if (!fieldDef || fieldDef.valueType !== ValueType.MULTI_CREDENTIAL) {
|
||||
throw new GenericError({
|
||||
message: `Field '${fieldId}' is not a multi-credential field`,
|
||||
name: "InvalidMultiCredentialField",
|
||||
cause: `Cannot create sub-credentials for field '${fieldId}' because it is not a multi-credential field.`,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const subFieldDefs = (fieldDef.subFields ??
|
||||
[]) as CredentialTypeField[];
|
||||
const subCredIds: string[] = [];
|
||||
|
||||
for (const subCredData of subCredDataList) {
|
||||
const validatedSubFields = await fieldValidator(
|
||||
subCredData.fields as any as CredentialField[],
|
||||
subFieldDefs,
|
||||
);
|
||||
|
||||
const subSecure = validatedSubFields.filter((f) => f.secure);
|
||||
const subNonSecure = validatedSubFields.filter((f) => !f.secure);
|
||||
|
||||
const subFieldsObject: Record<string, any> = {};
|
||||
subNonSecure.forEach((f) => {
|
||||
subFieldsObject[f.fieldId] = f.value;
|
||||
});
|
||||
|
||||
const subSecureValueData = subSecure.map((f) => {
|
||||
const { encrypted, hash } = generateSecureValue(f.value);
|
||||
return { name: f.fieldId, content: encrypted, hash };
|
||||
});
|
||||
|
||||
const subCred = await prisma.credential.create({
|
||||
data: {
|
||||
name: subCredData.name,
|
||||
typeId: data.typeId,
|
||||
companyId: data.companyId,
|
||||
subCredentialOfId: credential.id,
|
||||
fields: subFieldsObject,
|
||||
securevalues: { create: subSecureValueData },
|
||||
},
|
||||
});
|
||||
|
||||
subCredIds.push(subCred.id);
|
||||
}
|
||||
|
||||
fieldsObject[fieldId] = subCredIds;
|
||||
}
|
||||
|
||||
// Persist the sub-credential ID arrays on the parent
|
||||
await prisma.credential.update({
|
||||
where: { id: credential.id },
|
||||
data: { fields: fieldsObject },
|
||||
});
|
||||
}
|
||||
|
||||
// Re-fetch with full includes
|
||||
const completeCredential = await prisma.credential.findFirst({
|
||||
where: { id: credential.id },
|
||||
include: credentialInclude,
|
||||
});
|
||||
|
||||
return new CredentialController(completeCredential!);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add Sub-Credential
|
||||
*
|
||||
* Create a new sub-credential under an existing parent credential
|
||||
* for a specific multi-credential field.
|
||||
*
|
||||
* @param parentId - The parent credential ID
|
||||
* @param fieldId - The multi-credential field this sub-credential belongs to
|
||||
* @param data - The sub-credential data (name and fields)
|
||||
* @returns {Promise<CredentialController>} - The created sub-credential controller
|
||||
*/
|
||||
async addSubCredential(
|
||||
parentId: string,
|
||||
fieldId: string,
|
||||
data: {
|
||||
name: string;
|
||||
fields: { fieldId: string; value: string }[];
|
||||
},
|
||||
): Promise<CredentialController> {
|
||||
const parent = await prisma.credential.findFirst({
|
||||
where: { id: parentId },
|
||||
include: { type: true },
|
||||
});
|
||||
|
||||
if (!parent) {
|
||||
throw new GenericError({
|
||||
message: "Parent credential not found",
|
||||
name: "CredentialNotFound",
|
||||
cause: `No credential exists with ID '${parentId}'`,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const typeFields = parent.type.fields as any as CredentialTypeField[];
|
||||
const fieldDef = typeFields.find((f) => f.id === fieldId);
|
||||
|
||||
if (!fieldDef || fieldDef.valueType !== ValueType.MULTI_CREDENTIAL) {
|
||||
throw new GenericError({
|
||||
message: `Field '${fieldId}' is not a multi-credential field`,
|
||||
name: "InvalidMultiCredentialField",
|
||||
cause: `Cannot create sub-credentials for field '${fieldId}' because it is not a multi-credential field.`,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const subFieldDefs = (fieldDef.subFields ?? []) as CredentialTypeField[];
|
||||
const validatedFields = await fieldValidator(
|
||||
data.fields as any as CredentialField[],
|
||||
subFieldDefs,
|
||||
);
|
||||
|
||||
const secureFields = validatedFields.filter((f) => f.secure);
|
||||
const nonSecureFields = validatedFields.filter((f) => !f.secure);
|
||||
|
||||
const subFieldsObject: Record<string, any> = {};
|
||||
nonSecureFields.forEach((f) => {
|
||||
subFieldsObject[f.fieldId] = f.value;
|
||||
});
|
||||
|
||||
const secureValueData = secureFields.map((f) => {
|
||||
const { encrypted, hash } = generateSecureValue(f.value);
|
||||
return { name: f.fieldId, content: encrypted, hash };
|
||||
});
|
||||
|
||||
const subCredential = await prisma.credential.create({
|
||||
data: {
|
||||
name: data.name,
|
||||
typeId: parent.typeId,
|
||||
companyId: parent.companyId,
|
||||
subCredentialOfId: parentId,
|
||||
fields: subFieldsObject,
|
||||
securevalues: { create: secureValueData },
|
||||
},
|
||||
include: credentialInclude,
|
||||
});
|
||||
|
||||
// Update parent's fields JSON to include the new sub-credential ID
|
||||
const parentFields = parent.fields as Record<string, any>;
|
||||
const subCredIds: string[] = parentFields[fieldId] ?? [];
|
||||
subCredIds.push(subCredential.id);
|
||||
parentFields[fieldId] = subCredIds;
|
||||
|
||||
await prisma.credential.update({
|
||||
where: { id: parentId },
|
||||
data: { fields: parentFields },
|
||||
});
|
||||
|
||||
return new CredentialController(subCredential);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove Sub-Credential
|
||||
*
|
||||
* Delete a sub-credential and remove its reference from the parent credential's
|
||||
* multi-credential field.
|
||||
*
|
||||
* @param parentId - The parent credential ID
|
||||
* @param subCredentialId - The sub-credential ID to remove
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async removeSubCredential(
|
||||
parentId: string,
|
||||
subCredentialId: string,
|
||||
): Promise<void> {
|
||||
const subCredential = await prisma.credential.findFirst({
|
||||
where: { id: subCredentialId, subCredentialOfId: parentId },
|
||||
});
|
||||
|
||||
if (!subCredential) {
|
||||
throw new GenericError({
|
||||
message: "Sub-credential not found",
|
||||
name: "SubCredentialNotFound",
|
||||
cause: `No sub-credential with ID '${subCredentialId}' exists under credential '${parentId}'`,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
// Delete the sub-credential (cascade removes its secure values)
|
||||
await prisma.credential.delete({
|
||||
where: { id: subCredentialId },
|
||||
});
|
||||
|
||||
// Remove the sub-credential ID from the parent's fields JSON
|
||||
const parent = await prisma.credential.findFirst({
|
||||
where: { id: parentId },
|
||||
});
|
||||
|
||||
if (parent) {
|
||||
const parentFields = parent.fields as Record<string, any>;
|
||||
for (const key of Object.keys(parentFields)) {
|
||||
if (Array.isArray(parentFields[key])) {
|
||||
parentFields[key] = parentFields[key].filter(
|
||||
(id: string) => id !== subCredentialId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.credential.update({
|
||||
where: { id: parentId },
|
||||
data: { fields: parentFields },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch Sub-Credentials
|
||||
*
|
||||
* Fetch all sub-credentials that belong to a specific parent credential.
|
||||
*
|
||||
* @param parentId - The parent credential ID
|
||||
* @returns {Promise<CredentialController[]>} - Array of sub-credential controllers
|
||||
*/
|
||||
async fetchSubCredentials(parentId: string): Promise<CredentialController[]> {
|
||||
const subCredentials = await prisma.credential.findMany({
|
||||
where: { subCredentialOfId: parentId },
|
||||
include: credentialInclude,
|
||||
});
|
||||
|
||||
return subCredentials.map((sc) => new CredentialController(sc));
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete Credential
|
||||
*
|
||||
* Delete a credential by its ID.
|
||||
* Sub-credentials are cascade-deleted automatically by the database.
|
||||
*
|
||||
* @param id - The credential ID to delete
|
||||
* @returns {Promise<void>}
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import { prisma, unifi, unifiUsername, unifiPassword } from "../constants";
|
||||
import GenericError from "../Errors/GenericError";
|
||||
import { UnifiSite } from "../../generated/prisma/client";
|
||||
|
||||
let loggedIn = false;
|
||||
|
||||
async function ensureLoggedIn(): Promise<void> {
|
||||
if (loggedIn) return;
|
||||
if (!unifiPassword)
|
||||
throw new GenericError({
|
||||
name: "UnifiConfigError",
|
||||
message: "UniFi controller credentials are not configured.",
|
||||
status: 503,
|
||||
});
|
||||
await unifi.login(unifiUsername, unifiPassword);
|
||||
loggedIn = true;
|
||||
}
|
||||
|
||||
export const unifiSites = {
|
||||
/**
|
||||
* Fetch a UniFi site record from the database by its internal ID.
|
||||
*/
|
||||
async fetch(id: string): Promise<UnifiSite> {
|
||||
const site = await prisma.unifiSite.findFirst({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!site)
|
||||
throw new GenericError({
|
||||
name: "UnifiSiteNotFound",
|
||||
message: `UniFi site with id '${id}' was not found.`,
|
||||
status: 404,
|
||||
});
|
||||
|
||||
return site;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch all UniFi site records from the database.
|
||||
*/
|
||||
async fetchAll(): Promise<UnifiSite[]> {
|
||||
return prisma.unifiSite.findMany({
|
||||
include: { company: true },
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch all UniFi site records linked to a specific company.
|
||||
*/
|
||||
async fetchByCompany(companyId: string): Promise<UnifiSite[]> {
|
||||
return prisma.unifiSite.findMany({
|
||||
where: { companyId },
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Link a UniFi site to a company.
|
||||
*/
|
||||
async linkToCompany(siteId: string, companyId: string): Promise<UnifiSite> {
|
||||
const site = await prisma.unifiSite.findFirst({ where: { id: siteId } });
|
||||
if (!site)
|
||||
throw new GenericError({
|
||||
name: "UnifiSiteNotFound",
|
||||
message: `UniFi site '${siteId}' was not found.`,
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const company = await prisma.company.findFirst({
|
||||
where: { id: companyId },
|
||||
});
|
||||
if (!company)
|
||||
throw new GenericError({
|
||||
name: "CompanyNotFound",
|
||||
message: `Company '${companyId}' was not found.`,
|
||||
status: 404,
|
||||
});
|
||||
|
||||
return prisma.unifiSite.update({
|
||||
where: { id: siteId },
|
||||
data: { companyId },
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Unlink a UniFi site from its company.
|
||||
*/
|
||||
async unlinkFromCompany(siteId: string): Promise<UnifiSite> {
|
||||
return prisma.unifiSite.update({
|
||||
where: { id: siteId },
|
||||
data: { companyId: null },
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sync all sites from the UniFi controller into the database.
|
||||
* Creates new records for sites not yet tracked, updates names for existing ones.
|
||||
*/
|
||||
async syncSites(): Promise<UnifiSite[]> {
|
||||
await ensureLoggedIn();
|
||||
|
||||
// Fetch all sites from the controller
|
||||
const allSites = await unifi.getAllSites();
|
||||
|
||||
const results: UnifiSite[] = [];
|
||||
|
||||
for (const site of allSites) {
|
||||
const existing = await prisma.unifiSite.findFirst({
|
||||
where: { siteId: site.name },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
const updated = await prisma.unifiSite.update({
|
||||
where: { id: existing.id },
|
||||
data: { name: site.description },
|
||||
});
|
||||
results.push(updated);
|
||||
} else {
|
||||
const created = await prisma.unifiSite.create({
|
||||
data: {
|
||||
name: site.description,
|
||||
siteId: site.name,
|
||||
},
|
||||
});
|
||||
results.push(created);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get live site overview from the UniFi controller.
|
||||
*/
|
||||
async getSiteOverview(siteId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getSiteOverview(siteId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get live devices from the UniFi controller for a site.
|
||||
*/
|
||||
async getDevices(siteId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getDevices(siteId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get live WiFi networks (WLANs) from the UniFi controller for a site.
|
||||
*/
|
||||
async getWlanConf(siteId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getWlanConf(siteId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a WiFi network on the UniFi controller.
|
||||
*/
|
||||
async updateWlanConf(
|
||||
siteId: string,
|
||||
wlanId: string,
|
||||
updates: Parameters<typeof unifi.updateWlanConf>[2],
|
||||
) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.updateWlanConf(siteId, wlanId, updates);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get live network configurations from the UniFi controller for a site.
|
||||
*/
|
||||
async getNetworks(siteId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getNetworks(siteId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new site on the UniFi controller and track it in the database.
|
||||
*/
|
||||
async createSite(description: string): Promise<UnifiSite> {
|
||||
await ensureLoggedIn();
|
||||
|
||||
const created = await unifi.createSite(description);
|
||||
|
||||
return prisma.unifiSite.create({
|
||||
data: {
|
||||
name: created.description,
|
||||
siteId: created.name,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get WLAN groups from the UniFi controller for a site.
|
||||
*/
|
||||
async getWlanGroups(siteId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getWlanGroups(siteId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new WLAN group (AP broadcasting group) on the UniFi controller.
|
||||
*/
|
||||
async createWlanGroup(
|
||||
siteId: string,
|
||||
input: Parameters<typeof unifi.createWlanGroup>[1],
|
||||
) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.createWlanGroup(siteId, input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get user groups (speed profiles) from the UniFi controller for a site.
|
||||
*/
|
||||
async getUserGroups(siteId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getUserGroups(siteId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new user group (speed profile) on the UniFi controller.
|
||||
*/
|
||||
async createUserGroup(
|
||||
siteId: string,
|
||||
input: Parameters<typeof unifi.createUserGroup>[1],
|
||||
) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.createUserGroup(siteId, input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get AP groups from the UniFi controller for a site.
|
||||
*/
|
||||
async getApGroups(siteId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getApGroups(siteId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new AP group on the UniFi controller.
|
||||
*/
|
||||
async createApGroup(
|
||||
siteId: string,
|
||||
name: string,
|
||||
deviceMacs: string[],
|
||||
forWlanconf: boolean = false,
|
||||
) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.createApGroup(siteId, name, deviceMacs, forWlanconf);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an existing AP group's device MACs on the UniFi controller.
|
||||
*/
|
||||
async updateApGroup(siteId: string, groupId: string, deviceMacs: string[]) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.updateApGroup(siteId, groupId, deviceMacs);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get access points only from the UniFi controller for a site.
|
||||
*/
|
||||
async getAccessPoints(siteId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getAccessPoints(siteId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get WiFi SSID limits per AP per radio band.
|
||||
*/
|
||||
async getWifiLimits(siteId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getWifiLimits(siteId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get private pre-shared keys for a specific WLAN.
|
||||
*/
|
||||
async getPrivatePSKs(siteId: string, wlanId: string) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.getPrivatePSKs(siteId, wlanId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a private pre-shared key on a specific WLAN.
|
||||
*/
|
||||
async createPrivatePSK(
|
||||
siteId: string,
|
||||
wlanId: string,
|
||||
psk: Parameters<typeof unifi.createPrivatePSK>[2],
|
||||
) {
|
||||
await ensureLoggedIn();
|
||||
return unifi.createPrivatePSK(siteId, wlanId, psk);
|
||||
},
|
||||
};
|
||||
@@ -5,12 +5,14 @@ export enum ValueType {
|
||||
GENERIC_SECRET = "generic_secret",
|
||||
BITLOCKER_KEY = "bitlocker_key",
|
||||
PASSWORD = "password",
|
||||
MULTI_CREDENTIAL = "multi_credential",
|
||||
}
|
||||
|
||||
export interface CredentialTypeField {
|
||||
id: string; // I.e. "clientId", "clientSecret", etc.
|
||||
name: string; // I.e. "Client ID", "Client Secret", etc.
|
||||
required: boolean;
|
||||
subFields?: CredentialTypeField[]; // For multi-credential fields, defines the sub-fields that are required
|
||||
secure: boolean; // Whether this field should be stored encrypted in the database
|
||||
valueType: ValueType; // For future extensibility, currently all fields are strings
|
||||
}
|
||||
@@ -18,4 +20,5 @@ export interface CredentialTypeField {
|
||||
export interface CredentialField {
|
||||
fieldId: string; // I.e. "clientId", "clientSecret", etc.
|
||||
value: string; // Encrypted value stored in the database
|
||||
subCredentials?: string[]; // For multi-credential fields, the IDs of the sub-credentials that are associated with this field
|
||||
}
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import { Collection } from "@discordjs/collection";
|
||||
import { CredentialField, CredentialTypeField } from "./credentialTypeDefs";
|
||||
import {
|
||||
CredentialField,
|
||||
CredentialTypeField,
|
||||
ValueType,
|
||||
} from "./credentialTypeDefs";
|
||||
import GenericError from "../../Errors/GenericError";
|
||||
|
||||
export interface ValidatedField {
|
||||
fieldId: string;
|
||||
value: string;
|
||||
secure: boolean;
|
||||
isMultiCredential?: boolean;
|
||||
subCredentials?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Field Validator
|
||||
*
|
||||
@@ -11,19 +23,16 @@ import GenericError from "../../Errors/GenericError";
|
||||
* If all the credentials pass, it will return a processed version of the submitted fields including fields that need to be
|
||||
* stored securely (encrypted) and fields that do not.
|
||||
*
|
||||
* Multi-credential fields are handled specially — they don't carry a direct value but instead
|
||||
* reference sub-credential IDs.
|
||||
*
|
||||
* @param fields - The fields in object form that are being submitted.
|
||||
* @param acceptableFields - The acceptable field to be compared against.
|
||||
*/
|
||||
export const fieldValidator = async (
|
||||
fields: CredentialField[],
|
||||
acceptableFields: CredentialTypeField[],
|
||||
): Promise<
|
||||
{
|
||||
fieldId: string;
|
||||
value: string;
|
||||
secure: boolean;
|
||||
}[]
|
||||
> => {
|
||||
): Promise<ValidatedField[]> => {
|
||||
const afCollection = new Collection(acceptableFields.map((f) => [f.id, f]));
|
||||
|
||||
await Promise.all(
|
||||
@@ -45,6 +54,18 @@ export const fieldValidator = async (
|
||||
return fields.map((field) => {
|
||||
const matchingField = afCollection.get(field.fieldId)!;
|
||||
|
||||
// Multi-credential fields don't carry a direct value;
|
||||
// they reference sub-credential IDs instead.
|
||||
if (matchingField.valueType === ValueType.MULTI_CREDENTIAL) {
|
||||
return {
|
||||
fieldId: field.fieldId,
|
||||
value: "",
|
||||
secure: false,
|
||||
isMultiCredential: true,
|
||||
subCredentials: field.subCredentials ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
fieldId: field.fieldId,
|
||||
value: field.value,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import UserController from "../../controllers/UserController";
|
||||
|
||||
export const processObjectValuePerms = async <T>(
|
||||
obj: T,
|
||||
scope: string, // e.g. "unifi.wifi.read"
|
||||
user: UserController,
|
||||
): Promise<Partial<T>> => {
|
||||
let result: Partial<T> = {};
|
||||
|
||||
for (const key in obj) {
|
||||
if (await user.hasPermission(`${scope}.${key}`)) {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const processObjectPermMap = async <T extends Record<string, unknown>>(
|
||||
obj: T,
|
||||
scope: string,
|
||||
user: UserController,
|
||||
): Promise<Record<keyof T, boolean>> => {
|
||||
const result = {} as Record<keyof T, boolean>;
|
||||
|
||||
for (const key in obj) {
|
||||
result[key] = await user.hasPermission(`${scope}.${key}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -0,0 +1,952 @@
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import https from "https";
|
||||
import {
|
||||
ApGroup,
|
||||
ApRadioWifiUsage,
|
||||
ApWifiLimits,
|
||||
CreateSiteOptions,
|
||||
Device,
|
||||
DeviceRadio,
|
||||
DeviceState,
|
||||
DeviceUplink,
|
||||
Network,
|
||||
PrivatePSK,
|
||||
PrivatePSKCreateInput,
|
||||
SiteListItem,
|
||||
SiteOverview,
|
||||
SubsystemHealth,
|
||||
SysInfo,
|
||||
UserGroup,
|
||||
UserGroupCreateInput,
|
||||
WlanConf,
|
||||
WlanConfRaw,
|
||||
WlanConfUpdate,
|
||||
WlanGroup,
|
||||
WlanGroupCreateInput,
|
||||
} from "./unifiTypes";
|
||||
|
||||
export class UnifiClient {
|
||||
private client: AxiosInstance;
|
||||
|
||||
constructor(baseURL: string) {
|
||||
this.client = axios.create({
|
||||
baseURL,
|
||||
validateStatus: (s) => s >= 200 && s < 400,
|
||||
httpsAgent: new https.Agent({ rejectUnauthorized: false }),
|
||||
});
|
||||
}
|
||||
|
||||
private persistSession(res: { headers: Record<string, unknown> }): void {
|
||||
// Cookies
|
||||
const raw = res.headers["set-cookie"];
|
||||
if (raw) {
|
||||
const cookies = (Array.isArray(raw) ? raw : [raw]) as string[];
|
||||
const cookieString = cookies.map((c) => c.split(";")[0]).join("; ");
|
||||
this.client.defaults.headers.common["Cookie"] = cookieString;
|
||||
}
|
||||
// CSRF token (UniFi OS)
|
||||
const csrf = res.headers["x-csrf-token"];
|
||||
if (typeof csrf === "string") {
|
||||
this.client.defaults.headers.common["X-CSRF-Token"] = csrf;
|
||||
}
|
||||
}
|
||||
|
||||
async login(username: string, password: string): Promise<void> {
|
||||
const body = { username, password };
|
||||
|
||||
try {
|
||||
// UniFi OS
|
||||
const res = await this.client.post("/api/auth/login", body);
|
||||
console.log("Login OK (UniFi OS)", res.status);
|
||||
this.persistSession(res);
|
||||
} catch (e) {
|
||||
// Legacy controller
|
||||
console.log("UniFi OS login failed, trying legacy...");
|
||||
const res = await this.client.post("/api/login", body);
|
||||
console.log("Login OK (legacy)", res.status);
|
||||
this.persistSession(res);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchWlanConfRaw(site: string): Promise<WlanConfRaw[]> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/wlanconf`,
|
||||
`/api/s/${site}/rest/wlanconf`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const data = (res.data?.data ?? res.data) as WlanConfRaw[];
|
||||
console.log(`Fetched wlan from ${path}`);
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.log(
|
||||
`Failed ${path}:`,
|
||||
axios.isAxiosError(e) ? e.response?.status : e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch WLAN config from any known path");
|
||||
}
|
||||
|
||||
private static parseWlanConf(w: any): WlanConf {
|
||||
return {
|
||||
id: w._id,
|
||||
name: (w.name || w.ssid || "").toString(),
|
||||
siteId: w.site_id ?? "",
|
||||
enabled: w.enabled ?? true,
|
||||
security: w.security ?? "open",
|
||||
wpaMode: w.wpa_mode ?? "",
|
||||
wpaEnc: w.wpa_enc ?? "",
|
||||
wpa3Support: w.wpa3_support ?? false,
|
||||
wpa3Transition: w.wpa3_transition ?? false,
|
||||
wpa3FastRoaming: w.wpa3_fast_roaming ?? false,
|
||||
wpa3Enhanced192: w.wpa3_enhanced_192 ?? false,
|
||||
passphrase: typeof w.x_passphrase === "string" ? w.x_passphrase : null,
|
||||
passphraseAutogenerated: w.passphrase_autogenerated ?? false,
|
||||
hideSSID: w.hide_ssid ?? false,
|
||||
isGuest: w.is_guest ?? false,
|
||||
band: w.wlan_band ?? "both",
|
||||
bands: w.wlan_bands ?? [],
|
||||
networkconfId: w.networkconf_id ?? "",
|
||||
usergroupId: w.usergroup_id ?? "",
|
||||
apGroupIds: w.ap_group_ids ?? [],
|
||||
apGroupMode: w.ap_group_mode ?? "devices",
|
||||
pmfMode: w.pmf_mode ?? "disabled",
|
||||
groupRekey: w.group_rekey ?? 0,
|
||||
dtimMode: w.dtim_mode ?? "default",
|
||||
dtimNg: w.dtim_ng ?? 1,
|
||||
dtimNa: w.dtim_na ?? 3,
|
||||
dtim6e: w.dtim_6e ?? 3,
|
||||
l2Isolation: w.l2_isolation ?? false,
|
||||
fastRoamingEnabled: w.fast_roaming_enabled ?? false,
|
||||
bssTransition: w.bss_transition ?? false,
|
||||
uapsdEnabled: w.uapsd_enabled ?? false,
|
||||
iappEnabled: w.iapp_enabled ?? false,
|
||||
proxyArp: w.proxy_arp ?? false,
|
||||
mcastenhanceEnabled: w.mcastenhance_enabled ?? false,
|
||||
macFilterEnabled: w.mac_filter_enabled ?? false,
|
||||
macFilterPolicy: w.mac_filter_policy ?? "allow",
|
||||
macFilterList: w.mac_filter_list ?? [],
|
||||
radiusDasEnabled: w.radius_das_enabled ?? false,
|
||||
radiusMacAuthEnabled: w.radius_mac_auth_enabled ?? false,
|
||||
radiusMacaclFormat: w.radius_macacl_format ?? "none_lower",
|
||||
minrateSettingPreference: w.minrate_setting_preference ?? "auto",
|
||||
minrateNgEnabled: w.minrate_ng_enabled ?? false,
|
||||
minrateNgDataRateKbps: w.minrate_ng_data_rate_kbps ?? 1000,
|
||||
minrateNgAdvertisingRates: w.minrate_ng_advertising_rates ?? false,
|
||||
minrateNaEnabled: w.minrate_na_enabled ?? false,
|
||||
minrateNaDataRateKbps: w.minrate_na_data_rate_kbps ?? 6000,
|
||||
minrateNaAdvertisingRates: w.minrate_na_advertising_rates ?? false,
|
||||
settingPreference: w.setting_preference ?? "auto",
|
||||
no2ghzOui: w.no2ghz_oui ?? false,
|
||||
privatePreSharedKeysEnabled: w.private_preshared_keys_enabled ?? false,
|
||||
privatePreSharedKeys: w.private_preshared_keys ?? [],
|
||||
saeGroups: w.sae_groups ?? [],
|
||||
saePsk: w.sae_psk ?? [],
|
||||
schedule: w.schedule ?? [],
|
||||
scheduleWithDuration: w.schedule_with_duration ?? [],
|
||||
bcFilterList: w.bc_filter_list ?? [],
|
||||
externalId: w.external_id ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async getWlanConf(site: string): Promise<WlanConf[]> {
|
||||
const raw = await this.fetchWlanConfRaw(site);
|
||||
return raw.map(UnifiClient.parseWlanConf);
|
||||
}
|
||||
|
||||
async updateWlanConf(
|
||||
site: string,
|
||||
wlanId: string,
|
||||
updates: WlanConfUpdate,
|
||||
): Promise<WlanConf> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/wlanconf/${wlanId}`,
|
||||
`/api/s/${site}/rest/wlanconf/${wlanId}`,
|
||||
];
|
||||
|
||||
// Fetch current WLAN to check if a RADIUS profile is configured.
|
||||
// The controller rejects RADIUS fields when no profile is set.
|
||||
const currentWlans = await this.getWlanConf(site);
|
||||
const currentWlan = currentWlans.find((w) => w.id === wlanId);
|
||||
const hasRadius =
|
||||
currentWlan?.security === "wpaeap" || updates.security === "wpaeap";
|
||||
|
||||
if (!hasRadius) {
|
||||
delete updates.radius_das_enabled;
|
||||
delete updates.radius_mac_auth_enabled;
|
||||
}
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.put(path, updates);
|
||||
const raw = (res.data?.data?.[0] ?? res.data) as any;
|
||||
return UnifiClient.parseWlanConf(raw);
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
// Try next path on 404/401, throw on other errors
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw new Error(
|
||||
`Failed to update WLAN ${wlanId}: ${e.response.status} ${JSON.stringify(e.response.data)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not update WLAN config from any known path");
|
||||
}
|
||||
|
||||
async getAllSites(): Promise<SiteListItem[]> {
|
||||
const paths = ["/proxy/network/api/self/sites", "/api/self/sites"];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const raw = (res.data?.data ?? res.data) as any[];
|
||||
return raw.map(
|
||||
(s: any): SiteListItem => ({
|
||||
id: s._id,
|
||||
name: s.name,
|
||||
description: s.desc ?? "",
|
||||
deviceCount: s.device_count ?? 0,
|
||||
role: s.role ?? "",
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch sites from any known path");
|
||||
}
|
||||
|
||||
async getSiteOverview(site: string): Promise<SiteOverview> {
|
||||
const prefixes = ["/proxy/network", ""];
|
||||
|
||||
for (const prefix of prefixes) {
|
||||
try {
|
||||
const [healthRes, sysInfoRes, sitesRes] = await Promise.all([
|
||||
this.client.get(`${prefix}/api/s/${site}/stat/health`),
|
||||
this.client.get(`${prefix}/api/s/${site}/stat/sysinfo`),
|
||||
this.client.get(`${prefix}/api/self/sites`),
|
||||
]);
|
||||
|
||||
const healthRaw = (healthRes.data?.data ?? healthRes.data) as any[];
|
||||
const sysInfoRaw = (sysInfoRes.data?.data?.[0] ??
|
||||
sysInfoRes.data) as any;
|
||||
const sitesRaw = (sitesRes.data?.data ?? sitesRes.data) as any[];
|
||||
|
||||
const siteRaw = sitesRaw.find((s: any) => s.name === site);
|
||||
if (!siteRaw) throw new Error(`Site "${site}" not found in sites list`);
|
||||
|
||||
const health: SubsystemHealth[] = healthRaw.map((h: any) => ({
|
||||
subsystem: h.subsystem,
|
||||
status: h.status,
|
||||
numUser: h.num_user,
|
||||
numGuest: h.num_guest,
|
||||
numIot: h.num_iot,
|
||||
txBytesR: h["tx_bytes-r"],
|
||||
rxBytesR: h["rx_bytes-r"],
|
||||
numAp: h.num_ap,
|
||||
numSw: h.num_sw,
|
||||
numGw: h.num_gw,
|
||||
numAdopted: h.num_adopted,
|
||||
numDisconnected: h.num_disconnected,
|
||||
numPending: h.num_pending,
|
||||
numDisabled: h.num_disabled,
|
||||
}));
|
||||
|
||||
const sysInfo: SysInfo = {
|
||||
name: sysInfoRaw.name,
|
||||
hostname: sysInfoRaw.hostname,
|
||||
version: sysInfoRaw.version,
|
||||
build: sysInfoRaw.build,
|
||||
timezone: sysInfoRaw.timezone,
|
||||
uptime: sysInfoRaw.uptime,
|
||||
ipAddresses: sysInfoRaw.ip_addrs ?? [],
|
||||
updateAvailable: sysInfoRaw.update_available ?? false,
|
||||
isCloudConsole: sysInfoRaw.is_cloud_console ?? false,
|
||||
dataRetentionDays: sysInfoRaw.data_retention_days ?? 0,
|
||||
informPort: sysInfoRaw.inform_port,
|
||||
httpsPort: sysInfoRaw.https_port,
|
||||
unsupportedDeviceCount: sysInfoRaw.unsupported_device_count ?? 0,
|
||||
};
|
||||
|
||||
return {
|
||||
site: {
|
||||
id: siteRaw._id,
|
||||
name: siteRaw.name,
|
||||
description: siteRaw.desc ?? "",
|
||||
deviceCount: siteRaw.device_count ?? 0,
|
||||
role: siteRaw.role ?? "",
|
||||
},
|
||||
health,
|
||||
sysInfo,
|
||||
};
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch site overview from any known path");
|
||||
}
|
||||
|
||||
private static parseDeviceState(state: number): DeviceState {
|
||||
const map: Record<number, DeviceState> = {
|
||||
0: "disconnected",
|
||||
1: "connected",
|
||||
2: "pending",
|
||||
4: "adopting",
|
||||
5: "adopting",
|
||||
};
|
||||
return map[state] ?? "unknown";
|
||||
}
|
||||
|
||||
async getDevices(site: string): Promise<Device[]> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/stat/device`,
|
||||
`/api/s/${site}/stat/device`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const raw = (res.data?.data ?? res.data) as any[];
|
||||
|
||||
return raw.map((d: any): Device => {
|
||||
const uplink: DeviceUplink | null = d.uplink
|
||||
? {
|
||||
type: d.uplink.type,
|
||||
mac: d.uplink.uplink_mac,
|
||||
ip: d.uplink.uplink_remote_ip,
|
||||
uplinkRemotePort: d.uplink.uplink_remote_port,
|
||||
speed: d.uplink.speed,
|
||||
fullDuplex: d.uplink.full_duplex,
|
||||
}
|
||||
: null;
|
||||
|
||||
const radios: DeviceRadio[] = (d.radio_table ?? []).map(
|
||||
(r: any, i: number) => {
|
||||
const stats = d.radio_table_stats?.[i] ?? {};
|
||||
return {
|
||||
name: r.name ?? r.radio,
|
||||
radio: r.radio,
|
||||
channel: r.channel,
|
||||
txPower: r.tx_power,
|
||||
txPowerMode: r.tx_power_mode,
|
||||
minRssiEnabled: r.min_rssi_enabled ?? false,
|
||||
numSta: stats.num_sta ?? 0,
|
||||
satisfaction: stats.satisfaction ?? null,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
id: d._id,
|
||||
mac: d.mac,
|
||||
ip: d.ip ?? "",
|
||||
name: d.name ?? d.mac,
|
||||
model: d.model,
|
||||
shortname: d.shortname ?? d.model,
|
||||
type: d.type,
|
||||
version: d.version ?? "",
|
||||
serial: d.serial ?? "",
|
||||
state: UnifiClient.parseDeviceState(d.state),
|
||||
adopted: d.adopted ?? false,
|
||||
uptime: d.uptime ?? 0,
|
||||
lastSeen: d.last_seen ?? 0,
|
||||
upgradable: d.upgradable ?? false,
|
||||
satisfaction: d.satisfaction ?? null,
|
||||
numClients: d.num_sta ?? 0,
|
||||
numUserClients: d["user-num_sta"] ?? 0,
|
||||
numGuestClients: d["guest-num_sta"] ?? 0,
|
||||
txBytes: d.tx_bytes ?? 0,
|
||||
rxBytes: d.rx_bytes ?? 0,
|
||||
uplink,
|
||||
radios,
|
||||
modelInLts: d.model_in_lts ?? false,
|
||||
modelInEol: d.model_in_eol ?? false,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch devices from any known path");
|
||||
}
|
||||
|
||||
async getNetworks(site: string): Promise<Network[]> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/networkconf`,
|
||||
`/api/s/${site}/rest/networkconf`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const raw = (res.data?.data ?? res.data) as any[];
|
||||
|
||||
return raw.map(
|
||||
(n: any): Network => ({
|
||||
id: n._id,
|
||||
name: n.name ?? "",
|
||||
purpose: n.purpose ?? "corporate",
|
||||
enabled: n.enabled ?? true,
|
||||
ipSubnet: n.ip_subnet ?? null,
|
||||
vlan: n.vlan != null ? Number(n.vlan) : null,
|
||||
vlanEnabled: n.vlan_enabled ?? false,
|
||||
isNat: n.is_nat ?? false,
|
||||
domainName: n.domain_name ?? null,
|
||||
networkGroup: n.networkgroup ?? null,
|
||||
dhcpdEnabled: n.dhcpd_enabled ?? false,
|
||||
dhcpdStart: n.dhcpd_start ?? null,
|
||||
dhcpdStop: n.dhcpd_stop ?? null,
|
||||
dhcpdLeasetime: n.dhcpd_leasetime ?? null,
|
||||
dhcpRelayEnabled: n.dhcp_relay_enabled ?? false,
|
||||
dhcpGuardEnabled: n.dhcpguard_enabled ?? false,
|
||||
igmpSnooping: n.igmp_snooping ?? false,
|
||||
ipv6Enabled: n.ipv6_enabled ?? false,
|
||||
ipv6InterfaceType: n.ipv6_interface_type ?? null,
|
||||
internetAccessEnabled: n.internet_access_enabled ?? null,
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch networks from any known path");
|
||||
}
|
||||
|
||||
async createSite(description: string): Promise<SiteListItem> {
|
||||
const paths = [
|
||||
"/proxy/network/api/s/default/cmd/sitemgr",
|
||||
"/api/s/default/cmd/sitemgr",
|
||||
];
|
||||
|
||||
const body = { cmd: "add-site", desc: description };
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.post(path, body);
|
||||
const raw = (res.data?.data?.[0] ?? res.data) as any;
|
||||
return {
|
||||
id: raw._id,
|
||||
name: raw.name,
|
||||
description: raw.desc ?? description,
|
||||
deviceCount: raw.device_count ?? 0,
|
||||
role: raw.role ?? "",
|
||||
};
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw new Error(
|
||||
`Failed to create site: ${e.response.status} ${JSON.stringify(e.response.data)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not create site from any known path");
|
||||
}
|
||||
|
||||
// --- WLAN Groups ---
|
||||
|
||||
async getWlanGroups(site: string): Promise<WlanGroup[]> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/wlangroup`,
|
||||
`/api/s/${site}/rest/wlangroup`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const raw = (res.data?.data ?? res.data) as any[];
|
||||
return raw.map(
|
||||
(g: any): WlanGroup => ({
|
||||
id: g._id,
|
||||
name: g.name ?? "",
|
||||
siteId: g.site_id ?? "",
|
||||
noDelete: g.attr_no_delete ?? false,
|
||||
noEdit: g.attr_no_edit ?? false,
|
||||
hidden: g.attr_hidden ?? false,
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch WLAN groups from any known path");
|
||||
}
|
||||
|
||||
async createWlanGroup(
|
||||
site: string,
|
||||
input: WlanGroupCreateInput,
|
||||
): Promise<WlanGroup> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/wlangroup`,
|
||||
`/api/s/${site}/rest/wlangroup`,
|
||||
];
|
||||
|
||||
const body: Record<string, unknown> = { name: input.name };
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.post(path, body);
|
||||
const raw = (res.data?.data?.[0] ?? res.data) as any;
|
||||
return {
|
||||
id: raw._id,
|
||||
name: raw.name ?? input.name,
|
||||
siteId: raw.site_id ?? "",
|
||||
noDelete: raw.attr_no_delete ?? false,
|
||||
noEdit: raw.attr_no_edit ?? false,
|
||||
hidden: raw.attr_hidden ?? false,
|
||||
};
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw new Error(
|
||||
`Failed to create WLAN group: ${e.response.status} ${JSON.stringify(e.response.data)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not create WLAN group from any known path");
|
||||
}
|
||||
|
||||
// --- User Groups (Speed Profiles) ---
|
||||
|
||||
async getUserGroups(site: string): Promise<UserGroup[]> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/usergroup`,
|
||||
`/api/s/${site}/rest/usergroup`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const raw = (res.data?.data ?? res.data) as any[];
|
||||
return raw.map(
|
||||
(g: any): UserGroup => ({
|
||||
id: g._id,
|
||||
name: g.name ?? "",
|
||||
siteId: g.site_id ?? "",
|
||||
noDelete: g.attr_no_delete ?? false,
|
||||
downloadLimitKbps: g.qos_rate_max_down ?? -1,
|
||||
uploadLimitKbps: g.qos_rate_max_up ?? -1,
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch user groups from any known path");
|
||||
}
|
||||
|
||||
async createUserGroup(
|
||||
site: string,
|
||||
input: UserGroupCreateInput,
|
||||
): Promise<UserGroup> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/usergroup`,
|
||||
`/api/s/${site}/rest/usergroup`,
|
||||
];
|
||||
|
||||
const body: Record<string, unknown> = { name: input.name };
|
||||
if (input.downloadLimitKbps !== undefined)
|
||||
body.qos_rate_max_down = input.downloadLimitKbps;
|
||||
if (input.uploadLimitKbps !== undefined)
|
||||
body.qos_rate_max_up = input.uploadLimitKbps;
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.post(path, body);
|
||||
const raw = (res.data?.data?.[0] ?? res.data) as any;
|
||||
return {
|
||||
id: raw._id,
|
||||
name: raw.name ?? input.name,
|
||||
siteId: raw.site_id ?? "",
|
||||
noDelete: raw.attr_no_delete ?? false,
|
||||
downloadLimitKbps: raw.qos_rate_max_down ?? -1,
|
||||
uploadLimitKbps: raw.qos_rate_max_up ?? -1,
|
||||
};
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw new Error(
|
||||
`Failed to create user group: ${e.response.status} ${JSON.stringify(e.response.data)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not create user group from any known path");
|
||||
}
|
||||
|
||||
// --- AP Groups ---
|
||||
|
||||
async getApGroups(site: string): Promise<ApGroup[]> {
|
||||
const paths = [
|
||||
`/proxy/network/v2/api/site/${site}/apgroups`,
|
||||
`/v2/api/site/${site}/apgroups`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const raw = (res.data?.data ?? res.data) as any[];
|
||||
return raw.map(
|
||||
(g: any): ApGroup => ({
|
||||
id: g._id,
|
||||
name: g.name ?? "",
|
||||
deviceMacs: g.device_macs ?? [],
|
||||
noDelete: g.attr_no_delete ?? false,
|
||||
forWlanconf: g.for_wlanconf ?? false,
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch AP groups from any known path");
|
||||
}
|
||||
|
||||
async createApGroup(
|
||||
site: string,
|
||||
name: string,
|
||||
deviceMacs: string[],
|
||||
forWlanconf: boolean = false,
|
||||
): Promise<ApGroup> {
|
||||
const paths = [
|
||||
`/proxy/network/v2/api/site/${site}/apgroups`,
|
||||
`/v2/api/site/${site}/apgroups`,
|
||||
];
|
||||
|
||||
const body = {
|
||||
name,
|
||||
device_macs: deviceMacs,
|
||||
for_wlanconf: forWlanconf,
|
||||
};
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.post(path, body);
|
||||
const raw = (res.data?.data?.[0] ?? res.data) as any;
|
||||
return {
|
||||
id: raw._id,
|
||||
name: raw.name ?? name,
|
||||
deviceMacs: raw.device_macs ?? deviceMacs,
|
||||
noDelete: raw.attr_no_delete ?? false,
|
||||
forWlanconf: raw.for_wlanconf ?? forWlanconf,
|
||||
};
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw new Error(
|
||||
`Failed to create AP group: ${e.response.status} ${JSON.stringify(e.response.data)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not create AP group from any known path");
|
||||
}
|
||||
|
||||
async updateApGroup(
|
||||
site: string,
|
||||
groupId: string,
|
||||
deviceMacs: string[],
|
||||
): Promise<ApGroup> {
|
||||
const paths = [
|
||||
`/proxy/network/v2/api/site/${site}/apgroups/${groupId}`,
|
||||
`/v2/api/site/${site}/apgroups/${groupId}`,
|
||||
];
|
||||
|
||||
const body = {
|
||||
name: "devices_ap_group",
|
||||
device_macs: deviceMacs,
|
||||
for_wlanconf: true,
|
||||
};
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.put(path, body);
|
||||
const raw = (res.data?.data?.[0] ?? res.data) as any;
|
||||
return {
|
||||
id: raw._id ?? groupId,
|
||||
name: raw.name ?? "devices_ap_group",
|
||||
deviceMacs: raw.device_macs ?? deviceMacs,
|
||||
noDelete: raw.attr_no_delete ?? false,
|
||||
forWlanconf: raw.for_wlanconf ?? true,
|
||||
};
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw new Error(
|
||||
`Failed to update AP group: ${e.response.status} ${JSON.stringify(e.response.data)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not update AP group from any known path");
|
||||
}
|
||||
|
||||
// --- Access Points ---
|
||||
|
||||
async getAccessPoints(site: string): Promise<Device[]> {
|
||||
const devices = await this.getDevices(site);
|
||||
return devices.filter((d) => d.type === "uap");
|
||||
}
|
||||
|
||||
// --- WiFi Limits ---
|
||||
|
||||
async getWifiLimits(site: string): Promise<ApWifiLimits[]> {
|
||||
const SSID_LIMIT_PER_RADIO = 8;
|
||||
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/stat/device`,
|
||||
`/api/s/${site}/stat/device`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const raw = (res.data?.data ?? res.data) as any[];
|
||||
const aps = raw.filter((d: any) => d.type === "uap");
|
||||
|
||||
return aps.map((ap: any): ApWifiLimits => {
|
||||
const vapTable: any[] = ap.vap_table ?? [];
|
||||
const radioMap = new Map<string, { wlanNames: Set<string> }>();
|
||||
|
||||
for (const vap of vapTable) {
|
||||
if (!vap.up || !vap.radio) continue;
|
||||
if (!radioMap.has(vap.radio)) {
|
||||
radioMap.set(vap.radio, { wlanNames: new Set() });
|
||||
}
|
||||
if (vap.essid) {
|
||||
radioMap.get(vap.radio)!.wlanNames.add(vap.essid);
|
||||
}
|
||||
}
|
||||
|
||||
const radioBandMap: Record<string, string> = {
|
||||
ng: "2g",
|
||||
na: "5g",
|
||||
"6e": "6e",
|
||||
};
|
||||
|
||||
const radios: ApRadioWifiUsage[] = Array.from(radioMap.entries()).map(
|
||||
([radio, data]): ApRadioWifiUsage => ({
|
||||
radio,
|
||||
band: radioBandMap[radio] ?? radio,
|
||||
activeWlans: data.wlanNames.size,
|
||||
limit: SSID_LIMIT_PER_RADIO,
|
||||
remaining: Math.max(
|
||||
0,
|
||||
SSID_LIMIT_PER_RADIO - data.wlanNames.size,
|
||||
),
|
||||
wlanNames: Array.from(data.wlanNames),
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
apId: ap._id,
|
||||
apName: ap.name ?? ap.mac,
|
||||
mac: ap.mac,
|
||||
model: ap.model ?? "",
|
||||
radios,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch WiFi limits from any known path");
|
||||
}
|
||||
|
||||
// --- Private Pre-Shared Keys ---
|
||||
|
||||
private static parsePPSKs(raw: any[]): PrivatePSK[] {
|
||||
return raw.map(
|
||||
(p: any): PrivatePSK => ({
|
||||
key: p.key ?? "",
|
||||
name: p.name ?? "",
|
||||
mac: p.mac ?? null,
|
||||
vlanId: p.vlan_id ?? null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async getPrivatePSKs(site: string, wlanId: string): Promise<PrivatePSK[]> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/wlanconf/${wlanId}`,
|
||||
`/api/s/${site}/rest/wlanconf/${wlanId}`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const raw = (res.data?.data?.[0] ?? res.data) as any;
|
||||
return UnifiClient.parsePPSKs(raw.private_preshared_keys ?? []);
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch PPSKs from any known path");
|
||||
}
|
||||
|
||||
async createPrivatePSK(
|
||||
site: string,
|
||||
wlanId: string,
|
||||
psk: PrivatePSKCreateInput,
|
||||
): Promise<PrivatePSK[]> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/wlanconf/${wlanId}`,
|
||||
`/api/s/${site}/rest/wlanconf/${wlanId}`,
|
||||
];
|
||||
|
||||
// Fetch current PPSKs
|
||||
let currentPpsks: any[] = [];
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const raw = (res.data?.data?.[0] ?? res.data) as any;
|
||||
currentPpsks = (raw.private_preshared_keys ?? []) as any[];
|
||||
break;
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const newPsk: Record<string, unknown> = {
|
||||
key: psk.key,
|
||||
name: psk.name,
|
||||
};
|
||||
if (psk.mac) newPsk.mac = psk.mac;
|
||||
if (psk.vlanId !== undefined) newPsk.vlan_id = psk.vlanId;
|
||||
currentPpsks.push(newPsk);
|
||||
|
||||
// Update WLAN with new PPSKs
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.put(path, {
|
||||
private_preshared_keys: currentPpsks,
|
||||
private_preshared_keys_enabled: true,
|
||||
});
|
||||
const raw = (res.data?.data?.[0] ?? res.data) as any;
|
||||
return UnifiClient.parsePPSKs(raw.private_preshared_keys ?? []);
|
||||
} catch (e) {
|
||||
if (!axios.isAxiosError(e)) throw e;
|
||||
if (
|
||||
e.response &&
|
||||
e.response.status !== 404 &&
|
||||
e.response.status !== 401
|
||||
) {
|
||||
throw new Error(
|
||||
`Failed to create PPSK: ${e.response.status} ${JSON.stringify(e.response.data)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not create PPSK from any known path");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
export interface WlanConfRaw {
|
||||
_id: string;
|
||||
name?: string;
|
||||
ssid?: string;
|
||||
x_passphrase?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface WlanConf {
|
||||
id: string;
|
||||
name: string;
|
||||
siteId: string;
|
||||
enabled: boolean;
|
||||
security: string;
|
||||
wpaMode: string;
|
||||
wpaEnc: string;
|
||||
wpa3Support: boolean;
|
||||
wpa3Transition: boolean;
|
||||
wpa3FastRoaming: boolean;
|
||||
wpa3Enhanced192: boolean;
|
||||
passphrase: string | null;
|
||||
passphraseAutogenerated: boolean;
|
||||
hideSSID: boolean;
|
||||
isGuest: boolean;
|
||||
band: string;
|
||||
bands: string[];
|
||||
networkconfId: string;
|
||||
usergroupId: string;
|
||||
apGroupIds: string[];
|
||||
apGroupMode: string;
|
||||
pmfMode: string;
|
||||
groupRekey: number;
|
||||
dtimMode: string;
|
||||
dtimNg: number;
|
||||
dtimNa: number;
|
||||
dtim6e: number;
|
||||
l2Isolation: boolean;
|
||||
fastRoamingEnabled: boolean;
|
||||
bssTransition: boolean;
|
||||
uapsdEnabled: boolean;
|
||||
iappEnabled: boolean;
|
||||
proxyArp: boolean;
|
||||
mcastenhanceEnabled: boolean;
|
||||
macFilterEnabled: boolean;
|
||||
macFilterPolicy: string;
|
||||
macFilterList: string[];
|
||||
radiusDasEnabled: boolean;
|
||||
radiusMacAuthEnabled: boolean;
|
||||
radiusMacaclFormat: string;
|
||||
minrateSettingPreference: string;
|
||||
minrateNgEnabled: boolean;
|
||||
minrateNgDataRateKbps: number;
|
||||
minrateNgAdvertisingRates: boolean;
|
||||
minrateNaEnabled: boolean;
|
||||
minrateNaDataRateKbps: number;
|
||||
minrateNaAdvertisingRates: boolean;
|
||||
settingPreference: string;
|
||||
no2ghzOui: boolean;
|
||||
privatePreSharedKeysEnabled: boolean;
|
||||
privatePreSharedKeys: unknown[];
|
||||
saeGroups: unknown[];
|
||||
saePsk: unknown[];
|
||||
schedule: unknown[];
|
||||
scheduleWithDuration: unknown[];
|
||||
bcFilterList: unknown[];
|
||||
externalId: string | null;
|
||||
}
|
||||
|
||||
export interface WlanConfUpdate {
|
||||
name?: string;
|
||||
x_passphrase?: string;
|
||||
enabled?: boolean;
|
||||
security?: "wpapsk" | "wpaeap" | "open" | "osen";
|
||||
wpa_mode?: "wpa2" | "wpa3" | "wpa2wpa3";
|
||||
wpa_enc?: "ccmp" | "gcmp" | "ccmp-gcmp";
|
||||
hide_ssid?: boolean;
|
||||
mac_filter_enabled?: boolean;
|
||||
mac_filter_policy?: "allow" | "deny";
|
||||
is_guest?: boolean;
|
||||
l2_isolation?: boolean;
|
||||
fast_roaming_enabled?: boolean;
|
||||
bss_transition?: boolean;
|
||||
uapsd_enabled?: boolean;
|
||||
group_rekey?: number;
|
||||
dtim_mode?: "default" | "custom";
|
||||
dtim_ng?: number;
|
||||
dtim_na?: number;
|
||||
minrate_ng_enabled?: boolean;
|
||||
minrate_na_enabled?: boolean;
|
||||
radius_das_enabled?: boolean;
|
||||
radius_mac_auth_enabled?: boolean;
|
||||
pmf_mode?: "disabled" | "optional" | "required";
|
||||
wlan_band?: "both" | "2g" | "5g";
|
||||
usergroup_id?: string;
|
||||
proxy_arp?: boolean;
|
||||
mcastenhance_enabled?: boolean;
|
||||
mac_filter_list?: string[];
|
||||
no2ghz_oui?: boolean;
|
||||
ap_group_ids?: string[];
|
||||
ap_group_mode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* CamelCase update input matching the WlanConf return shape.
|
||||
* Accepted by the API and converted to WlanConfUpdate (snake_case) before
|
||||
* being sent to the UniFi controller.
|
||||
*/
|
||||
export interface WlanConfUpdateInput {
|
||||
name?: string;
|
||||
passphrase?: string;
|
||||
enabled?: boolean;
|
||||
security?: "wpapsk" | "wpaeap" | "open" | "osen";
|
||||
wpaMode?: "wpa2" | "wpa3" | "wpa2wpa3";
|
||||
wpaEnc?: "ccmp" | "gcmp" | "ccmp-gcmp";
|
||||
hideSSID?: boolean;
|
||||
macFilterEnabled?: boolean;
|
||||
macFilterPolicy?: "allow" | "deny";
|
||||
isGuest?: boolean;
|
||||
l2Isolation?: boolean;
|
||||
fastRoamingEnabled?: boolean;
|
||||
bssTransition?: boolean;
|
||||
uapsdEnabled?: boolean;
|
||||
groupRekey?: number;
|
||||
dtimMode?: "default" | "custom";
|
||||
dtimNg?: number;
|
||||
dtimNa?: number;
|
||||
minrateNgEnabled?: boolean;
|
||||
minrateNaEnabled?: boolean;
|
||||
radiusDasEnabled?: boolean;
|
||||
radiusMacAuthEnabled?: boolean;
|
||||
pmfMode?: "disabled" | "optional" | "required";
|
||||
band?: "both" | "2g" | "5g";
|
||||
usergroupId?: string;
|
||||
proxyArp?: boolean;
|
||||
mcastenhanceEnabled?: boolean;
|
||||
macFilterList?: string[];
|
||||
no2ghzOui?: boolean;
|
||||
apGroupIds?: string[];
|
||||
apGroupMode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a camelCase WlanConfUpdateInput to the snake_case WlanConfUpdate
|
||||
* expected by the UniFi controller API.
|
||||
*/
|
||||
export function toWlanConfUpdate(input: WlanConfUpdateInput): WlanConfUpdate {
|
||||
const result: WlanConfUpdate = {};
|
||||
|
||||
if (input.name !== undefined) result.name = input.name;
|
||||
if (input.passphrase !== undefined) result.x_passphrase = input.passphrase;
|
||||
if (input.enabled !== undefined) result.enabled = input.enabled;
|
||||
if (input.security !== undefined) result.security = input.security;
|
||||
if (input.wpaMode !== undefined) result.wpa_mode = input.wpaMode;
|
||||
if (input.wpaEnc !== undefined) result.wpa_enc = input.wpaEnc;
|
||||
if (input.hideSSID !== undefined) result.hide_ssid = input.hideSSID;
|
||||
if (input.macFilterEnabled !== undefined)
|
||||
result.mac_filter_enabled = input.macFilterEnabled;
|
||||
if (input.macFilterPolicy !== undefined)
|
||||
result.mac_filter_policy = input.macFilterPolicy;
|
||||
if (input.isGuest !== undefined) result.is_guest = input.isGuest;
|
||||
if (input.l2Isolation !== undefined) result.l2_isolation = input.l2Isolation;
|
||||
if (input.fastRoamingEnabled !== undefined)
|
||||
result.fast_roaming_enabled = input.fastRoamingEnabled;
|
||||
if (input.bssTransition !== undefined)
|
||||
result.bss_transition = input.bssTransition;
|
||||
if (input.uapsdEnabled !== undefined)
|
||||
result.uapsd_enabled = input.uapsdEnabled;
|
||||
if (input.groupRekey !== undefined) result.group_rekey = input.groupRekey;
|
||||
if (input.dtimMode !== undefined) result.dtim_mode = input.dtimMode;
|
||||
if (input.dtimNg !== undefined) result.dtim_ng = input.dtimNg;
|
||||
if (input.dtimNa !== undefined) result.dtim_na = input.dtimNa;
|
||||
if (input.minrateNgEnabled !== undefined)
|
||||
result.minrate_ng_enabled = input.minrateNgEnabled;
|
||||
if (input.minrateNaEnabled !== undefined)
|
||||
result.minrate_na_enabled = input.minrateNaEnabled;
|
||||
if (input.radiusDasEnabled !== undefined)
|
||||
result.radius_das_enabled = input.radiusDasEnabled;
|
||||
if (input.radiusMacAuthEnabled !== undefined)
|
||||
result.radius_mac_auth_enabled = input.radiusMacAuthEnabled;
|
||||
if (input.pmfMode !== undefined) result.pmf_mode = input.pmfMode;
|
||||
if (input.band !== undefined) result.wlan_band = input.band;
|
||||
if (input.usergroupId !== undefined) result.usergroup_id = input.usergroupId;
|
||||
if (input.proxyArp !== undefined) result.proxy_arp = input.proxyArp;
|
||||
if (input.mcastenhanceEnabled !== undefined)
|
||||
result.mcastenhance_enabled = input.mcastenhanceEnabled;
|
||||
if (input.macFilterList !== undefined)
|
||||
result.mac_filter_list = input.macFilterList;
|
||||
if (input.no2ghzOui !== undefined) result.no2ghz_oui = input.no2ghzOui;
|
||||
if (input.apGroupIds !== undefined) result.ap_group_ids = input.apGroupIds;
|
||||
if (input.apGroupMode !== undefined) result.ap_group_mode = input.apGroupMode;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- Site overview types ---
|
||||
|
||||
export interface SubsystemHealth {
|
||||
subsystem: "wlan" | "wan" | "www" | "lan" | "vpn";
|
||||
status: "ok" | "warn" | "error" | "unknown";
|
||||
numUser?: number;
|
||||
numGuest?: number;
|
||||
numIot?: number;
|
||||
txBytesR?: number;
|
||||
rxBytesR?: number;
|
||||
// WLAN-specific
|
||||
numAp?: number;
|
||||
// LAN-specific
|
||||
numSw?: number;
|
||||
// WAN-specific
|
||||
numGw?: number;
|
||||
// Shared device counts
|
||||
numAdopted?: number;
|
||||
numDisconnected?: number;
|
||||
numPending?: number;
|
||||
numDisabled?: number;
|
||||
}
|
||||
|
||||
export interface SysInfo {
|
||||
name: string;
|
||||
hostname: string;
|
||||
version: string;
|
||||
build: string;
|
||||
timezone: string;
|
||||
uptime: number;
|
||||
ipAddresses: string[];
|
||||
updateAvailable: boolean;
|
||||
isCloudConsole: boolean;
|
||||
dataRetentionDays: number;
|
||||
informPort: number;
|
||||
httpsPort: number;
|
||||
unsupportedDeviceCount: number;
|
||||
}
|
||||
|
||||
export interface SiteInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
deviceCount: number;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export interface SiteOverview {
|
||||
site: SiteInfo;
|
||||
health: SubsystemHealth[];
|
||||
sysInfo: SysInfo;
|
||||
}
|
||||
|
||||
// --- Device types ---
|
||||
|
||||
export type DeviceType = "uap" | "usw" | "ugw" | "uxg" | "ubb" | "udm";
|
||||
export type DeviceState =
|
||||
| "connected"
|
||||
| "disconnected"
|
||||
| "pending"
|
||||
| "adopting"
|
||||
| "unknown";
|
||||
|
||||
export interface DeviceUplink {
|
||||
type?: string;
|
||||
mac?: string;
|
||||
ip?: string;
|
||||
uplinkRemotePort?: number;
|
||||
speed?: number;
|
||||
fullDuplex?: boolean;
|
||||
}
|
||||
|
||||
export interface DeviceRadio {
|
||||
name: string;
|
||||
radio: string;
|
||||
channel: number;
|
||||
txPower: number;
|
||||
txPowerMode: string;
|
||||
minRssiEnabled: boolean;
|
||||
numSta: number;
|
||||
satisfaction: number | null;
|
||||
}
|
||||
|
||||
export interface Device {
|
||||
id: string;
|
||||
mac: string;
|
||||
ip: string;
|
||||
name: string;
|
||||
model: string;
|
||||
shortname: string;
|
||||
type: DeviceType;
|
||||
version: string;
|
||||
serial: string;
|
||||
state: DeviceState;
|
||||
adopted: boolean;
|
||||
uptime: number;
|
||||
lastSeen: number;
|
||||
upgradable: boolean;
|
||||
satisfaction: number | null;
|
||||
numClients: number;
|
||||
numUserClients: number;
|
||||
numGuestClients: number;
|
||||
txBytes: number;
|
||||
rxBytes: number;
|
||||
uplink: DeviceUplink | null;
|
||||
radios: DeviceRadio[];
|
||||
modelInLts: boolean;
|
||||
modelInEol: boolean;
|
||||
}
|
||||
|
||||
// --- Network types ---
|
||||
|
||||
export type NetworkPurpose =
|
||||
| "corporate"
|
||||
| "vlan-only"
|
||||
| "wan"
|
||||
| "vpn-client"
|
||||
| "remote-user-vpn"
|
||||
| "site-vpn";
|
||||
|
||||
export interface Network {
|
||||
id: string;
|
||||
name: string;
|
||||
purpose: NetworkPurpose;
|
||||
enabled: boolean;
|
||||
ipSubnet: string | null;
|
||||
vlan: number | null;
|
||||
vlanEnabled: boolean;
|
||||
isNat: boolean;
|
||||
domainName: string | null;
|
||||
networkGroup: string | null;
|
||||
dhcpdEnabled: boolean;
|
||||
dhcpdStart: string | null;
|
||||
dhcpdStop: string | null;
|
||||
dhcpdLeasetime: number | null;
|
||||
dhcpRelayEnabled: boolean;
|
||||
dhcpGuardEnabled: boolean;
|
||||
igmpSnooping: boolean;
|
||||
ipv6Enabled: boolean;
|
||||
ipv6InterfaceType: string | null;
|
||||
internetAccessEnabled: boolean | null;
|
||||
}
|
||||
|
||||
// --- Site create types ---
|
||||
|
||||
export interface CreateSiteOptions {
|
||||
/** Human-readable description / display name for the site */
|
||||
description: string;
|
||||
}
|
||||
|
||||
// --- Site list types ---
|
||||
|
||||
export interface SiteListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
deviceCount: number;
|
||||
role: string;
|
||||
}
|
||||
|
||||
// --- WLAN Group types ---
|
||||
|
||||
export interface WlanGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
siteId: string;
|
||||
noDelete: boolean;
|
||||
noEdit: boolean;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
export interface WlanGroupCreateInput {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// --- AP Group types ---
|
||||
|
||||
export interface ApGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
deviceMacs: string[];
|
||||
noDelete: boolean;
|
||||
forWlanconf: boolean;
|
||||
}
|
||||
|
||||
// --- User Group (Speed Profile) types ---
|
||||
|
||||
export interface UserGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
siteId: string;
|
||||
noDelete: boolean;
|
||||
/** Download rate limit in Kbps. -1 means unlimited. */
|
||||
downloadLimitKbps: number;
|
||||
/** Upload rate limit in Kbps. -1 means unlimited. */
|
||||
uploadLimitKbps: number;
|
||||
}
|
||||
|
||||
export interface UserGroupCreateInput {
|
||||
name: string;
|
||||
/** Download rate limit in Kbps. -1 or omit for unlimited. */
|
||||
downloadLimitKbps?: number;
|
||||
/** Upload rate limit in Kbps. -1 or omit for unlimited. */
|
||||
uploadLimitKbps?: number;
|
||||
}
|
||||
|
||||
// --- Private PSK types ---
|
||||
|
||||
export interface PrivatePSK {
|
||||
key: string;
|
||||
name: string;
|
||||
mac: string | null;
|
||||
vlanId: number | null;
|
||||
}
|
||||
|
||||
export interface PrivatePSKCreateInput {
|
||||
key: string;
|
||||
name: string;
|
||||
mac?: string;
|
||||
vlanId?: number;
|
||||
}
|
||||
|
||||
// --- WiFi Limit types ---
|
||||
|
||||
export interface ApRadioWifiUsage {
|
||||
radio: string;
|
||||
band: string;
|
||||
activeWlans: number;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
wlanNames: string[];
|
||||
}
|
||||
|
||||
export interface ApWifiLimits {
|
||||
apId: string;
|
||||
apName: string;
|
||||
mac: string;
|
||||
model: string;
|
||||
radios: ApRadioWifiUsage[];
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export interface Company {
|
||||
territory: Territory;
|
||||
market: Market;
|
||||
accountNumber: string;
|
||||
defaultContact: Contact;
|
||||
defaultContact: ContactHref;
|
||||
dateAcquired: string;
|
||||
annualRevenue: number;
|
||||
numberOfEmployees: number;
|
||||
@@ -27,7 +27,7 @@ export interface Company {
|
||||
billingTerms: BasicEntity;
|
||||
billToCompany: LinkedCompany;
|
||||
billingSite: LinkedSite;
|
||||
billingContact: Contact;
|
||||
billingContact: ContactHref;
|
||||
invoiceDeliveryMethod: BasicEntity;
|
||||
invoiceToEmailAddress: string;
|
||||
deletedFlag: boolean;
|
||||
@@ -91,7 +91,7 @@ export interface LinkedSite extends BasicEntity {
|
||||
};
|
||||
}
|
||||
|
||||
export interface Contact {
|
||||
export interface ContactHref {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
@@ -305,3 +305,140 @@ export interface ConfigurationInfo {
|
||||
|
||||
// Your payload is an array:
|
||||
export type ConfigurationResponse = ConfigurationItem[];
|
||||
export interface Contact {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
company: ContactCompany;
|
||||
site: ContactSite;
|
||||
relationship: ContactRelationship;
|
||||
department: ContactDepartment;
|
||||
inactiveFlag: boolean;
|
||||
title: string;
|
||||
marriedFlag: boolean;
|
||||
childrenFlag: boolean;
|
||||
disablePortalLoginFlag: boolean;
|
||||
unsubscribeFlag: boolean;
|
||||
mobileGuid: string;
|
||||
facebookUrl: string;
|
||||
twitterUrl: string;
|
||||
linkedInUrl: string;
|
||||
defaultPhoneType: string;
|
||||
defaultPhoneNbr: string;
|
||||
defaultBillingFlag: boolean;
|
||||
defaultFlag: boolean;
|
||||
companyLocation: ContactCompanyLocation;
|
||||
communicationItems: ContactCommunicationItem[];
|
||||
types: ContactType[];
|
||||
customFields: ContactCustomField[];
|
||||
photo: ContactPhoto;
|
||||
ignoreDuplicates: boolean;
|
||||
_info: ContactInfo;
|
||||
}
|
||||
|
||||
export interface ContactCompany {
|
||||
id: number;
|
||||
identifier: string;
|
||||
name: string;
|
||||
_info: {
|
||||
company_href: string;
|
||||
mobileGuid: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContactSite {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
site_href: string;
|
||||
mobileGuid: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContactRelationship {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
relationship_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContactDepartment {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
department_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContactCompanyLocation {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
location_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type CommunicationTypeEnum = "Phone" | "Fax" | "Email"; // from CW docs [web:40]
|
||||
|
||||
export interface ContactCommunicationItem {
|
||||
id: number;
|
||||
type: {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
type_href: string;
|
||||
};
|
||||
};
|
||||
value: string;
|
||||
defaultFlag: boolean;
|
||||
communicationType: CommunicationTypeEnum;
|
||||
domain?: string; // only present for email entries
|
||||
}
|
||||
|
||||
export interface ContactType {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
type_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContactCustomField {
|
||||
id: number;
|
||||
caption: string;
|
||||
type: string;
|
||||
entryMethod: string;
|
||||
numberOfDecimals: number;
|
||||
value: string | number | boolean | null;
|
||||
connectWiseId: string;
|
||||
rowNum: number;
|
||||
userDefinedFieldRecId: number;
|
||||
podId: string;
|
||||
}
|
||||
|
||||
export interface ContactPhoto {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
filename: string;
|
||||
document_href: string;
|
||||
documentDownload_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContactInfo {
|
||||
lastUpdated: string; // ISO datetime
|
||||
updatedBy: string;
|
||||
communications_href: string;
|
||||
notes_href: string;
|
||||
tracks_href: string;
|
||||
portalSecurity_href: string;
|
||||
activities_href: string;
|
||||
documents_href: string;
|
||||
configurations_href: string;
|
||||
tickets_href: string;
|
||||
opportunities_href: string;
|
||||
projects_href: string;
|
||||
image_href: string;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,13 @@ export interface PermissionNode {
|
||||
usedIn: string[];
|
||||
/** Dependencies - other permissions that must be granted alongside this one */
|
||||
dependencies?: string[];
|
||||
/**
|
||||
* When present, indicates this permission gates individual fields on the
|
||||
* response object via `processObjectValuePerms`. Each entry is a full
|
||||
* permission node in the form `<scope>.<fieldName>`. Only fields whose
|
||||
* corresponding permission the user holds are included in the response.
|
||||
*/
|
||||
fieldLevelPermissions?: string[];
|
||||
}
|
||||
|
||||
export interface PermissionCategory {
|
||||
@@ -56,6 +63,12 @@ export const PERMISSION_NODES = {
|
||||
usedIn: ["src/api/companies/[id]/fetch.ts"],
|
||||
dependencies: ["company.fetch"],
|
||||
},
|
||||
{
|
||||
node: "company.fetch.contacts",
|
||||
description: "View all company contacts",
|
||||
usedIn: ["src/api/companies/[id]/fetch.ts"],
|
||||
dependencies: ["company.fetch"],
|
||||
},
|
||||
{
|
||||
node: "company.fetch.many",
|
||||
description: "Fetch multiple companies",
|
||||
@@ -120,6 +133,24 @@ export const PERMISSION_NODES = {
|
||||
],
|
||||
dependencies: ["credential.fetch"],
|
||||
},
|
||||
{
|
||||
node: "credential.sub_credentials.fetch",
|
||||
description: "Fetch sub-credentials of a parent credential",
|
||||
usedIn: ["src/api/credentials/fetchSubCredentials.ts"],
|
||||
dependencies: ["credential.fetch"],
|
||||
},
|
||||
{
|
||||
node: "credential.sub_credentials.create",
|
||||
description: "Create a sub-credential on a parent credential",
|
||||
usedIn: ["src/api/credentials/addSubCredential.ts"],
|
||||
dependencies: ["credential.fetch"],
|
||||
},
|
||||
{
|
||||
node: "credential.sub_credentials.delete",
|
||||
description: "Remove a sub-credential from a parent credential",
|
||||
usedIn: ["src/api/credentials/removeSubCredential.ts"],
|
||||
dependencies: ["credential.fetch"],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -309,6 +340,242 @@ export const PERMISSION_NODES = {
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
unifi: {
|
||||
name: "UniFi Permissions",
|
||||
description:
|
||||
"Permissions for accessing and managing UniFi network infrastructure",
|
||||
permissions: [
|
||||
{
|
||||
node: "unifi.access",
|
||||
description:
|
||||
"Gate permission for the entire UniFi API — required for all UniFi routes",
|
||||
usedIn: [
|
||||
"src/api/unifi/sites/fetchAll.ts",
|
||||
"src/api/unifi/sites/sync.ts",
|
||||
"src/api/unifi/site/fetch.ts",
|
||||
"src/api/unifi/site/overview.ts",
|
||||
"src/api/unifi/site/devices.ts",
|
||||
"src/api/unifi/site/wifi/fetchAll.ts",
|
||||
"src/api/unifi/site/wifi/update.ts",
|
||||
"src/api/unifi/site/wifi/ppskFetchAll.ts",
|
||||
"src/api/unifi/site/wifi/ppskCreate.ts",
|
||||
"src/api/unifi/site/networks.ts",
|
||||
"src/api/unifi/site/link.ts",
|
||||
"src/api/unifi/site/unlink.ts",
|
||||
"src/api/companies/[id]/unifiSites.ts",
|
||||
"src/api/unifi/sites/create.ts",
|
||||
"src/api/unifi/site/wlanGroups.ts",
|
||||
"src/api/unifi/site/accessPoints.ts",
|
||||
"src/api/unifi/site/wifiLimits.ts",
|
||||
"src/api/unifi/site/speedProfilesFetchAll.ts",
|
||||
"src/api/unifi/site/speedProfilesCreate.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
node: "unifi.sites.create",
|
||||
description: "Create a new site on the UniFi controller",
|
||||
usedIn: ["src/api/unifi/sites/create.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.sites.fetch",
|
||||
description: "Fetch a single UniFi site",
|
||||
usedIn: ["src/api/unifi/site/fetch.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.sites.fetch.many",
|
||||
description: "Fetch all UniFi sites",
|
||||
usedIn: ["src/api/unifi/sites/fetchAll.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.sites.sync",
|
||||
description: "Sync sites from the UniFi controller into the database",
|
||||
usedIn: ["src/api/unifi/sites/sync.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.sites.link",
|
||||
description: "Link or unlink a UniFi site to/from a company",
|
||||
usedIn: ["src/api/unifi/site/link.ts", "src/api/unifi/site/unlink.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.overview",
|
||||
description: "View live site overview from the UniFi controller",
|
||||
usedIn: ["src/api/unifi/site/overview.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.devices",
|
||||
description: "View live device list from the UniFi controller",
|
||||
usedIn: ["src/api/unifi/site/devices.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.wifi",
|
||||
description: "View WiFi networks (WLANs) from the UniFi controller",
|
||||
usedIn: ["src/api/unifi/site/wifi/fetchAll.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.wifi.read",
|
||||
description:
|
||||
"Field-level gate for WiFi network response data. Each key on the WlanConf object is checked as unifi.site.wifi.read.<field>. Only fields the user has permission for are included in the response.",
|
||||
usedIn: ["src/api/unifi/site/wifi/fetchAll.ts"],
|
||||
dependencies: ["unifi.access", "unifi.site.wifi"],
|
||||
fieldLevelPermissions: [
|
||||
"unifi.site.wifi.read.id",
|
||||
"unifi.site.wifi.read.name",
|
||||
"unifi.site.wifi.read.siteId",
|
||||
"unifi.site.wifi.read.enabled",
|
||||
"unifi.site.wifi.read.security",
|
||||
"unifi.site.wifi.read.wpaMode",
|
||||
"unifi.site.wifi.read.wpaEnc",
|
||||
"unifi.site.wifi.read.wpa3Support",
|
||||
"unifi.site.wifi.read.wpa3Transition",
|
||||
"unifi.site.wifi.read.wpa3FastRoaming",
|
||||
"unifi.site.wifi.read.wpa3Enhanced192",
|
||||
"unifi.site.wifi.read.passphrase",
|
||||
"unifi.site.wifi.read.passphraseAutogenerated",
|
||||
"unifi.site.wifi.read.hideSSID",
|
||||
"unifi.site.wifi.read.isGuest",
|
||||
"unifi.site.wifi.read.band",
|
||||
"unifi.site.wifi.read.bands",
|
||||
"unifi.site.wifi.read.networkconfId",
|
||||
"unifi.site.wifi.read.usergroupId",
|
||||
"unifi.site.wifi.read.apGroupIds",
|
||||
"unifi.site.wifi.read.apGroupMode",
|
||||
"unifi.site.wifi.read.pmfMode",
|
||||
"unifi.site.wifi.read.groupRekey",
|
||||
"unifi.site.wifi.read.dtimMode",
|
||||
"unifi.site.wifi.read.dtimNg",
|
||||
"unifi.site.wifi.read.dtimNa",
|
||||
"unifi.site.wifi.read.dtim6e",
|
||||
"unifi.site.wifi.read.l2Isolation",
|
||||
"unifi.site.wifi.read.fastRoamingEnabled",
|
||||
"unifi.site.wifi.read.bssTransition",
|
||||
"unifi.site.wifi.read.uapsdEnabled",
|
||||
"unifi.site.wifi.read.iappEnabled",
|
||||
"unifi.site.wifi.read.proxyArp",
|
||||
"unifi.site.wifi.read.mcastenhanceEnabled",
|
||||
"unifi.site.wifi.read.macFilterEnabled",
|
||||
"unifi.site.wifi.read.macFilterPolicy",
|
||||
"unifi.site.wifi.read.macFilterList",
|
||||
"unifi.site.wifi.read.radiusDasEnabled",
|
||||
"unifi.site.wifi.read.radiusMacAuthEnabled",
|
||||
"unifi.site.wifi.read.radiusMacaclFormat",
|
||||
"unifi.site.wifi.read.minrateSettingPreference",
|
||||
"unifi.site.wifi.read.minrateNgEnabled",
|
||||
"unifi.site.wifi.read.minrateNgDataRateKbps",
|
||||
"unifi.site.wifi.read.minrateNgAdvertisingRates",
|
||||
"unifi.site.wifi.read.minrateNaEnabled",
|
||||
"unifi.site.wifi.read.minrateNaDataRateKbps",
|
||||
"unifi.site.wifi.read.minrateNaAdvertisingRates",
|
||||
"unifi.site.wifi.read.settingPreference",
|
||||
"unifi.site.wifi.read.no2ghzOui",
|
||||
"unifi.site.wifi.read.privatePreSharedKeysEnabled",
|
||||
"unifi.site.wifi.read.privatePreSharedKeys",
|
||||
"unifi.site.wifi.read.saeGroups",
|
||||
"unifi.site.wifi.read.saePsk",
|
||||
"unifi.site.wifi.read.schedule",
|
||||
"unifi.site.wifi.read.scheduleWithDuration",
|
||||
"unifi.site.wifi.read.bcFilterList",
|
||||
"unifi.site.wifi.read.externalId",
|
||||
],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.wifi.update",
|
||||
description: "Update a WiFi network on the UniFi controller",
|
||||
usedIn: ["src/api/unifi/site/wifi/update.ts"],
|
||||
dependencies: ["unifi.access", "unifi.site.wifi"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.networks",
|
||||
description: "View network configurations from the UniFi controller",
|
||||
usedIn: ["src/api/unifi/site/networks.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.wlan-groups",
|
||||
description:
|
||||
"View WLAN groups (AP broadcasting groups) from the UniFi controller for a site",
|
||||
usedIn: [
|
||||
"src/api/unifi/site/wlanGroups.ts",
|
||||
"src/api/unifi/site/wlanGroupsCreate.ts",
|
||||
],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.wlan-groups.create",
|
||||
description:
|
||||
"Create a new WLAN group (AP broadcasting group) on the UniFi controller",
|
||||
usedIn: ["src/api/unifi/site/wlanGroupsCreate.ts"],
|
||||
dependencies: ["unifi.access", "unifi.site.wlan-groups"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.access-points",
|
||||
description:
|
||||
"View access points (UAPs only) from the UniFi controller for a site",
|
||||
usedIn: ["src/api/unifi/site/accessPoints.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.ap-groups",
|
||||
description:
|
||||
"View AP groups from the UniFi controller — shows which access points are grouped together for SSID broadcasting",
|
||||
usedIn: ["src/api/unifi/site/apGroups.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.wifi-limits",
|
||||
description:
|
||||
"View WiFi SSID limits per access point per radio band — shows how many SSIDs each AP radio can still accept",
|
||||
usedIn: ["src/api/unifi/site/wifiLimits.ts"],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.speed-profiles",
|
||||
description:
|
||||
"View speed limit profiles (user groups) from the UniFi controller",
|
||||
usedIn: [
|
||||
"src/api/unifi/site/speedProfilesFetchAll.ts",
|
||||
"src/api/unifi/site/speedProfilesCreate.ts",
|
||||
],
|
||||
dependencies: ["unifi.access"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.speed-profiles.create",
|
||||
description:
|
||||
"Create a new speed limit profile (user group) on the UniFi controller",
|
||||
usedIn: ["src/api/unifi/site/speedProfilesCreate.ts"],
|
||||
dependencies: ["unifi.access", "unifi.site.speed-profiles"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.wifi.ppsk",
|
||||
description:
|
||||
"View private pre-shared keys (PPSKs) for a specific WiFi network",
|
||||
usedIn: [
|
||||
"src/api/unifi/site/wifi/ppskFetchAll.ts",
|
||||
"src/api/unifi/site/wifi/ppskCreate.ts",
|
||||
],
|
||||
dependencies: ["unifi.access", "unifi.site.wifi"],
|
||||
},
|
||||
{
|
||||
node: "unifi.site.wifi.ppsk.create",
|
||||
description:
|
||||
"Create a private pre-shared key on a specific WiFi network",
|
||||
usedIn: ["src/api/unifi/site/wifi/ppskCreate.ts"],
|
||||
dependencies: [
|
||||
"unifi.access",
|
||||
"unifi.site.wifi",
|
||||
"unifi.site.wifi.ppsk",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
} as const satisfies Record<string, PermissionCategory>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Test script to probe UniFi API endpoints for response shapes
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
const controllerBaseUrl = "https://unifi.totaltech.net";
|
||||
const site = "km9b1v8i";
|
||||
const username = "admin";
|
||||
const password = "Tt$Un1fiIZth3B3$t26";
|
||||
|
||||
class TestClient {
|
||||
private client: AxiosInstance;
|
||||
|
||||
constructor(baseURL: string) {
|
||||
this.client = axios.create({
|
||||
baseURL,
|
||||
validateStatus: (s) => s >= 200 && s < 400,
|
||||
});
|
||||
}
|
||||
|
||||
private persistSession(res: { headers: Record<string, unknown> }): void {
|
||||
const raw = res.headers["set-cookie"];
|
||||
if (raw) {
|
||||
const cookies = (Array.isArray(raw) ? raw : [raw]) as string[];
|
||||
const cookieString = cookies.map((c) => c.split(";")[0]).join("; ");
|
||||
this.client.defaults.headers.common["Cookie"] = cookieString;
|
||||
}
|
||||
const csrf = res.headers["x-csrf-token"];
|
||||
if (typeof csrf === "string") {
|
||||
this.client.defaults.headers.common["X-CSRF-Token"] = csrf;
|
||||
}
|
||||
}
|
||||
|
||||
async login(): Promise<void> {
|
||||
try {
|
||||
const res = await this.client.post("/api/auth/login", {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
console.log("Login OK (UniFi OS)", res.status);
|
||||
this.persistSession(res);
|
||||
} catch (e) {
|
||||
const res = await this.client.post("/api/login", { username, password });
|
||||
console.log("Login OK (legacy)", res.status);
|
||||
this.persistSession(res);
|
||||
}
|
||||
}
|
||||
|
||||
async tryGet(label: string, paths: string[]): Promise<any> {
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const data = res.data?.data ?? res.data;
|
||||
console.log(`\n=== ${label} (${path}) ===`);
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
return data;
|
||||
} catch (e: any) {
|
||||
console.log(` Failed ${path}: ${e.response?.status ?? e.message}`);
|
||||
}
|
||||
}
|
||||
console.log(` Could not fetch ${label} from any path`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const client = new TestClient(controllerBaseUrl);
|
||||
await client.login();
|
||||
|
||||
// 1. WLAN Groups (AP groups in UniFi)
|
||||
await client.tryGet("WLAN Groups", [
|
||||
`/proxy/network/api/s/${site}/rest/wlangroup`,
|
||||
`/api/s/${site}/rest/wlangroup`,
|
||||
]);
|
||||
|
||||
// 2. User Groups (bandwidth/speed limit profiles)
|
||||
await client.tryGet("User Groups (Speed Profiles)", [
|
||||
`/proxy/network/api/s/${site}/rest/usergroup`,
|
||||
`/api/s/${site}/rest/usergroup`,
|
||||
]);
|
||||
|
||||
// 3. Devices - APs only (compact)
|
||||
const devices = await client.tryGet("Devices", [
|
||||
`/proxy/network/api/s/${site}/stat/device`,
|
||||
]);
|
||||
if (devices) {
|
||||
const aps = devices.filter((d: any) => d.type === "uap");
|
||||
console.log(`\n=== APs (${aps.length}) - compact ===`);
|
||||
aps.forEach((ap: any) => {
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
_id: ap._id,
|
||||
name: ap.name,
|
||||
mac: ap.mac,
|
||||
model: ap.model,
|
||||
radio_table: ap.radio_table?.map((r: any) => ({
|
||||
radio: r.radio,
|
||||
name: r.name,
|
||||
})),
|
||||
wlangroup_id_ng: ap.wlangroup_id_ng,
|
||||
wlangroup_id_na: ap.wlangroup_id_na,
|
||||
vap_table_count: ap.vap_table?.length,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 4. One full WLAN to see private_preshared_keys structure
|
||||
const wlans = await client.tryGet("WLANs", [
|
||||
`/proxy/network/api/s/${site}/rest/wlanconf`,
|
||||
]);
|
||||
if (wlans) {
|
||||
// Log just the PPSK-related fields from each WLAN
|
||||
console.log("\n=== PPSK fields per WLAN ===");
|
||||
wlans.forEach((w: any) => {
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
name: w.name,
|
||||
_id: w._id,
|
||||
private_preshared_keys_enabled: w.private_preshared_keys_enabled,
|
||||
private_preshared_keys: w.private_preshared_keys,
|
||||
ap_group_ids: w.ap_group_ids,
|
||||
ap_group_mode: w.ap_group_mode,
|
||||
wlan_band: w.wlan_band,
|
||||
wlan_bands: w.wlan_bands,
|
||||
usergroup_id: w.usergroup_id,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => console.error(e));
|
||||
@@ -0,0 +1,123 @@
|
||||
// unifi-wifi-list.ts
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
const controllerBaseUrl = "https://unifi.totaltech.net";
|
||||
const site = "km9b1v8i";
|
||||
const username = "admin";
|
||||
const password = "Tt$Un1fiIZth3B3$t26";
|
||||
|
||||
interface WlanConfRaw {
|
||||
_id: string;
|
||||
name?: string;
|
||||
ssid?: string;
|
||||
x_passphrase?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface WlanConf {
|
||||
id: string;
|
||||
ssid: string;
|
||||
password: string | null;
|
||||
}
|
||||
|
||||
class UnifiClient {
|
||||
private client: AxiosInstance;
|
||||
|
||||
constructor(baseURL: string) {
|
||||
this.client = axios.create({
|
||||
baseURL,
|
||||
validateStatus: (s) => s >= 200 && s < 400,
|
||||
});
|
||||
}
|
||||
|
||||
private persistSession(res: { headers: Record<string, unknown> }): void {
|
||||
// Cookies
|
||||
const raw = res.headers["set-cookie"];
|
||||
if (raw) {
|
||||
const cookies = (Array.isArray(raw) ? raw : [raw]) as string[];
|
||||
const cookieString = cookies.map((c) => c.split(";")[0]).join("; ");
|
||||
this.client.defaults.headers.common["Cookie"] = cookieString;
|
||||
}
|
||||
// CSRF token (UniFi OS)
|
||||
const csrf = res.headers["x-csrf-token"];
|
||||
if (typeof csrf === "string") {
|
||||
this.client.defaults.headers.common["X-CSRF-Token"] = csrf;
|
||||
}
|
||||
}
|
||||
|
||||
async login(username: string, password: string): Promise<void> {
|
||||
const body = { username, password };
|
||||
|
||||
try {
|
||||
// UniFi OS
|
||||
const res = await this.client.post("/api/auth/login", body);
|
||||
console.log("Login OK (UniFi OS)", res.status);
|
||||
this.persistSession(res);
|
||||
} catch (e) {
|
||||
// Legacy controller
|
||||
console.log("UniFi OS login failed, trying legacy...");
|
||||
const res = await this.client.post("/api/login", body);
|
||||
console.log("Login OK (legacy)", res.status);
|
||||
this.persistSession(res);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchWlanConfRaw(site: string): Promise<WlanConfRaw[]> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/wlanconf`,
|
||||
`/api/s/${site}/rest/wlanconf`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const data = (res.data?.data ?? res.data) as WlanConfRaw[];
|
||||
console.log(`Fetched wlan from ${path}`);
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.log(
|
||||
`Failed ${path}:`,
|
||||
axios.isAxiosError(e) ? e.response?.status : e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch WLAN config from any known path");
|
||||
}
|
||||
|
||||
async getWlanConf(site: string): Promise<WlanConf[]> {
|
||||
const raw = await this.fetchWlanConfRaw(site);
|
||||
|
||||
return raw.map(
|
||||
(w): WlanConf => ({
|
||||
id: w._id,
|
||||
ssid: (w.name || w.ssid || "").toString(),
|
||||
password: typeof w.x_passphrase === "string" ? w.x_passphrase : null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const unifi = new UnifiClient(controllerBaseUrl);
|
||||
|
||||
try {
|
||||
await unifi.login(username, password);
|
||||
|
||||
const wlans = await unifi.getWlanConf(site);
|
||||
|
||||
wlans.forEach((wlan) => {
|
||||
console.log(`${wlan.ssid}: ${wlan.password ?? "<no password>"}`);
|
||||
});
|
||||
} catch (err) {
|
||||
if (axios.isAxiosError(err)) {
|
||||
console.error("HTTP error", err.response?.status, err.response?.data);
|
||||
} else {
|
||||
console.error("Error", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => console.error(e));
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
// unifi-wifi-list.ts
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
const controllerBaseUrl = "https://unifi.totaltech.net";
|
||||
const site = "km9b1v8i";
|
||||
const username = "admin";
|
||||
const password = "Tt$Un1fiIZth3B3$t26";
|
||||
|
||||
interface WlanConfRaw {
|
||||
_id: string;
|
||||
name?: string;
|
||||
ssid?: string;
|
||||
x_passphrase?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface WlanConf {
|
||||
id: string;
|
||||
ssid: string;
|
||||
password: string | null;
|
||||
}
|
||||
|
||||
class UnifiClient {
|
||||
private client: AxiosInstance;
|
||||
|
||||
constructor(baseURL: string) {
|
||||
this.client = axios.create({
|
||||
baseURL,
|
||||
validateStatus: (s) => s >= 200 && s < 400,
|
||||
});
|
||||
}
|
||||
|
||||
private persistSession(res: { headers: Record<string, unknown> }): void {
|
||||
// Cookies
|
||||
const raw = res.headers["set-cookie"];
|
||||
if (raw) {
|
||||
const cookies = (Array.isArray(raw) ? raw : [raw]) as string[];
|
||||
const cookieString = cookies.map((c) => c.split(";")[0]).join("; ");
|
||||
this.client.defaults.headers.common["Cookie"] = cookieString;
|
||||
}
|
||||
// CSRF token (UniFi OS)
|
||||
const csrf = res.headers["x-csrf-token"];
|
||||
if (typeof csrf === "string") {
|
||||
this.client.defaults.headers.common["X-CSRF-Token"] = csrf;
|
||||
}
|
||||
}
|
||||
|
||||
async login(username: string, password: string): Promise<void> {
|
||||
const body = { username, password };
|
||||
|
||||
try {
|
||||
// UniFi OS
|
||||
const res = await this.client.post("/api/auth/login", body);
|
||||
console.log("Login OK (UniFi OS)", res.status);
|
||||
this.persistSession(res);
|
||||
} catch (e) {
|
||||
// Legacy controller
|
||||
console.log("UniFi OS login failed, trying legacy...");
|
||||
const res = await this.client.post("/api/login", body);
|
||||
console.log("Login OK (legacy)", res.status);
|
||||
this.persistSession(res);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchWlanConfRaw(site: string): Promise<WlanConfRaw[]> {
|
||||
const paths = [
|
||||
`/proxy/network/api/s/${site}/rest/wlanconf`,
|
||||
`/api/s/${site}/rest/wlanconf`,
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await this.client.get(path);
|
||||
const data = (res.data?.data ?? res.data) as WlanConfRaw[];
|
||||
console.log(`Fetched wlan from ${path}`);
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.log(
|
||||
`Failed ${path}:`,
|
||||
axios.isAxiosError(e) ? e.response?.status : e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not fetch WLAN config from any known path");
|
||||
}
|
||||
|
||||
async getWlanConf(site: string): Promise<WlanConf[]> {
|
||||
const raw = await this.fetchWlanConfRaw(site);
|
||||
|
||||
return raw.map(
|
||||
(w): WlanConf => ({
|
||||
id: w._id,
|
||||
ssid: (w.name || w.ssid || "").toString(),
|
||||
password: typeof w.x_passphrase === "string" ? w.x_passphrase : null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const unifi = new UnifiClient(controllerBaseUrl);
|
||||
|
||||
try {
|
||||
await unifi.login(username, password);
|
||||
|
||||
const wlans = await unifi.getWlanConf(site);
|
||||
|
||||
wlans.forEach((wlan) => {
|
||||
console.log(`${wlan.ssid}: ${wlan.password ?? "<no password>"}`);
|
||||
});
|
||||
} catch (err) {
|
||||
if (axios.isAxiosError(err)) {
|
||||
console.error("HTTP error", err.response?.status, err.response?.data);
|
||||
} else {
|
||||
console.error("Error", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => console.error(e));
|
||||
Reference in New Issue
Block a user