setup unifi wlans

This commit is contained in:
2026-02-22 19:12:34 -06:00
parent 70284bc14e
commit 3c89f24189
66 changed files with 7393 additions and 110 deletions
+10
View File
@@ -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. 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. 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. 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
View File
File diff suppressed because it is too large Load Diff
+51 -10
View File
@@ -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` | 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.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.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) | | `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 ### Credential Permissions
| Permission Node | Description | Used In | | Permission Node | Description | Used In |
| ------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ----------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `credential.create` | Create a new credential | [src/api/credentials/create.ts](src/api/credentials/create.ts) | | `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` | 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.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.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.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.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.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.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 ### 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. - **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. - **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 ## Permission Issuers
Permissions can be issued by different sources: Permissions can be issued by different sources:
+5
View File
@@ -32,6 +32,11 @@ export type User = Prisma.UserModel
* *
*/ */
export type Role = Prisma.RoleModel export type Role = Prisma.RoleModel
/**
* Model UnifiSite
*
*/
export type UnifiSite = Prisma.UnifiSiteModel
/** /**
* Model Company * Model Company
* *
+5
View File
@@ -54,6 +54,11 @@ export type User = Prisma.UserModel
* *
*/ */
export type Role = Prisma.RoleModel export type Role = Prisma.RoleModel
/**
* Model UnifiSite
*
*/
export type UnifiSite = Prisma.UnifiSiteModel
/** /**
* Model Company * Model Company
* *
File diff suppressed because one or more lines are too long
+90 -1
View File
@@ -387,6 +387,7 @@ export const ModelName = {
Session: 'Session', Session: 'Session',
User: 'User', User: 'User',
Role: 'Role', Role: 'Role',
UnifiSite: 'UnifiSite',
Company: 'Company', Company: 'Company',
CredentialType: 'CredentialType', CredentialType: 'CredentialType',
SecureValue: 'SecureValue', SecureValue: 'SecureValue',
@@ -406,7 +407,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
omit: GlobalOmitOptions omit: GlobalOmitOptions
} }
meta: { meta: {
modelProps: "session" | "user" | "role" | "company" | "credentialType" | "secureValue" | "credential" modelProps: "session" | "user" | "role" | "unifiSite" | "company" | "credentialType" | "secureValue" | "credential"
txIsolationLevel: TransactionIsolationLevel txIsolationLevel: TransactionIsolationLevel
} }
model: { 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: { Company: {
payload: Prisma.$CompanyPayload<ExtArgs> payload: Prisma.$CompanyPayload<ExtArgs>
fields: Prisma.CompanyFieldRefs fields: Prisma.CompanyFieldRefs
@@ -1009,6 +1084,18 @@ export const RoleScalarFieldEnum = {
export type RoleScalarFieldEnum = (typeof RoleScalarFieldEnum)[keyof typeof 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 = { export const CompanyScalarFieldEnum = {
id: 'id', id: 'id',
name: 'name', name: 'name',
@@ -1051,6 +1138,7 @@ export const CredentialScalarFieldEnum = {
id: 'id', id: 'id',
name: 'name', name: 'name',
notes: 'notes', notes: 'notes',
subCredentialOfId: 'subCredentialOfId',
typeId: 'typeId', typeId: 'typeId',
fields: 'fields', fields: 'fields',
companyId: 'companyId', companyId: 'companyId',
@@ -1281,6 +1369,7 @@ export type GlobalOmitConfig = {
session?: Prisma.SessionOmit session?: Prisma.SessionOmit
user?: Prisma.UserOmit user?: Prisma.UserOmit
role?: Prisma.RoleOmit role?: Prisma.RoleOmit
unifiSite?: Prisma.UnifiSiteOmit
company?: Prisma.CompanyOmit company?: Prisma.CompanyOmit
credentialType?: Prisma.CredentialTypeOmit credentialType?: Prisma.CredentialTypeOmit
secureValue?: Prisma.SecureValueOmit secureValue?: Prisma.SecureValueOmit
@@ -54,6 +54,7 @@ export const ModelName = {
Session: 'Session', Session: 'Session',
User: 'User', User: 'User',
Role: 'Role', Role: 'Role',
UnifiSite: 'UnifiSite',
Company: 'Company', Company: 'Company',
CredentialType: 'CredentialType', CredentialType: 'CredentialType',
SecureValue: 'SecureValue', SecureValue: 'SecureValue',
@@ -118,6 +119,18 @@ export const RoleScalarFieldEnum = {
export type RoleScalarFieldEnum = (typeof RoleScalarFieldEnum)[keyof typeof 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 = { export const CompanyScalarFieldEnum = {
id: 'id', id: 'id',
name: 'name', name: 'name',
@@ -160,6 +173,7 @@ export const CredentialScalarFieldEnum = {
id: 'id', id: 'id',
name: 'name', name: 'name',
notes: 'notes', notes: 'notes',
subCredentialOfId: 'subCredentialOfId',
typeId: 'typeId', typeId: 'typeId',
fields: 'fields', fields: 'fields',
companyId: 'companyId', companyId: 'companyId',
+1
View File
@@ -11,6 +11,7 @@
export type * from './models/Session.ts' export type * from './models/Session.ts'
export type * from './models/User.ts' export type * from './models/User.ts'
export type * from './models/Role.ts' export type * from './models/Role.ts'
export type * from './models/UnifiSite.ts'
export type * from './models/Company.ts' export type * from './models/Company.ts'
export type * from './models/CredentialType.ts' export type * from './models/CredentialType.ts'
export type * from './models/SecureValue.ts' export type * from './models/SecureValue.ts'
+125
View File
@@ -225,6 +225,7 @@ export type CompanyWhereInput = {
createdAt?: Prisma.DateTimeFilter<"Company"> | Date | string createdAt?: Prisma.DateTimeFilter<"Company"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string
credentials?: Prisma.CredentialListRelationFilter credentials?: Prisma.CredentialListRelationFilter
unifiSites?: Prisma.UnifiSiteListRelationFilter
} }
export type CompanyOrderByWithRelationInput = { export type CompanyOrderByWithRelationInput = {
@@ -235,6 +236,7 @@ export type CompanyOrderByWithRelationInput = {
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
credentials?: Prisma.CredentialOrderByRelationAggregateInput credentials?: Prisma.CredentialOrderByRelationAggregateInput
unifiSites?: Prisma.UnifiSiteOrderByRelationAggregateInput
} }
export type CompanyWhereUniqueInput = Prisma.AtLeast<{ export type CompanyWhereUniqueInput = Prisma.AtLeast<{
@@ -248,6 +250,7 @@ export type CompanyWhereUniqueInput = Prisma.AtLeast<{
createdAt?: Prisma.DateTimeFilter<"Company"> | Date | string createdAt?: Prisma.DateTimeFilter<"Company"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string
credentials?: Prisma.CredentialListRelationFilter credentials?: Prisma.CredentialListRelationFilter
unifiSites?: Prisma.UnifiSiteListRelationFilter
}, "id" | "cw_CompanyId" | "cw_Identifier"> }, "id" | "cw_CompanyId" | "cw_Identifier">
export type CompanyOrderByWithAggregationInput = { export type CompanyOrderByWithAggregationInput = {
@@ -284,6 +287,7 @@ export type CompanyCreateInput = {
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
credentials?: Prisma.CredentialCreateNestedManyWithoutCompanyInput credentials?: Prisma.CredentialCreateNestedManyWithoutCompanyInput
unifiSites?: Prisma.UnifiSiteCreateNestedManyWithoutCompanyInput
} }
export type CompanyUncheckedCreateInput = { export type CompanyUncheckedCreateInput = {
@@ -294,6 +298,7 @@ export type CompanyUncheckedCreateInput = {
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
credentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutCompanyInput credentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutCompanyInput
unifiSites?: Prisma.UnifiSiteUncheckedCreateNestedManyWithoutCompanyInput
} }
export type CompanyUpdateInput = { export type CompanyUpdateInput = {
@@ -304,6 +309,7 @@ export type CompanyUpdateInput = {
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
credentials?: Prisma.CredentialUpdateManyWithoutCompanyNestedInput credentials?: Prisma.CredentialUpdateManyWithoutCompanyNestedInput
unifiSites?: Prisma.UnifiSiteUpdateManyWithoutCompanyNestedInput
} }
export type CompanyUncheckedUpdateInput = { export type CompanyUncheckedUpdateInput = {
@@ -314,6 +320,7 @@ export type CompanyUncheckedUpdateInput = {
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
credentials?: Prisma.CredentialUncheckedUpdateManyWithoutCompanyNestedInput credentials?: Prisma.CredentialUncheckedUpdateManyWithoutCompanyNestedInput
unifiSites?: Prisma.UnifiSiteUncheckedUpdateManyWithoutCompanyNestedInput
} }
export type CompanyCreateManyInput = { export type CompanyCreateManyInput = {
@@ -343,6 +350,11 @@ export type CompanyUncheckedUpdateManyInput = {
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
export type CompanyNullableScalarRelationFilter = {
is?: Prisma.CompanyWhereInput | null
isNot?: Prisma.CompanyWhereInput | null
}
export type CompanyCountOrderByAggregateInput = { export type CompanyCountOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
name?: Prisma.SortOrder name?: Prisma.SortOrder
@@ -383,6 +395,22 @@ export type CompanyScalarRelationFilter = {
isNot?: Prisma.CompanyWhereInput 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 = { export type IntFieldUpdateOperationsInput = {
set?: number set?: number
increment?: number increment?: number
@@ -405,6 +433,62 @@ export type CompanyUpdateOneRequiredWithoutCredentialsNestedInput = {
update?: Prisma.XOR<Prisma.XOR<Prisma.CompanyUpdateToOneWithWhereWithoutCredentialsInput, Prisma.CompanyUpdateWithoutCredentialsInput>, Prisma.CompanyUncheckedUpdateWithoutCredentialsInput> 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 = { export type CompanyCreateWithoutCredentialsInput = {
id?: string id?: string
name: string name: string
@@ -412,6 +496,7 @@ export type CompanyCreateWithoutCredentialsInput = {
cw_Identifier: string cw_Identifier: string
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
unifiSites?: Prisma.UnifiSiteCreateNestedManyWithoutCompanyInput
} }
export type CompanyUncheckedCreateWithoutCredentialsInput = { export type CompanyUncheckedCreateWithoutCredentialsInput = {
@@ -421,6 +506,7 @@ export type CompanyUncheckedCreateWithoutCredentialsInput = {
cw_Identifier: string cw_Identifier: string
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
unifiSites?: Prisma.UnifiSiteUncheckedCreateNestedManyWithoutCompanyInput
} }
export type CompanyCreateOrConnectWithoutCredentialsInput = { export type CompanyCreateOrConnectWithoutCredentialsInput = {
@@ -446,6 +532,7 @@ export type CompanyUpdateWithoutCredentialsInput = {
cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
unifiSites?: Prisma.UnifiSiteUpdateManyWithoutCompanyNestedInput
} }
export type CompanyUncheckedUpdateWithoutCredentialsInput = { export type CompanyUncheckedUpdateWithoutCredentialsInput = {
@@ -455,6 +542,7 @@ export type CompanyUncheckedUpdateWithoutCredentialsInput = {
cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
unifiSites?: Prisma.UnifiSiteUncheckedUpdateManyWithoutCompanyNestedInput
} }
@@ -464,10 +552,12 @@ export type CompanyUncheckedUpdateWithoutCredentialsInput = {
export type CompanyCountOutputType = { export type CompanyCountOutputType = {
credentials: number credentials: number
unifiSites: number
} }
export type CompanyCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type CompanyCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
credentials?: boolean | CompanyCountOutputTypeCountCredentialsArgs credentials?: boolean | CompanyCountOutputTypeCountCredentialsArgs
unifiSites?: boolean | CompanyCountOutputTypeCountUnifiSitesArgs
} }
/** /**
@@ -487,6 +577,13 @@ export type CompanyCountOutputTypeCountCredentialsArgs<ExtArgs extends runtime.T
where?: Prisma.CredentialWhereInput 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<{ export type CompanySelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
id?: boolean id?: boolean
@@ -496,6 +593,7 @@ export type CompanySelect<ExtArgs extends runtime.Types.Extensions.InternalArgs
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs> credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs>
unifiSites?: boolean | Prisma.Company$unifiSitesArgs<ExtArgs>
_count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs> _count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs>
}, ExtArgs["result"]["company"]> }, 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 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> = { export type CompanyInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs> credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs>
unifiSites?: boolean | Prisma.Company$unifiSitesArgs<ExtArgs>
_count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs> _count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs>
} }
export type CompanyIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {} 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" name: "Company"
objects: { objects: {
credentials: Prisma.$CredentialPayload<ExtArgs>[] credentials: Prisma.$CredentialPayload<ExtArgs>[]
unifiSites: Prisma.$UnifiSitePayload<ExtArgs>[]
} }
scalars: runtime.Types.Extensions.GetPayloadResult<{ scalars: runtime.Types.Extensions.GetPayloadResult<{
id: string 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> { 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" 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> 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. * Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved. * @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[] 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 * Company without action
*/ */
+362 -1
View File
@@ -28,6 +28,7 @@ export type CredentialMinAggregateOutputType = {
id: string | null id: string | null
name: string | null name: string | null
notes: string | null notes: string | null
subCredentialOfId: string | null
typeId: string | null typeId: string | null
companyId: string | null companyId: string | null
createdAt: Date | null createdAt: Date | null
@@ -38,6 +39,7 @@ export type CredentialMaxAggregateOutputType = {
id: string | null id: string | null
name: string | null name: string | null
notes: string | null notes: string | null
subCredentialOfId: string | null
typeId: string | null typeId: string | null
companyId: string | null companyId: string | null
createdAt: Date | null createdAt: Date | null
@@ -48,6 +50,7 @@ export type CredentialCountAggregateOutputType = {
id: number id: number
name: number name: number
notes: number notes: number
subCredentialOfId: number
typeId: number typeId: number
fields: number fields: number
companyId: number companyId: number
@@ -61,6 +64,7 @@ export type CredentialMinAggregateInputType = {
id?: true id?: true
name?: true name?: true
notes?: true notes?: true
subCredentialOfId?: true
typeId?: true typeId?: true
companyId?: true companyId?: true
createdAt?: true createdAt?: true
@@ -71,6 +75,7 @@ export type CredentialMaxAggregateInputType = {
id?: true id?: true
name?: true name?: true
notes?: true notes?: true
subCredentialOfId?: true
typeId?: true typeId?: true
companyId?: true companyId?: true
createdAt?: true createdAt?: true
@@ -81,6 +86,7 @@ export type CredentialCountAggregateInputType = {
id?: true id?: true
name?: true name?: true
notes?: true notes?: true
subCredentialOfId?: true
typeId?: true typeId?: true
fields?: true fields?: true
companyId?: true companyId?: true
@@ -165,6 +171,7 @@ export type CredentialGroupByOutputType = {
id: string id: string
name: string name: string
notes: string | null notes: string | null
subCredentialOfId: string | null
typeId: string typeId: string
fields: runtime.JsonValue fields: runtime.JsonValue
companyId: string companyId: string
@@ -197,11 +204,14 @@ export type CredentialWhereInput = {
id?: Prisma.StringFilter<"Credential"> | string id?: Prisma.StringFilter<"Credential"> | string
name?: Prisma.StringFilter<"Credential"> | string name?: Prisma.StringFilter<"Credential"> | string
notes?: Prisma.StringNullableFilter<"Credential"> | string | null notes?: Prisma.StringNullableFilter<"Credential"> | string | null
subCredentialOfId?: Prisma.StringNullableFilter<"Credential"> | string | null
typeId?: Prisma.StringFilter<"Credential"> | string typeId?: Prisma.StringFilter<"Credential"> | string
fields?: Prisma.JsonFilter<"Credential"> fields?: Prisma.JsonFilter<"Credential">
companyId?: Prisma.StringFilter<"Credential"> | string companyId?: Prisma.StringFilter<"Credential"> | string
createdAt?: Prisma.DateTimeFilter<"Credential"> | Date | string createdAt?: Prisma.DateTimeFilter<"Credential"> | Date | string
updatedAt?: 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> type?: Prisma.XOR<Prisma.CredentialTypeScalarRelationFilter, Prisma.CredentialTypeWhereInput>
company?: Prisma.XOR<Prisma.CompanyScalarRelationFilter, Prisma.CompanyWhereInput> company?: Prisma.XOR<Prisma.CompanyScalarRelationFilter, Prisma.CompanyWhereInput>
securevalues?: Prisma.SecureValueListRelationFilter securevalues?: Prisma.SecureValueListRelationFilter
@@ -211,11 +221,14 @@ export type CredentialOrderByWithRelationInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
name?: Prisma.SortOrder name?: Prisma.SortOrder
notes?: Prisma.SortOrderInput | Prisma.SortOrder notes?: Prisma.SortOrderInput | Prisma.SortOrder
subCredentialOfId?: Prisma.SortOrderInput | Prisma.SortOrder
typeId?: Prisma.SortOrder typeId?: Prisma.SortOrder
fields?: Prisma.SortOrder fields?: Prisma.SortOrder
companyId?: Prisma.SortOrder companyId?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
subCredentialOf?: Prisma.CredentialOrderByWithRelationInput
subCredentials?: Prisma.CredentialOrderByRelationAggregateInput
type?: Prisma.CredentialTypeOrderByWithRelationInput type?: Prisma.CredentialTypeOrderByWithRelationInput
company?: Prisma.CompanyOrderByWithRelationInput company?: Prisma.CompanyOrderByWithRelationInput
securevalues?: Prisma.SecureValueOrderByRelationAggregateInput securevalues?: Prisma.SecureValueOrderByRelationAggregateInput
@@ -228,11 +241,14 @@ export type CredentialWhereUniqueInput = Prisma.AtLeast<{
NOT?: Prisma.CredentialWhereInput | Prisma.CredentialWhereInput[] NOT?: Prisma.CredentialWhereInput | Prisma.CredentialWhereInput[]
name?: Prisma.StringFilter<"Credential"> | string name?: Prisma.StringFilter<"Credential"> | string
notes?: Prisma.StringNullableFilter<"Credential"> | string | null notes?: Prisma.StringNullableFilter<"Credential"> | string | null
subCredentialOfId?: Prisma.StringNullableFilter<"Credential"> | string | null
typeId?: Prisma.StringFilter<"Credential"> | string typeId?: Prisma.StringFilter<"Credential"> | string
fields?: Prisma.JsonFilter<"Credential"> fields?: Prisma.JsonFilter<"Credential">
companyId?: Prisma.StringFilter<"Credential"> | string companyId?: Prisma.StringFilter<"Credential"> | string
createdAt?: Prisma.DateTimeFilter<"Credential"> | Date | string createdAt?: Prisma.DateTimeFilter<"Credential"> | Date | string
updatedAt?: 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> type?: Prisma.XOR<Prisma.CredentialTypeScalarRelationFilter, Prisma.CredentialTypeWhereInput>
company?: Prisma.XOR<Prisma.CompanyScalarRelationFilter, Prisma.CompanyWhereInput> company?: Prisma.XOR<Prisma.CompanyScalarRelationFilter, Prisma.CompanyWhereInput>
securevalues?: Prisma.SecureValueListRelationFilter securevalues?: Prisma.SecureValueListRelationFilter
@@ -242,6 +258,7 @@ export type CredentialOrderByWithAggregationInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
name?: Prisma.SortOrder name?: Prisma.SortOrder
notes?: Prisma.SortOrderInput | Prisma.SortOrder notes?: Prisma.SortOrderInput | Prisma.SortOrder
subCredentialOfId?: Prisma.SortOrderInput | Prisma.SortOrder
typeId?: Prisma.SortOrder typeId?: Prisma.SortOrder
fields?: Prisma.SortOrder fields?: Prisma.SortOrder
companyId?: Prisma.SortOrder companyId?: Prisma.SortOrder
@@ -259,6 +276,7 @@ export type CredentialScalarWhereWithAggregatesInput = {
id?: Prisma.StringWithAggregatesFilter<"Credential"> | string id?: Prisma.StringWithAggregatesFilter<"Credential"> | string
name?: Prisma.StringWithAggregatesFilter<"Credential"> | string name?: Prisma.StringWithAggregatesFilter<"Credential"> | string
notes?: Prisma.StringNullableWithAggregatesFilter<"Credential"> | string | null notes?: Prisma.StringNullableWithAggregatesFilter<"Credential"> | string | null
subCredentialOfId?: Prisma.StringNullableWithAggregatesFilter<"Credential"> | string | null
typeId?: Prisma.StringWithAggregatesFilter<"Credential"> | string typeId?: Prisma.StringWithAggregatesFilter<"Credential"> | string
fields?: Prisma.JsonWithAggregatesFilter<"Credential"> fields?: Prisma.JsonWithAggregatesFilter<"Credential">
companyId?: Prisma.StringWithAggregatesFilter<"Credential"> | string companyId?: Prisma.StringWithAggregatesFilter<"Credential"> | string
@@ -273,6 +291,8 @@ export type CredentialCreateInput = {
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
subCredentialOf?: Prisma.CredentialCreateNestedOneWithoutSubCredentialsInput
subCredentials?: Prisma.CredentialCreateNestedManyWithoutSubCredentialOfInput
type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput
company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput
securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput
@@ -282,11 +302,13 @@ export type CredentialUncheckedCreateInput = {
id?: string id?: string
name: string name: string
notes?: string | null notes?: string | null
subCredentialOfId?: string | null
typeId: string typeId: string
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId: string companyId: string
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
subCredentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput
securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput
} }
@@ -297,6 +319,8 @@ export type CredentialUpdateInput = {
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
subCredentialOf?: Prisma.CredentialUpdateOneWithoutSubCredentialsNestedInput
subCredentials?: Prisma.CredentialUpdateManyWithoutSubCredentialOfNestedInput
type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput
company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput
securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput
@@ -306,11 +330,13 @@ export type CredentialUncheckedUpdateInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
typeId?: Prisma.StringFieldUpdateOperationsInput | string typeId?: Prisma.StringFieldUpdateOperationsInput | string
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId?: Prisma.StringFieldUpdateOperationsInput | string companyId?: Prisma.StringFieldUpdateOperationsInput | string
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
subCredentials?: Prisma.CredentialUncheckedUpdateManyWithoutSubCredentialOfNestedInput
securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput
} }
@@ -318,6 +344,7 @@ export type CredentialCreateManyInput = {
id?: string id?: string
name: string name: string
notes?: string | null notes?: string | null
subCredentialOfId?: string | null
typeId: string typeId: string
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId: string companyId: string
@@ -338,6 +365,7 @@ export type CredentialUncheckedUpdateManyInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
typeId?: Prisma.StringFieldUpdateOperationsInput | string typeId?: Prisma.StringFieldUpdateOperationsInput | string
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId?: Prisma.StringFieldUpdateOperationsInput | string companyId?: Prisma.StringFieldUpdateOperationsInput | string
@@ -360,10 +388,16 @@ export type CredentialScalarRelationFilter = {
isNot?: Prisma.CredentialWhereInput isNot?: Prisma.CredentialWhereInput
} }
export type CredentialNullableScalarRelationFilter = {
is?: Prisma.CredentialWhereInput | null
isNot?: Prisma.CredentialWhereInput | null
}
export type CredentialCountOrderByAggregateInput = { export type CredentialCountOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
name?: Prisma.SortOrder name?: Prisma.SortOrder
notes?: Prisma.SortOrder notes?: Prisma.SortOrder
subCredentialOfId?: Prisma.SortOrder
typeId?: Prisma.SortOrder typeId?: Prisma.SortOrder
fields?: Prisma.SortOrder fields?: Prisma.SortOrder
companyId?: Prisma.SortOrder companyId?: Prisma.SortOrder
@@ -375,6 +409,7 @@ export type CredentialMaxOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
name?: Prisma.SortOrder name?: Prisma.SortOrder
notes?: Prisma.SortOrder notes?: Prisma.SortOrder
subCredentialOfId?: Prisma.SortOrder
typeId?: Prisma.SortOrder typeId?: Prisma.SortOrder
companyId?: Prisma.SortOrder companyId?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
@@ -385,6 +420,7 @@ export type CredentialMinOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
name?: Prisma.SortOrder name?: Prisma.SortOrder
notes?: Prisma.SortOrder notes?: Prisma.SortOrder
subCredentialOfId?: Prisma.SortOrder
typeId?: Prisma.SortOrder typeId?: Prisma.SortOrder
companyId?: Prisma.SortOrder companyId?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
@@ -489,6 +525,64 @@ export type CredentialUpdateOneRequiredWithoutSecurevaluesNestedInput = {
update?: Prisma.XOR<Prisma.XOR<Prisma.CredentialUpdateToOneWithWhereWithoutSecurevaluesInput, Prisma.CredentialUpdateWithoutSecurevaluesInput>, Prisma.CredentialUncheckedUpdateWithoutSecurevaluesInput> 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 = { export type CredentialCreateWithoutCompanyInput = {
id?: string id?: string
name: string name: string
@@ -496,6 +590,8 @@ export type CredentialCreateWithoutCompanyInput = {
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
subCredentialOf?: Prisma.CredentialCreateNestedOneWithoutSubCredentialsInput
subCredentials?: Prisma.CredentialCreateNestedManyWithoutSubCredentialOfInput
type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput
securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput
} }
@@ -504,10 +600,12 @@ export type CredentialUncheckedCreateWithoutCompanyInput = {
id?: string id?: string
name: string name: string
notes?: string | null notes?: string | null
subCredentialOfId?: string | null
typeId: string typeId: string
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
subCredentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput
securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput
} }
@@ -544,6 +642,7 @@ export type CredentialScalarWhereInput = {
id?: Prisma.StringFilter<"Credential"> | string id?: Prisma.StringFilter<"Credential"> | string
name?: Prisma.StringFilter<"Credential"> | string name?: Prisma.StringFilter<"Credential"> | string
notes?: Prisma.StringNullableFilter<"Credential"> | string | null notes?: Prisma.StringNullableFilter<"Credential"> | string | null
subCredentialOfId?: Prisma.StringNullableFilter<"Credential"> | string | null
typeId?: Prisma.StringFilter<"Credential"> | string typeId?: Prisma.StringFilter<"Credential"> | string
fields?: Prisma.JsonFilter<"Credential"> fields?: Prisma.JsonFilter<"Credential">
companyId?: Prisma.StringFilter<"Credential"> | string companyId?: Prisma.StringFilter<"Credential"> | string
@@ -558,6 +657,8 @@ export type CredentialCreateWithoutTypeInput = {
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
subCredentialOf?: Prisma.CredentialCreateNestedOneWithoutSubCredentialsInput
subCredentials?: Prisma.CredentialCreateNestedManyWithoutSubCredentialOfInput
company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput
securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput securevalues?: Prisma.SecureValueCreateNestedManyWithoutCredentialInput
} }
@@ -566,10 +667,12 @@ export type CredentialUncheckedCreateWithoutTypeInput = {
id?: string id?: string
name: string name: string
notes?: string | null notes?: string | null
subCredentialOfId?: string | null
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId: string companyId: string
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
subCredentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput
securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput securevalues?: Prisma.SecureValueUncheckedCreateNestedManyWithoutCredentialInput
} }
@@ -606,6 +709,8 @@ export type CredentialCreateWithoutSecurevaluesInput = {
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
subCredentialOf?: Prisma.CredentialCreateNestedOneWithoutSubCredentialsInput
subCredentials?: Prisma.CredentialCreateNestedManyWithoutSubCredentialOfInput
type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput type: Prisma.CredentialTypeCreateNestedOneWithoutCredentialsInput
company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput company: Prisma.CompanyCreateNestedOneWithoutCredentialsInput
} }
@@ -614,11 +719,13 @@ export type CredentialUncheckedCreateWithoutSecurevaluesInput = {
id?: string id?: string
name: string name: string
notes?: string | null notes?: string | null
subCredentialOfId?: string | null
typeId: string typeId: string
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId: string companyId: string
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
subCredentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutSubCredentialOfInput
} }
export type CredentialCreateOrConnectWithoutSecurevaluesInput = { export type CredentialCreateOrConnectWithoutSecurevaluesInput = {
@@ -644,6 +751,8 @@ export type CredentialUpdateWithoutSecurevaluesInput = {
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
subCredentialOf?: Prisma.CredentialUpdateOneWithoutSubCredentialsNestedInput
subCredentials?: Prisma.CredentialUpdateManyWithoutSubCredentialOfNestedInput
type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput
company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput
} }
@@ -652,17 +761,140 @@ export type CredentialUncheckedUpdateWithoutSecurevaluesInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
typeId?: Prisma.StringFieldUpdateOperationsInput | string typeId?: Prisma.StringFieldUpdateOperationsInput | string
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId?: Prisma.StringFieldUpdateOperationsInput | string companyId?: Prisma.StringFieldUpdateOperationsInput | string
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: 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 = { export type CredentialCreateManyCompanyInput = {
id?: string id?: string
name: string name: string
notes?: string | null notes?: string | null
subCredentialOfId?: string | null
typeId: string typeId: string
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Date | string createdAt?: Date | string
@@ -676,6 +908,8 @@ export type CredentialUpdateWithoutCompanyInput = {
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
subCredentialOf?: Prisma.CredentialUpdateOneWithoutSubCredentialsNestedInput
subCredentials?: Prisma.CredentialUpdateManyWithoutSubCredentialOfNestedInput
type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput type?: Prisma.CredentialTypeUpdateOneRequiredWithoutCredentialsNestedInput
securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput
} }
@@ -684,10 +918,12 @@ export type CredentialUncheckedUpdateWithoutCompanyInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
typeId?: Prisma.StringFieldUpdateOperationsInput | string typeId?: Prisma.StringFieldUpdateOperationsInput | string
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
subCredentials?: Prisma.CredentialUncheckedUpdateManyWithoutSubCredentialOfNestedInput
securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput
} }
@@ -695,6 +931,7 @@ export type CredentialUncheckedUpdateManyWithoutCompanyInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
typeId?: Prisma.StringFieldUpdateOperationsInput | string typeId?: Prisma.StringFieldUpdateOperationsInput | string
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -705,6 +942,7 @@ export type CredentialCreateManyTypeInput = {
id?: string id?: string
name: string name: string
notes?: string | null notes?: string | null
subCredentialOfId?: string | null
fields: Prisma.JsonNullValueInput | runtime.InputJsonValue fields: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId: string companyId: string
createdAt?: Date | string createdAt?: Date | string
@@ -718,6 +956,8 @@ export type CredentialUpdateWithoutTypeInput = {
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
subCredentialOf?: Prisma.CredentialUpdateOneWithoutSubCredentialsNestedInput
subCredentials?: Prisma.CredentialUpdateManyWithoutSubCredentialOfNestedInput
company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput company?: Prisma.CompanyUpdateOneRequiredWithoutCredentialsNestedInput
securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput securevalues?: Prisma.SecureValueUpdateManyWithoutCredentialNestedInput
} }
@@ -726,10 +966,12 @@ export type CredentialUncheckedUpdateWithoutTypeInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
subCredentialOfId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId?: Prisma.StringFieldUpdateOperationsInput | string companyId?: Prisma.StringFieldUpdateOperationsInput | string
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
subCredentials?: Prisma.CredentialUncheckedUpdateManyWithoutSubCredentialOfNestedInput
securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput securevalues?: Prisma.SecureValueUncheckedUpdateManyWithoutCredentialNestedInput
} }
@@ -737,6 +979,55 @@ export type CredentialUncheckedUpdateManyWithoutTypeInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string
notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null 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 fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue
companyId?: Prisma.StringFieldUpdateOperationsInput | string companyId?: Prisma.StringFieldUpdateOperationsInput | string
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -749,10 +1040,12 @@ export type CredentialUncheckedUpdateManyWithoutTypeInput = {
*/ */
export type CredentialCountOutputType = { export type CredentialCountOutputType = {
subCredentials: number
securevalues: number securevalues: number
} }
export type CredentialCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type CredentialCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
subCredentials?: boolean | CredentialCountOutputTypeCountSubCredentialsArgs
securevalues?: boolean | CredentialCountOutputTypeCountSecurevaluesArgs securevalues?: boolean | CredentialCountOutputTypeCountSecurevaluesArgs
} }
@@ -766,6 +1059,13 @@ export type CredentialCountOutputTypeDefaultArgs<ExtArgs extends runtime.Types.E
select?: Prisma.CredentialCountOutputTypeSelect<ExtArgs> | null 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 * CredentialCountOutputType without action
*/ */
@@ -778,11 +1078,14 @@ export type CredentialSelect<ExtArgs extends runtime.Types.Extensions.InternalAr
id?: boolean id?: boolean
name?: boolean name?: boolean
notes?: boolean notes?: boolean
subCredentialOfId?: boolean
typeId?: boolean typeId?: boolean
fields?: boolean fields?: boolean
companyId?: boolean companyId?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
subCredentials?: boolean | Prisma.Credential$subCredentialsArgs<ExtArgs>
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs> type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs> company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
securevalues?: boolean | Prisma.Credential$securevaluesArgs<ExtArgs> securevalues?: boolean | Prisma.Credential$securevaluesArgs<ExtArgs>
@@ -793,11 +1096,13 @@ export type CredentialSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Ex
id?: boolean id?: boolean
name?: boolean name?: boolean
notes?: boolean notes?: boolean
subCredentialOfId?: boolean
typeId?: boolean typeId?: boolean
fields?: boolean fields?: boolean
companyId?: boolean companyId?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs> type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs> company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
}, ExtArgs["result"]["credential"]> }, ExtArgs["result"]["credential"]>
@@ -806,11 +1111,13 @@ export type CredentialSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Ex
id?: boolean id?: boolean
name?: boolean name?: boolean
notes?: boolean notes?: boolean
subCredentialOfId?: boolean
typeId?: boolean typeId?: boolean
fields?: boolean fields?: boolean
companyId?: boolean companyId?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs> type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs> company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
}, ExtArgs["result"]["credential"]> }, ExtArgs["result"]["credential"]>
@@ -819,6 +1126,7 @@ export type CredentialSelectScalar = {
id?: boolean id?: boolean
name?: boolean name?: boolean
notes?: boolean notes?: boolean
subCredentialOfId?: boolean
typeId?: boolean typeId?: boolean
fields?: boolean fields?: boolean
companyId?: boolean companyId?: boolean
@@ -826,18 +1134,22 @@ export type CredentialSelectScalar = {
updatedAt?: boolean 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> = { 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> type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs> company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
securevalues?: boolean | Prisma.Credential$securevaluesArgs<ExtArgs> securevalues?: boolean | Prisma.Credential$securevaluesArgs<ExtArgs>
_count?: boolean | Prisma.CredentialCountOutputTypeDefaultArgs<ExtArgs> _count?: boolean | Prisma.CredentialCountOutputTypeDefaultArgs<ExtArgs>
} }
export type CredentialIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type CredentialIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs> type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs> company?: boolean | Prisma.CompanyDefaultArgs<ExtArgs>
} }
export type CredentialIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type CredentialIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
subCredentialOf?: boolean | Prisma.Credential$subCredentialOfArgs<ExtArgs>
type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs> type?: boolean | Prisma.CredentialTypeDefaultArgs<ExtArgs>
company?: boolean | Prisma.CompanyDefaultArgs<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> = { export type $CredentialPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
name: "Credential" name: "Credential"
objects: { objects: {
subCredentialOf: Prisma.$CredentialPayload<ExtArgs> | null
subCredentials: Prisma.$CredentialPayload<ExtArgs>[]
type: Prisma.$CredentialTypePayload<ExtArgs> type: Prisma.$CredentialTypePayload<ExtArgs>
company: Prisma.$CompanyPayload<ExtArgs> company: Prisma.$CompanyPayload<ExtArgs>
securevalues: Prisma.$SecureValuePayload<ExtArgs>[] securevalues: Prisma.$SecureValuePayload<ExtArgs>[]
@@ -853,6 +1167,7 @@ export type $CredentialPayload<ExtArgs extends runtime.Types.Extensions.Internal
id: string id: string
name: string name: string
notes: string | null notes: string | null
subCredentialOfId: string | null
typeId: string typeId: string
fields: runtime.JsonValue fields: runtime.JsonValue
companyId: string 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> { 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" 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> 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> 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> 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 id: Prisma.FieldRef<"Credential", 'String'>
readonly name: Prisma.FieldRef<"Credential", 'String'> readonly name: Prisma.FieldRef<"Credential", 'String'>
readonly notes: Prisma.FieldRef<"Credential", 'String'> readonly notes: Prisma.FieldRef<"Credential", 'String'>
readonly subCredentialOfId: Prisma.FieldRef<"Credential", 'String'>
readonly typeId: Prisma.FieldRef<"Credential", 'String'> readonly typeId: Prisma.FieldRef<"Credential", 'String'>
readonly fields: Prisma.FieldRef<"Credential", 'Json'> readonly fields: Prisma.FieldRef<"Credential", 'Json'>
readonly companyId: Prisma.FieldRef<"Credential", 'String'> readonly companyId: Prisma.FieldRef<"Credential", 'String'>
@@ -1687,6 +2005,49 @@ export type CredentialDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.In
limit?: number 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 * 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
View File
@@ -55,6 +55,19 @@ model Role {
updatedAt DateTime @updatedAt 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 { model Company {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
@@ -63,6 +76,7 @@ model Company {
cw_Identifier String @unique cw_Identifier String @unique
credentials Credential[] credentials Credential[]
unifiSites UnifiSite[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -97,9 +111,12 @@ model SecureValue {
} }
model Credential { model Credential {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
notes String? notes String?
subCredentialOfId String?
subCredentialOf Credential? @relation("SubCredentials", fields: [subCredentialOfId], references: [id], onDelete: Cascade)
subCredentials Credential[] @relation("SubCredentials")
typeId String typeId String
type CredentialType @relation(fields: [typeId], references: [id], onDelete: Cascade) type CredentialType @relation(fields: [typeId], references: [id], onDelete: Cascade)
+20 -1
View File
@@ -14,6 +14,9 @@ export default createRoute(
async (c) => { async (c) => {
const company = await companies.fetch(c.req.param("identifier")); const company = await companies.fetch(c.req.param("identifier"));
const includeAddress = c.req.query("includeAddress") === "true"; 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 // Check for address-specific permission if includeAddress is requested
if (includeAddress) { 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( const response = apiResponse.successful(
"Company Fetched Successfully!", "Company Fetched Successfully!",
company.toJson({ includeAddress }), company.toJson({
includeAddress,
includePrimaryContact,
includeAllContacts,
}),
); );
return c.json(response, response.status as ContentfulStatusCode); return c.json(response, response.status as ContentfulStatusCode);
}, },
+24
View File
@@ -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"],
}),
);
+2 -1
View File
@@ -1,6 +1,7 @@
import { default as fetchAll } from "./fetchAll"; import { default as fetchAll } from "./fetchAll";
import { default as fetch } from "./[id]/fetch"; import { default as fetch } from "./[id]/fetch";
import { default as configurations } from "./[id]/configurations"; import { default as configurations } from "./[id]/configurations";
import { default as unifiSites } from "./[id]/unifiSites";
import { default as count } from "./count"; import { default as count } from "./count";
export { configurations, count, fetch, fetchAll }; export { configurations, count, fetch, fetchAll, unifiSites };
+12 -9
View File
@@ -15,19 +15,22 @@ export default createRoute(
async (c) => { async (c) => {
const body = await c.req.json(); 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({ const schema = z.object({
name: z.string().min(1, "Name is required"), name: z.string().min(1, "Name is required"),
permissionScope: z.string().min(1, "Permission scope is required"), permissionScope: z.string().min(1, "Permission scope is required"),
icon: z.string().optional(), icon: z.string().optional(),
fields: z.array( fields: z.array(fieldSchema),
z.object({
id: z.string(),
name: z.string(),
required: z.boolean(),
secure: z.boolean(),
valueType: z.enum(Object.values(ValueType)),
}),
),
}); });
const data = schema.parse(body); const data = schema.parse(body);
+12 -11
View File
@@ -16,21 +16,22 @@ export default createRoute(
const body = await c.req.json(); const body = await c.req.json();
const credentialType = await credentialTypes.fetch(c.req.param("id")); 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({ const schema = z.object({
name: z.string().optional(), name: z.string().optional(),
permissionScope: z.string().optional(), permissionScope: z.string().optional(),
icon: z.string().optional(), icon: z.string().optional(),
fields: z fields: z.array(fieldSchema).optional(),
.array(
z.object({
id: z.string(),
name: z.string(),
required: z.boolean(),
secure: z.boolean(),
valueType: z.enum(Object.values(ValueType)),
}),
)
.optional(),
}); });
const data = schema.parse(body); const data = schema.parse(body);
+48
View File
@@ -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"],
}),
);
+16
View File
@@ -25,6 +25,22 @@ export default createRoute(
value: z.string(), 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); 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"],
}),
);
+6
View File
@@ -8,6 +8,9 @@ import { default as readSecureValues } from "./readSecureValues";
import { default as readSecureValue } from "./readSecureValue"; import { default as readSecureValue } from "./readSecureValue";
import { default as deleteCredential } from "./delete"; import { default as deleteCredential } from "./delete";
import { default as valueTypes } from "./valueTypes"; 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 { export {
valueTypes, valueTypes,
@@ -20,4 +23,7 @@ export {
readSecureValues, readSecureValues,
readSecureValue, readSecureValue,
deleteCredential as delete, 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"],
}),
);
+7
View File
@@ -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;
+1
View File
@@ -54,6 +54,7 @@ v1.route("/credential", require("./routers/credentialRouter").default);
v1.route("/credential-type", require("./routers/credentialTypeRouter").default); v1.route("/credential-type", require("./routers/credentialTypeRouter").default);
v1.route("/role", require("./routers/roleRouter").default); v1.route("/role", require("./routers/roleRouter").default);
v1.route("/permissions", require("./routers/permissionRouter").default); v1.route("/permissions", require("./routers/permissionRouter").default);
v1.route("/unifi", require("./routers/unifiRouter").default);
app.route("/v1", v1); app.route("/v1", v1);
export default app; export default app;
+43
View File
@@ -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,
};
+23
View File
@@ -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"],
}),
);
+23
View File
@@ -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"],
}),
);
+21
View File
@@ -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"] }),
);
+20
View File
@@ -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"] }),
);
+26
View File
@@ -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"] }),
);
+21
View File
@@ -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"] }),
);
+21
View File
@@ -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"] }),
);
+40
View File
@@ -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"],
}),
);
+21
View File
@@ -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"] }),
);
+31
View File
@@ -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"] }),
);
+47
View File
@@ -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",
],
}),
);
+24
View File
@@ -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"],
}),
);
+117
View File
@@ -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"],
}),
);
+23
View File
@@ -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"],
}),
);
+23
View File
@@ -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"],
}),
);
+38
View File
@@ -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",
],
}),
);
+25
View File
@@ -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"] }),
);
+20
View File
@@ -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"] }),
);
+20
View File
@@ -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"] }),
);
+11
View File
@@ -5,6 +5,7 @@ import * as msal from "@azure/msal-node";
import { Server } from "socket.io"; import { Server } from "socket.io";
import { Server as Engine } from "@socket.io/bun-engine"; import { Server as Engine } from "@socket.io/bun-engine";
import axios from "axios"; import axios from "axios";
import { UnifiClient } from "./modules/unifi-api/UnifiClient";
const connectionString = `${process.env.DATABASE_URL}`; const connectionString = `${process.env.DATABASE_URL}`;
const adapter = new PrismaPg({ connectionString }); const adapter = new PrismaPg({ connectionString });
@@ -67,3 +68,13 @@ const connectWiseApi = axios.create({
}); });
export { connectWiseApi }; 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);
+61 -14
View File
@@ -2,7 +2,7 @@ import { Company } from "../../generated/prisma/client";
import { fetchCwCompanyById } from "../modules/cw-utils/fetchCompany"; import { fetchCwCompanyById } from "../modules/cw-utils/fetchCompany";
import { fetchCompanyConfigurations } from "../modules/cw-utils/configurations/fetchCompanyConfigurations"; import { fetchCompanyConfigurations } from "../modules/cw-utils/configurations/fetchCompanyConfigurations";
import { updateCwInternalCompany } from "../modules/cw-utils/updateCompany"; import { updateCwInternalCompany } from "../modules/cw-utils/updateCompany";
import { Company as CWCompany } from "../types/ConnectWiseTypes"; import { Company as CWCompany, Contact } from "../types/ConnectWiseTypes";
/** /**
* Company Controller * Company Controller
@@ -16,9 +16,13 @@ export class CompanyController {
public name: string; public name: string;
public readonly cw_Identifier: string; public readonly cw_Identifier: string;
public readonly cw_CompanyId: number; 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.id = companyData.id;
this.name = companyData.name; this.name = companyData.name;
this.cw_Identifier = companyData.cw_Identifier; this.cw_Identifier = companyData.cw_Identifier;
@@ -67,23 +71,66 @@ export class CompanyController {
return data; return data;
} }
public toJson(opts?: { includeAddress: boolean }) { public toJson(opts?: {
includeAddress: boolean;
includePrimaryContact: boolean;
includeAllContacts?: boolean;
}) {
return { return {
id: this.id, id: this.id,
name: this.name, name: this.name,
cw_Identifier: this.cw_Identifier, cw_Identifier: this.cw_Identifier,
cw_CompanyId: this.cw_CompanyId, cw_CompanyId: this.cw_CompanyId,
cw_Data: { cw_Data: {
address: { address: !opts?.includeAddress
line1: this.cw_Data?.addressLine1, ? undefined
line2: this.cw_Data?.addressLine2 ?? null, : {
city: this.cw_Data?.city, line1: this.cw_Data?.company.addressLine1,
state: this.cw_Data?.state, line2: this.cw_Data?.company.addressLine2 ?? null,
zip: this.cw_Data?.zip, city: this.cw_Data?.company.city,
country: this.cw_Data?.country state: this.cw_Data?.company.state,
? this.cw_Data.country.name zip: this.cw_Data?.company.zip,
: "United States", 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
);
})(),
})),
}, },
}; };
} }
+88 -11
View File
@@ -28,11 +28,13 @@ export class CredentialController {
public notes: string | null; public notes: string | null;
public readonly typeId: string; public readonly typeId: string;
public readonly companyId: string; public readonly companyId: string;
public readonly subCredentialOfId: string | null;
public fields: any; public fields: any;
private _type: CredentialType; private _type: CredentialType;
private _company: Company; private _company: Company;
private _secureValues: SecureValue[]; private _secureValues: SecureValue[];
private _subCredentials: CredentialController[];
public readonly createdAt: Date; public readonly createdAt: Date;
public updatedAt: Date; public updatedAt: Date;
@@ -42,6 +44,11 @@ export class CredentialController {
type: CredentialType; type: CredentialType;
company: Company; company: Company;
securevalues: SecureValue[]; securevalues: SecureValue[];
subCredentials?: (Credential & {
type: CredentialType;
company: Company;
securevalues: SecureValue[];
})[];
}, },
) { ) {
this.id = credentialData.id; this.id = credentialData.id;
@@ -49,13 +56,69 @@ export class CredentialController {
this.notes = credentialData.notes; this.notes = credentialData.notes;
this.typeId = credentialData.typeId; this.typeId = credentialData.typeId;
this.companyId = credentialData.companyId; this.companyId = credentialData.companyId;
this.subCredentialOfId = credentialData.subCredentialOfId;
this._type = credentialData.type; this._type = credentialData.type;
this._company = credentialData.company; this._company = credentialData.company;
this._secureValues = credentialData.securevalues; this._secureValues = credentialData.securevalues;
this.fields = (() => { this._subCredentials = (credentialData.subCredentials ?? []).map(
let fields = credentialData.fields as Record<string, any>; (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, id: f.id,
name: f.name, name: f.name,
secure: f.secure, secure: f.secure,
@@ -63,13 +126,9 @@ export class CredentialController {
valueType: f.valueType as ValueType, valueType: f.valueType as ValueType,
value: f.secure value: f.secure
? `secure-${this._secureValues.find((sv) => sv.name === f.id)?.id}` ? `secure-${this._secureValues.find((sv) => sv.name === f.id)?.id}`
: fields[f.id], : raw[f.id],
})); };
});
return fields;
})();
this.createdAt = credentialData.createdAt;
this.updatedAt = credentialData.updatedAt;
} }
/** /**
@@ -151,6 +210,19 @@ export class CredentialController {
fieldsObject[field.fieldId] = field.value; 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 // Update the credential with non-secure fields
const updatedCredential = await prisma.credential.update({ const updatedCredential = await prisma.credential.update({
where: { id: this.id }, where: { id: this.id },
@@ -313,13 +385,14 @@ export class CredentialController {
* @param opts - Options to change the output * @param opts - Options to change the output
* @returns - An object that is JSON friendly * @returns - An object that is JSON friendly
*/ */
toJson(opts?: { includeSecureValues?: boolean }) { toJson(opts?: { includeSecureValues?: boolean }): Record<string, any> {
return { return {
id: this.id, id: this.id,
name: this.name, name: this.name,
notes: this.notes, notes: this.notes,
typeId: this.typeId, typeId: this.typeId,
companyId: this.companyId, companyId: this.companyId,
subCredentialOfId: this.subCredentialOfId ?? undefined,
fields: this.fields, fields: this.fields,
type: { type: {
id: this._type.id, id: this._type.id,
@@ -331,6 +404,10 @@ export class CredentialController {
id: this._company.id, id: this._company.id,
name: this._company.name, name: this._company.name,
}, },
subCredentials:
this._subCredentials.length > 0
? this._subCredentials.map((sc) => sc.toJson(opts))
: undefined,
secureFieldIds: opts?.includeSecureValues secureFieldIds: opts?.includeSecureValues
? this._secureValues.map((sv) => sv.name) ? this._secureValues.map((sv) => sv.name)
: undefined, : undefined,
+29
View File
@@ -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,
};
}
}
+6
View File
@@ -1,6 +1,7 @@
import { refresh } from "./api/auth"; import { refresh } from "./api/auth";
import app from "./api/server"; import app from "./api/server";
import { engine, PORT } from "./constants"; import { engine, PORT } from "./constants";
import { unifiSites } from "./managers/unifiSites";
import { refreshCompanies } from "./modules/cw-utils/refreshCompanies"; import { refreshCompanies } from "./modules/cw-utils/refreshCompanies";
import { events, setupEventDebugger } from "./modules/globalEvents"; import { events, setupEventDebugger } from "./modules/globalEvents";
@@ -13,6 +14,11 @@ setInterval(() => {
return refreshCompanies(); return refreshCompanies();
}, 60 * 1000); }, 60 * 1000);
await unifiSites.syncSites();
setInterval(() => {
return unifiSites.syncSites();
}, 60 * 1000);
Bun.serve({ Bun.serve({
port: PORT, port: PORT,
websocket: engine.handler().websocket, websocket: engine.handler().websocket,
+13 -2
View File
@@ -12,10 +12,21 @@ export const companies = {
if (!search) throw new Error("Unknown company."); if (!search) throw new Error("Unknown company.");
const freshCwData = await connectWiseApi.get( const freshCwData: { data: Company } = await connectWiseApi.get(
`/company/companies/${search.cw_CompanyId}`, `/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() { async count() {
-2
View File
@@ -79,8 +79,6 @@ export const credentialTypes = {
}); });
} }
console.log(data.fields);
const credentialType = await prisma.credentialType.create({ const credentialType = await prisma.credentialType.create({
data: { data: {
name: data.name, name: data.name,
+288 -26
View File
@@ -4,10 +4,28 @@ import { fieldValidator } from "../modules/credentials/fieldValidator";
import { import {
CredentialField, CredentialField,
CredentialTypeField, CredentialTypeField,
ValueType,
} from "../modules/credentials/credentialTypeDefs"; } from "../modules/credentials/credentialTypeDefs";
import { generateSecureValue } from "../modules/credentials/generateSecureValue"; import { generateSecureValue } from "../modules/credentials/generateSecureValue";
import GenericError from "../Errors/GenericError"; 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 = { export const credentials = {
/** /**
* Fetch Credential * Fetch Credential
@@ -20,11 +38,7 @@ export const credentials = {
async fetch(id: string): Promise<CredentialController> { async fetch(id: string): Promise<CredentialController> {
const credential = await prisma.credential.findFirst({ const credential = await prisma.credential.findFirst({
where: { id }, where: { id },
include: { include: credentialInclude,
type: true,
company: true,
securevalues: true,
},
}); });
if (!credential) { if (!credential) {
@@ -42,19 +56,17 @@ export const credentials = {
/** /**
* Fetch Credentials by Company * 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 * @param companyId - The company ID to fetch credentials for
* @returns {Promise<CredentialController[]>} - Array of credential controllers * @returns {Promise<CredentialController[]>} - Array of credential controllers
*/ */
async fetchByCompany(companyId: string): Promise<CredentialController[]> { async fetchByCompany(companyId: string): Promise<CredentialController[]> {
const credentialsList = await prisma.credential.findMany({ const credentialsList = await prisma.credential.findMany({
where: { companyId }, where: { companyId, subCredentialOfId: null },
include: { include: credentialInclude,
type: true,
company: true,
securevalues: true,
},
}); });
return credentialsList.map((cred) => new CredentialController(cred)); return credentialsList.map((cred) => new CredentialController(cred));
@@ -68,6 +80,10 @@ export const credentials = {
* the credential type, encrypting secure fields, and inserting everything * the credential type, encrypting secure fields, and inserting everything
* into the database atomically. * 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 * @param data - The credential data to create
* @returns {Promise<CredentialController>} - The created credential controller * @returns {Promise<CredentialController>} - The created credential controller
*/ */
@@ -80,6 +96,10 @@ export const credentials = {
fieldId: string; fieldId: string;
value: string; value: string;
}[]; }[];
subCredentials?: Record<
string,
{ name: string; fields: { fieldId: string; value: string }[] }[]
>;
}): Promise<CredentialController> { }): Promise<CredentialController> {
// Fetch the credential type to get acceptable fields // Fetch the credential type to get acceptable fields
const credentialType = await prisma.credentialType.findFirst({ const credentialType = await prisma.credentialType.findFirst({
@@ -95,22 +115,31 @@ export const credentials = {
}); });
} }
// Validate the fields against acceptable fields const typeFields = credentialType.fields! as any as CredentialTypeField[];
const acceptableFields = (
credentialType.fields! as any as CredentialTypeField[] // Validate the fields against acceptable fields (exclude multi-credential fields
).map((f: { id: string; name: string; secure: boolean }) => ({ // from value validation since they don't carry a direct value).
const acceptableFields = typeFields.map((f) => ({
id: f.id, id: f.id,
name: f.name, name: f.name,
secure: f.secure, secure: f.secure,
required: f.required,
valueType: f.valueType,
subFields: f.subFields,
})) as CredentialTypeField[]; })) as CredentialTypeField[];
const validatedFields = await fieldValidator( const validatedFields = await fieldValidator(
data.fields as any as CredentialField[], data.fields as any as CredentialField[],
acceptableFields, acceptableFields,
); );
// Separate secure and non-secure fields // Separate secure, non-secure, and multi-credential fields
const secureFields = validatedFields.filter((f) => f.secure); const secureFields = validatedFields.filter(
const nonSecureFields = validatedFields.filter((f) => !f.secure); (f) => f.secure && !f.isMultiCredential,
);
const nonSecureFields = validatedFields.filter(
(f) => !f.secure && !f.isMultiCredential,
);
// Build fields object for non-secure fields // Build fields object for non-secure fields
const fieldsObject: Record<string, any> = {}; const fieldsObject: Record<string, any> = {};
@@ -118,6 +147,13 @@ export const credentials = {
fieldsObject[field.fieldId] = field.value; 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 // Encrypt secure field values
const secureValueData = secureFields.map((field) => { const secureValueData = secureFields.map((field) => {
const { encrypted, hash } = generateSecureValue(field.value); 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({ const credential = await prisma.credential.create({
data: { data: {
name: data.name, name: data.name,
@@ -140,20 +176,246 @@ export const credentials = {
create: secureValueData, 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 Credential
* *
* Delete a credential by its ID. * Delete a credential by its ID.
* Sub-credentials are cascade-deleted automatically by the database.
* *
* @param id - The credential ID to delete * @param id - The credential ID to delete
* @returns {Promise<void>} * @returns {Promise<void>}
+293
View File
@@ -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", GENERIC_SECRET = "generic_secret",
BITLOCKER_KEY = "bitlocker_key", BITLOCKER_KEY = "bitlocker_key",
PASSWORD = "password", PASSWORD = "password",
MULTI_CREDENTIAL = "multi_credential",
} }
export interface CredentialTypeField { export interface CredentialTypeField {
id: string; // I.e. "clientId", "clientSecret", etc. id: string; // I.e. "clientId", "clientSecret", etc.
name: string; // I.e. "Client ID", "Client Secret", etc. name: string; // I.e. "Client ID", "Client Secret", etc.
required: boolean; 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 secure: boolean; // Whether this field should be stored encrypted in the database
valueType: ValueType; // For future extensibility, currently all fields are strings valueType: ValueType; // For future extensibility, currently all fields are strings
} }
@@ -18,4 +20,5 @@ export interface CredentialTypeField {
export interface CredentialField { export interface CredentialField {
fieldId: string; // I.e. "clientId", "clientSecret", etc. fieldId: string; // I.e. "clientId", "clientSecret", etc.
value: string; // Encrypted value stored in the database 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
} }
+29 -8
View File
@@ -1,7 +1,19 @@
import { Collection } from "@discordjs/collection"; import { Collection } from "@discordjs/collection";
import { CredentialField, CredentialTypeField } from "./credentialTypeDefs"; import {
CredentialField,
CredentialTypeField,
ValueType,
} from "./credentialTypeDefs";
import GenericError from "../../Errors/GenericError"; import GenericError from "../../Errors/GenericError";
export interface ValidatedField {
fieldId: string;
value: string;
secure: boolean;
isMultiCredential?: boolean;
subCredentials?: string[];
}
/** /**
* Field Validator * 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 * 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. * 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 fields - The fields in object form that are being submitted.
* @param acceptableFields - The acceptable field to be compared against. * @param acceptableFields - The acceptable field to be compared against.
*/ */
export const fieldValidator = async ( export const fieldValidator = async (
fields: CredentialField[], fields: CredentialField[],
acceptableFields: CredentialTypeField[], acceptableFields: CredentialTypeField[],
): Promise< ): Promise<ValidatedField[]> => {
{
fieldId: string;
value: string;
secure: boolean;
}[]
> => {
const afCollection = new Collection(acceptableFields.map((f) => [f.id, f])); const afCollection = new Collection(acceptableFields.map((f) => [f.id, f]));
await Promise.all( await Promise.all(
@@ -45,6 +54,18 @@ export const fieldValidator = async (
return fields.map((field) => { return fields.map((field) => {
const matchingField = afCollection.get(field.fieldId)!; 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 { return {
fieldId: field.fieldId, fieldId: field.fieldId,
value: field.value, 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;
};
+952
View File
@@ -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");
}
}
+434
View File
@@ -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[];
}
+140 -3
View File
@@ -15,7 +15,7 @@ export interface Company {
territory: Territory; territory: Territory;
market: Market; market: Market;
accountNumber: string; accountNumber: string;
defaultContact: Contact; defaultContact: ContactHref;
dateAcquired: string; dateAcquired: string;
annualRevenue: number; annualRevenue: number;
numberOfEmployees: number; numberOfEmployees: number;
@@ -27,7 +27,7 @@ export interface Company {
billingTerms: BasicEntity; billingTerms: BasicEntity;
billToCompany: LinkedCompany; billToCompany: LinkedCompany;
billingSite: LinkedSite; billingSite: LinkedSite;
billingContact: Contact; billingContact: ContactHref;
invoiceDeliveryMethod: BasicEntity; invoiceDeliveryMethod: BasicEntity;
invoiceToEmailAddress: string; invoiceToEmailAddress: string;
deletedFlag: boolean; deletedFlag: boolean;
@@ -91,7 +91,7 @@ export interface LinkedSite extends BasicEntity {
}; };
} }
export interface Contact { export interface ContactHref {
id: number; id: number;
name: string; name: string;
_info: { _info: {
@@ -305,3 +305,140 @@ export interface ConfigurationInfo {
// Your payload is an array: // Your payload is an array:
export type ConfigurationResponse = ConfigurationItem[]; 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;
}
+267
View File
@@ -15,6 +15,13 @@ export interface PermissionNode {
usedIn: string[]; usedIn: string[];
/** Dependencies - other permissions that must be granted alongside this one */ /** Dependencies - other permissions that must be granted alongside this one */
dependencies?: string[]; 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 { export interface PermissionCategory {
@@ -56,6 +63,12 @@ export const PERMISSION_NODES = {
usedIn: ["src/api/companies/[id]/fetch.ts"], usedIn: ["src/api/companies/[id]/fetch.ts"],
dependencies: ["company.fetch"], 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", node: "company.fetch.many",
description: "Fetch multiple companies", description: "Fetch multiple companies",
@@ -120,6 +133,24 @@ export const PERMISSION_NODES = {
], ],
dependencies: ["credential.fetch"], 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>; } as const satisfies Record<string, PermissionCategory>;
/** /**
+133
View File
@@ -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));
+123
View File
@@ -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
View File
@@ -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));