Version
This commit is contained in:
@@ -42,6 +42,11 @@ export type UnifiSite = Prisma.UnifiSiteModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type Company = Prisma.CompanyModel
|
export type Company = Prisma.CompanyModel
|
||||||
|
/**
|
||||||
|
* Model CatalogItem
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type CatalogItem = Prisma.CatalogItemModel
|
||||||
/**
|
/**
|
||||||
* Model CredentialType
|
* Model CredentialType
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ export type UnifiSite = Prisma.UnifiSiteModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type Company = Prisma.CompanyModel
|
export type Company = Prisma.CompanyModel
|
||||||
|
/**
|
||||||
|
* Model CatalogItem
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type CatalogItem = Prisma.CatalogItemModel
|
||||||
/**
|
/**
|
||||||
* Model CredentialType
|
* Model CredentialType
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -175,6 +175,60 @@ export type IntWithAggregatesFilter<$PrismaModel = never> = {
|
|||||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IntNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FloatFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FloatWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
export type JsonFilter<$PrismaModel = never> =
|
export type JsonFilter<$PrismaModel = never> =
|
||||||
| Prisma.PatchUndefined<
|
| Prisma.PatchUndefined<
|
||||||
Prisma.Either<Required<JsonFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonFilterBase<$PrismaModel>>, 'path'>>,
|
Prisma.Either<Required<JsonFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
@@ -400,6 +454,49 @@ export type NestedFloatFilter<$PrismaModel = never> = {
|
|||||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedFloatNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedFloatWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
export type NestedJsonFilter<$PrismaModel = never> =
|
export type NestedJsonFilter<$PrismaModel = never> =
|
||||||
| Prisma.PatchUndefined<
|
| Prisma.PatchUndefined<
|
||||||
Prisma.Either<Required<NestedJsonFilterBase<$PrismaModel>>, Exclude<keyof Required<NestedJsonFilterBase<$PrismaModel>>, 'path'>>,
|
Prisma.Either<Required<NestedJsonFilterBase<$PrismaModel>>, Exclude<keyof Required<NestedJsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -389,6 +389,7 @@ export const ModelName = {
|
|||||||
Role: 'Role',
|
Role: 'Role',
|
||||||
UnifiSite: 'UnifiSite',
|
UnifiSite: 'UnifiSite',
|
||||||
Company: 'Company',
|
Company: 'Company',
|
||||||
|
CatalogItem: 'CatalogItem',
|
||||||
CredentialType: 'CredentialType',
|
CredentialType: 'CredentialType',
|
||||||
SecureValue: 'SecureValue',
|
SecureValue: 'SecureValue',
|
||||||
Credential: 'Credential'
|
Credential: 'Credential'
|
||||||
@@ -407,7 +408,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
omit: GlobalOmitOptions
|
omit: GlobalOmitOptions
|
||||||
}
|
}
|
||||||
meta: {
|
meta: {
|
||||||
modelProps: "session" | "user" | "role" | "unifiSite" | "company" | "credentialType" | "secureValue" | "credential"
|
modelProps: "session" | "user" | "role" | "unifiSite" | "company" | "catalogItem" | "credentialType" | "secureValue" | "credential"
|
||||||
txIsolationLevel: TransactionIsolationLevel
|
txIsolationLevel: TransactionIsolationLevel
|
||||||
}
|
}
|
||||||
model: {
|
model: {
|
||||||
@@ -781,6 +782,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CatalogItem: {
|
||||||
|
payload: Prisma.$CatalogItemPayload<ExtArgs>
|
||||||
|
fields: Prisma.CatalogItemFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.CatalogItemFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.CatalogItemFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.CatalogItemFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.CatalogItemFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.CatalogItemFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.CatalogItemCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.CatalogItemCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.CatalogItemCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.CatalogItemDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.CatalogItemUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.CatalogItemDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.CatalogItemUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.CatalogItemUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.CatalogItemUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CatalogItemPayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.CatalogItemAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateCatalogItem>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.CatalogItemGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.CatalogItemGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.CatalogItemCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.CatalogItemCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
CredentialType: {
|
CredentialType: {
|
||||||
payload: Prisma.$CredentialTypePayload<ExtArgs>
|
payload: Prisma.$CredentialTypePayload<ExtArgs>
|
||||||
fields: Prisma.CredentialTypeFieldRefs
|
fields: Prisma.CredentialTypeFieldRefs
|
||||||
@@ -1108,6 +1183,32 @@ export const CompanyScalarFieldEnum = {
|
|||||||
export type CompanyScalarFieldEnum = (typeof CompanyScalarFieldEnum)[keyof typeof CompanyScalarFieldEnum]
|
export type CompanyScalarFieldEnum = (typeof CompanyScalarFieldEnum)[keyof typeof CompanyScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const CatalogItemScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
cwCatalogId: 'cwCatalogId',
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
customerDescription: 'customerDescription',
|
||||||
|
internalNotes: 'internalNotes',
|
||||||
|
manufacturer: 'manufacturer',
|
||||||
|
manufactureCwId: 'manufactureCwId',
|
||||||
|
partNumber: 'partNumber',
|
||||||
|
vendorName: 'vendorName',
|
||||||
|
vendorSku: 'vendorSku',
|
||||||
|
vendorCwId: 'vendorCwId',
|
||||||
|
price: 'price',
|
||||||
|
cost: 'cost',
|
||||||
|
inactive: 'inactive',
|
||||||
|
salesTaxable: 'salesTaxable',
|
||||||
|
onHand: 'onHand',
|
||||||
|
cwLastUpdated: 'cwLastUpdated',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type CatalogItemScalarFieldEnum = (typeof CatalogItemScalarFieldEnum)[keyof typeof CatalogItemScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const CredentialTypeScalarFieldEnum = {
|
export const CredentialTypeScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@@ -1244,20 +1345,6 @@ export type ListIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to a field of type 'Json'
|
|
||||||
*/
|
|
||||||
export type JsonFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Json'>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to a field of type 'QueryMode'
|
|
||||||
*/
|
|
||||||
export type EnumQueryModeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'QueryMode'>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to a field of type 'Float'
|
* Reference to a field of type 'Float'
|
||||||
*/
|
*/
|
||||||
@@ -1271,6 +1358,20 @@ export type FloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, '
|
|||||||
export type ListFloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float[]'>
|
export type ListFloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float[]'>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to a field of type 'Json'
|
||||||
|
*/
|
||||||
|
export type JsonFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Json'>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to a field of type 'QueryMode'
|
||||||
|
*/
|
||||||
|
export type EnumQueryModeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'QueryMode'>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Batch Payload for updateMany & deleteMany & createMany
|
* Batch Payload for updateMany & deleteMany & createMany
|
||||||
*/
|
*/
|
||||||
@@ -1371,6 +1472,7 @@ export type GlobalOmitConfig = {
|
|||||||
role?: Prisma.RoleOmit
|
role?: Prisma.RoleOmit
|
||||||
unifiSite?: Prisma.UnifiSiteOmit
|
unifiSite?: Prisma.UnifiSiteOmit
|
||||||
company?: Prisma.CompanyOmit
|
company?: Prisma.CompanyOmit
|
||||||
|
catalogItem?: Prisma.CatalogItemOmit
|
||||||
credentialType?: Prisma.CredentialTypeOmit
|
credentialType?: Prisma.CredentialTypeOmit
|
||||||
secureValue?: Prisma.SecureValueOmit
|
secureValue?: Prisma.SecureValueOmit
|
||||||
credential?: Prisma.CredentialOmit
|
credential?: Prisma.CredentialOmit
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export const ModelName = {
|
|||||||
Role: 'Role',
|
Role: 'Role',
|
||||||
UnifiSite: 'UnifiSite',
|
UnifiSite: 'UnifiSite',
|
||||||
Company: 'Company',
|
Company: 'Company',
|
||||||
|
CatalogItem: 'CatalogItem',
|
||||||
CredentialType: 'CredentialType',
|
CredentialType: 'CredentialType',
|
||||||
SecureValue: 'SecureValue',
|
SecureValue: 'SecureValue',
|
||||||
Credential: 'Credential'
|
Credential: 'Credential'
|
||||||
@@ -143,6 +144,32 @@ export const CompanyScalarFieldEnum = {
|
|||||||
export type CompanyScalarFieldEnum = (typeof CompanyScalarFieldEnum)[keyof typeof CompanyScalarFieldEnum]
|
export type CompanyScalarFieldEnum = (typeof CompanyScalarFieldEnum)[keyof typeof CompanyScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const CatalogItemScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
cwCatalogId: 'cwCatalogId',
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
customerDescription: 'customerDescription',
|
||||||
|
internalNotes: 'internalNotes',
|
||||||
|
manufacturer: 'manufacturer',
|
||||||
|
manufactureCwId: 'manufactureCwId',
|
||||||
|
partNumber: 'partNumber',
|
||||||
|
vendorName: 'vendorName',
|
||||||
|
vendorSku: 'vendorSku',
|
||||||
|
vendorCwId: 'vendorCwId',
|
||||||
|
price: 'price',
|
||||||
|
cost: 'cost',
|
||||||
|
inactive: 'inactive',
|
||||||
|
salesTaxable: 'salesTaxable',
|
||||||
|
onHand: 'onHand',
|
||||||
|
cwLastUpdated: 'cwLastUpdated',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type CatalogItemScalarFieldEnum = (typeof CatalogItemScalarFieldEnum)[keyof typeof CatalogItemScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const CredentialTypeScalarFieldEnum = {
|
export const CredentialTypeScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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/UnifiSite.ts'
|
||||||
export type * from './models/Company.ts'
|
export type * from './models/Company.ts'
|
||||||
|
export type * from './models/CatalogItem.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'
|
||||||
export type * from './models/Credential.ts'
|
export type * from './models/Credential.ts'
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -82,6 +82,39 @@ model Company {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model CatalogItem {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
cwCatalogId Int @unique
|
||||||
|
name String
|
||||||
|
description String?
|
||||||
|
customerDescription String?
|
||||||
|
internalNotes String?
|
||||||
|
|
||||||
|
linkedItems CatalogItem[] @relation("LinkedItems")
|
||||||
|
linkedTo CatalogItem[] @relation("LinkedItems")
|
||||||
|
|
||||||
|
manufacturer String?
|
||||||
|
manufactureCwId Int?
|
||||||
|
|
||||||
|
partNumber String?
|
||||||
|
|
||||||
|
vendorName String?
|
||||||
|
vendorSku String?
|
||||||
|
vendorCwId Int?
|
||||||
|
|
||||||
|
price Float
|
||||||
|
cost Float
|
||||||
|
|
||||||
|
inactive Boolean @default(false)
|
||||||
|
salesTaxable Boolean @default(true)
|
||||||
|
|
||||||
|
onHand Int @default(0)
|
||||||
|
cwLastUpdated DateTime?
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
model CredentialType {
|
model CredentialType {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
|
|||||||
+17
-10
@@ -24,16 +24,23 @@ export const sessionDuration = 30 * 24 * 60 * 60000;
|
|||||||
export const accessTokenDuration = "10min";
|
export const accessTokenDuration = "10min";
|
||||||
export const refreshTokenDuration = "30d";
|
export const refreshTokenDuration = "30d";
|
||||||
|
|
||||||
export const accessTokenPrivateKey =
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
readFileSync(`.accessToken.key`).toString();
|
|
||||||
export const refreshTokenPrivateKey =
|
export const accessTokenPrivateKey = isProduction
|
||||||
readFileSync(`.refreshToken.key`).toString();
|
? process.env.ACCESS_TOKEN_PRIVATE_KEY!
|
||||||
export const permissionsPrivateKey = readFileSync(`.permissions.key`);
|
: readFileSync(`.accessToken.key`).toString();
|
||||||
export const secureValuesPrivateKey =
|
export const refreshTokenPrivateKey = isProduction
|
||||||
readFileSync(`.secureValues.key`).toString();
|
? process.env.REFRESH_TOKEN_PRIVATE_KEY!
|
||||||
export const secureValuesPublicKey = readFileSync(
|
: readFileSync(`.refreshToken.key`).toString();
|
||||||
`public-keys/.secureValues.pub`,
|
export const permissionsPrivateKey = isProduction
|
||||||
).toString();
|
? process.env.PERMISSIONS_PRIVATE_KEY!
|
||||||
|
: readFileSync(`.permissions.key`).toString();
|
||||||
|
export const secureValuesPrivateKey = isProduction
|
||||||
|
? process.env.SECURE_VALUES_PRIVATE_KEY!
|
||||||
|
: readFileSync(`.secureValues.key`).toString();
|
||||||
|
export const secureValuesPublicKey = isProduction
|
||||||
|
? process.env.SECURE_VALUES_PUBLIC_KEY!
|
||||||
|
: readFileSync(`public-keys/.secureValues.pub`).toString();
|
||||||
|
|
||||||
// Microsoft Auth Constants
|
// Microsoft Auth Constants
|
||||||
const msalConfig: msal.Configuration = {
|
const msalConfig: msal.Configuration = {
|
||||||
|
|||||||
+18
-1
@@ -1,8 +1,10 @@
|
|||||||
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, unifi, unifiPassword, unifiUsername } from "./constants";
|
||||||
import { unifiSites } from "./managers/unifiSites";
|
import { unifiSites } from "./managers/unifiSites";
|
||||||
import { refreshCompanies } from "./modules/cw-utils/refreshCompanies";
|
import { refreshCompanies } from "./modules/cw-utils/refreshCompanies";
|
||||||
|
import { refreshCatalog } from "./modules/cw-utils/procurement/refreshCatalog";
|
||||||
|
import { refreshInventory } from "./modules/cw-utils/procurement/refreshInventory";
|
||||||
import { events, setupEventDebugger } from "./modules/globalEvents";
|
import { events, setupEventDebugger } from "./modules/globalEvents";
|
||||||
|
|
||||||
// Setup global event debugger in non-production environments
|
// Setup global event debugger in non-production environments
|
||||||
@@ -14,6 +16,21 @@ setInterval(() => {
|
|||||||
return refreshCompanies();
|
return refreshCompanies();
|
||||||
}, 60 * 1000);
|
}, 60 * 1000);
|
||||||
|
|
||||||
|
// Refresh the internal catalog every minute
|
||||||
|
await refreshCatalog();
|
||||||
|
setInterval(() => {
|
||||||
|
return refreshCatalog();
|
||||||
|
}, 60 * 1000);
|
||||||
|
|
||||||
|
// Refresh inventory on hand every 2 minutes
|
||||||
|
await refreshInventory();
|
||||||
|
setInterval(
|
||||||
|
() => {
|
||||||
|
return refreshInventory();
|
||||||
|
},
|
||||||
|
2 * 60 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
await unifiSites.syncSites();
|
await unifiSites.syncSites();
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
return unifiSites.syncSites();
|
return unifiSites.syncSites();
|
||||||
|
|||||||
+54
-41
@@ -1,6 +1,7 @@
|
|||||||
import { prisma, unifi, unifiUsername, unifiPassword } from "../constants";
|
import { prisma, unifi, unifiUsername, unifiPassword } from "../constants";
|
||||||
import GenericError from "../Errors/GenericError";
|
import GenericError from "../Errors/GenericError";
|
||||||
import { UnifiSite } from "../../generated/prisma/client";
|
import { UnifiSite } from "../../generated/prisma/client";
|
||||||
|
import { events } from "../modules/globalEvents";
|
||||||
|
|
||||||
let loggedIn = false;
|
let loggedIn = false;
|
||||||
|
|
||||||
@@ -16,6 +17,24 @@ async function ensureLoggedIn(): Promise<void> {
|
|||||||
loggedIn = true;
|
loggedIn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a UniFi API call with automatic re-authentication on failure.
|
||||||
|
* If the call fails and we were previously logged in (i.e. session likely
|
||||||
|
* expired), resets the login state, re-authenticates, and retries once.
|
||||||
|
*/
|
||||||
|
async function withReauth<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
|
await ensureLoggedIn();
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} catch (e) {
|
||||||
|
if (!loggedIn) throw e;
|
||||||
|
events.emit("unifi:reauth");
|
||||||
|
loggedIn = false;
|
||||||
|
await ensureLoggedIn();
|
||||||
|
return fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const unifiSites = {
|
export const unifiSites = {
|
||||||
/**
|
/**
|
||||||
* Fetch a UniFi site record from the database by its internal ID.
|
* Fetch a UniFi site record from the database by its internal ID.
|
||||||
@@ -96,12 +115,14 @@ export const unifiSites = {
|
|||||||
* Creates new records for sites not yet tracked, updates names for existing ones.
|
* Creates new records for sites not yet tracked, updates names for existing ones.
|
||||||
*/
|
*/
|
||||||
async syncSites(): Promise<UnifiSite[]> {
|
async syncSites(): Promise<UnifiSite[]> {
|
||||||
await ensureLoggedIn();
|
events.emit("unifi:sites:sync:started");
|
||||||
|
|
||||||
// Fetch all sites from the controller
|
// Fetch all sites from the controller
|
||||||
const allSites = await unifi.getAllSites();
|
const allSites = await withReauth(() => unifi.getAllSites());
|
||||||
|
|
||||||
const results: UnifiSite[] = [];
|
const results: UnifiSite[] = [];
|
||||||
|
let created = 0;
|
||||||
|
let updated = 0;
|
||||||
|
|
||||||
for (const site of allSites) {
|
for (const site of allSites) {
|
||||||
const existing = await prisma.unifiSite.findFirst({
|
const existing = await prisma.unifiSite.findFirst({
|
||||||
@@ -109,22 +130,30 @@ export const unifiSites = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
const updated = await prisma.unifiSite.update({
|
const updatedSite = await prisma.unifiSite.update({
|
||||||
where: { id: existing.id },
|
where: { id: existing.id },
|
||||||
data: { name: site.description },
|
data: { name: site.description },
|
||||||
});
|
});
|
||||||
results.push(updated);
|
results.push(updatedSite);
|
||||||
|
updated++;
|
||||||
} else {
|
} else {
|
||||||
const created = await prisma.unifiSite.create({
|
const createdSite = await prisma.unifiSite.create({
|
||||||
data: {
|
data: {
|
||||||
name: site.description,
|
name: site.description,
|
||||||
siteId: site.name,
|
siteId: site.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
results.push(created);
|
results.push(createdSite);
|
||||||
|
created++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
events.emit("unifi:sites:sync:completed", {
|
||||||
|
total: results.length,
|
||||||
|
created,
|
||||||
|
updated,
|
||||||
|
});
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -132,24 +161,21 @@ export const unifiSites = {
|
|||||||
* Get live site overview from the UniFi controller.
|
* Get live site overview from the UniFi controller.
|
||||||
*/
|
*/
|
||||||
async getSiteOverview(siteId: string) {
|
async getSiteOverview(siteId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getSiteOverview(siteId));
|
||||||
return unifi.getSiteOverview(siteId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get live devices from the UniFi controller for a site.
|
* Get live devices from the UniFi controller for a site.
|
||||||
*/
|
*/
|
||||||
async getDevices(siteId: string) {
|
async getDevices(siteId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getDevices(siteId));
|
||||||
return unifi.getDevices(siteId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get live WiFi networks (WLANs) from the UniFi controller for a site.
|
* Get live WiFi networks (WLANs) from the UniFi controller for a site.
|
||||||
*/
|
*/
|
||||||
async getWlanConf(siteId: string) {
|
async getWlanConf(siteId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getWlanConf(siteId));
|
||||||
return unifi.getWlanConf(siteId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,25 +186,21 @@ export const unifiSites = {
|
|||||||
wlanId: string,
|
wlanId: string,
|
||||||
updates: Parameters<typeof unifi.updateWlanConf>[2],
|
updates: Parameters<typeof unifi.updateWlanConf>[2],
|
||||||
) {
|
) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.updateWlanConf(siteId, wlanId, updates));
|
||||||
return unifi.updateWlanConf(siteId, wlanId, updates);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get live network configurations from the UniFi controller for a site.
|
* Get live network configurations from the UniFi controller for a site.
|
||||||
*/
|
*/
|
||||||
async getNetworks(siteId: string) {
|
async getNetworks(siteId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getNetworks(siteId));
|
||||||
return unifi.getNetworks(siteId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new site on the UniFi controller and track it in the database.
|
* Create a new site on the UniFi controller and track it in the database.
|
||||||
*/
|
*/
|
||||||
async createSite(description: string): Promise<UnifiSite> {
|
async createSite(description: string): Promise<UnifiSite> {
|
||||||
await ensureLoggedIn();
|
const created = await withReauth(() => unifi.createSite(description));
|
||||||
|
|
||||||
const created = await unifi.createSite(description);
|
|
||||||
|
|
||||||
return prisma.unifiSite.create({
|
return prisma.unifiSite.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -192,8 +214,7 @@ export const unifiSites = {
|
|||||||
* Get WLAN groups from the UniFi controller for a site.
|
* Get WLAN groups from the UniFi controller for a site.
|
||||||
*/
|
*/
|
||||||
async getWlanGroups(siteId: string) {
|
async getWlanGroups(siteId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getWlanGroups(siteId));
|
||||||
return unifi.getWlanGroups(siteId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,16 +224,14 @@ export const unifiSites = {
|
|||||||
siteId: string,
|
siteId: string,
|
||||||
input: Parameters<typeof unifi.createWlanGroup>[1],
|
input: Parameters<typeof unifi.createWlanGroup>[1],
|
||||||
) {
|
) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.createWlanGroup(siteId, input));
|
||||||
return unifi.createWlanGroup(siteId, input);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user groups (speed profiles) from the UniFi controller for a site.
|
* Get user groups (speed profiles) from the UniFi controller for a site.
|
||||||
*/
|
*/
|
||||||
async getUserGroups(siteId: string) {
|
async getUserGroups(siteId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getUserGroups(siteId));
|
||||||
return unifi.getUserGroups(siteId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -222,16 +241,14 @@ export const unifiSites = {
|
|||||||
siteId: string,
|
siteId: string,
|
||||||
input: Parameters<typeof unifi.createUserGroup>[1],
|
input: Parameters<typeof unifi.createUserGroup>[1],
|
||||||
) {
|
) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.createUserGroup(siteId, input));
|
||||||
return unifi.createUserGroup(siteId, input);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get AP groups from the UniFi controller for a site.
|
* Get AP groups from the UniFi controller for a site.
|
||||||
*/
|
*/
|
||||||
async getApGroups(siteId: string) {
|
async getApGroups(siteId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getApGroups(siteId));
|
||||||
return unifi.getApGroups(siteId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -243,40 +260,37 @@ export const unifiSites = {
|
|||||||
deviceMacs: string[],
|
deviceMacs: string[],
|
||||||
forWlanconf: boolean = false,
|
forWlanconf: boolean = false,
|
||||||
) {
|
) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() =>
|
||||||
return unifi.createApGroup(siteId, name, deviceMacs, forWlanconf);
|
unifi.createApGroup(siteId, name, deviceMacs, forWlanconf),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an existing AP group's device MACs on the UniFi controller.
|
* Update an existing AP group's device MACs on the UniFi controller.
|
||||||
*/
|
*/
|
||||||
async updateApGroup(siteId: string, groupId: string, deviceMacs: string[]) {
|
async updateApGroup(siteId: string, groupId: string, deviceMacs: string[]) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.updateApGroup(siteId, groupId, deviceMacs));
|
||||||
return unifi.updateApGroup(siteId, groupId, deviceMacs);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get access points only from the UniFi controller for a site.
|
* Get access points only from the UniFi controller for a site.
|
||||||
*/
|
*/
|
||||||
async getAccessPoints(siteId: string) {
|
async getAccessPoints(siteId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getAccessPoints(siteId));
|
||||||
return unifi.getAccessPoints(siteId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get WiFi SSID limits per AP per radio band.
|
* Get WiFi SSID limits per AP per radio band.
|
||||||
*/
|
*/
|
||||||
async getWifiLimits(siteId: string) {
|
async getWifiLimits(siteId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getWifiLimits(siteId));
|
||||||
return unifi.getWifiLimits(siteId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get private pre-shared keys for a specific WLAN.
|
* Get private pre-shared keys for a specific WLAN.
|
||||||
*/
|
*/
|
||||||
async getPrivatePSKs(siteId: string, wlanId: string) {
|
async getPrivatePSKs(siteId: string, wlanId: string) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.getPrivatePSKs(siteId, wlanId));
|
||||||
return unifi.getPrivatePSKs(siteId, wlanId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -287,7 +301,6 @@ export const unifiSites = {
|
|||||||
wlanId: string,
|
wlanId: string,
|
||||||
psk: Parameters<typeof unifi.createPrivatePSK>[2],
|
psk: Parameters<typeof unifi.createPrivatePSK>[2],
|
||||||
) {
|
) {
|
||||||
await ensureLoggedIn();
|
return withReauth(() => unifi.createPrivatePSK(siteId, wlanId, psk));
|
||||||
return unifi.createPrivatePSK(siteId, wlanId, psk);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { Collection } from "@discordjs/collection";
|
||||||
|
import { connectWiseApi } from "../../../constants";
|
||||||
|
import { CatalogItem } from "./catalog.types.ts";
|
||||||
|
|
||||||
|
export interface CatalogSummary {
|
||||||
|
id: number;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InventoryEntry {
|
||||||
|
id: number;
|
||||||
|
onHand: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const catalogCw = {
|
||||||
|
countItems: async (): Promise<number> => {
|
||||||
|
const response = await connectWiseApi.get("/procurement/catalog/count");
|
||||||
|
return response.data.count;
|
||||||
|
},
|
||||||
|
fetchAllCatalogSummary: async (): Promise<
|
||||||
|
Collection<number, CatalogSummary>
|
||||||
|
> => {
|
||||||
|
const allItems = new Collection<number, CatalogSummary>();
|
||||||
|
const pageSize = 1000;
|
||||||
|
|
||||||
|
const count = await catalogCw.countItems();
|
||||||
|
const totalPages = Math.ceil(count / pageSize);
|
||||||
|
|
||||||
|
for (let page = 0; page < totalPages; page++) {
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/procurement/catalog?page=${page + 1}&pageSize=${pageSize}&fields=id,_info`,
|
||||||
|
);
|
||||||
|
const items: CatalogSummary[] = response.data;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
allItems.set(item.id, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allItems;
|
||||||
|
},
|
||||||
|
fetchInventoryOnHand: async (cwCatalogId: number): Promise<number> => {
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/procurement/catalog/${cwCatalogId}/inventory?fields=onHand`,
|
||||||
|
);
|
||||||
|
const entries: InventoryEntry[] = response.data;
|
||||||
|
return entries.reduce((sum, e) => sum + (e.onHand || 0), 0);
|
||||||
|
},
|
||||||
|
fetchAllItemsFromCw: async (): Promise<Collection<number, CatalogItem>> => {
|
||||||
|
const allItems = new Collection<number, CatalogItem>();
|
||||||
|
const pageSize = 1000;
|
||||||
|
|
||||||
|
const count = await catalogCw.countItems();
|
||||||
|
const totalPages = Math.ceil(count / pageSize);
|
||||||
|
|
||||||
|
for (let page = 0; page < totalPages; page++) {
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/procurement/catalog?page=${page + 1}&pageSize=${pageSize}`,
|
||||||
|
);
|
||||||
|
const items: CatalogItem[] = response.data;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
allItems.set(item.id, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allItems;
|
||||||
|
},
|
||||||
|
fetch: async (id: string): Promise<CatalogItem> => {
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/procurement/catalog/items/${id}`,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
interface CWReference {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CWVendorReference {
|
||||||
|
id: number;
|
||||||
|
identifier: string;
|
||||||
|
name: string;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CWCustomField {
|
||||||
|
id: number;
|
||||||
|
caption: string;
|
||||||
|
type: string;
|
||||||
|
entryMethod: string;
|
||||||
|
numberOfDecimals: number;
|
||||||
|
value: unknown;
|
||||||
|
connectWiseId: string;
|
||||||
|
rowNum: number;
|
||||||
|
userDefinedFieldRecId: number;
|
||||||
|
podId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CatalogItem {
|
||||||
|
id: number;
|
||||||
|
identifier: string;
|
||||||
|
description: string;
|
||||||
|
inactiveFlag: boolean;
|
||||||
|
subcategory: CWReference;
|
||||||
|
type: CWReference;
|
||||||
|
productClass: string;
|
||||||
|
serializedFlag: boolean;
|
||||||
|
serializedCostFlag: boolean;
|
||||||
|
phaseProductFlag: boolean;
|
||||||
|
unitOfMeasure: CWReference;
|
||||||
|
minStockLevel: number;
|
||||||
|
price: number;
|
||||||
|
cost: number;
|
||||||
|
priceAttribute: string;
|
||||||
|
taxableFlag: boolean;
|
||||||
|
dropShipFlag: boolean;
|
||||||
|
specialOrderFlag: boolean;
|
||||||
|
customerDescription: string;
|
||||||
|
manufacturer: CWReference;
|
||||||
|
manufacturerPartNumber: string;
|
||||||
|
vendor: CWVendorReference;
|
||||||
|
vendorSku: string;
|
||||||
|
notes: string;
|
||||||
|
integrationXRef: string;
|
||||||
|
sla: CWReference;
|
||||||
|
entityType: CWReference;
|
||||||
|
recurringFlag: boolean;
|
||||||
|
recurringRevenue: number;
|
||||||
|
recurringCost: number;
|
||||||
|
recurringOneTimeFlag: boolean;
|
||||||
|
recurringBillCycle: CWReference;
|
||||||
|
recurringCycleType: string;
|
||||||
|
calculatedPriceFlag: boolean;
|
||||||
|
calculatedCostFlag: boolean;
|
||||||
|
category: CWReference;
|
||||||
|
calculatedPrice: number;
|
||||||
|
calculatedCost: number;
|
||||||
|
billableOption: string;
|
||||||
|
connectWiseID: string;
|
||||||
|
agreementType: CWReference;
|
||||||
|
markupPercentage: number;
|
||||||
|
markupFlag: boolean;
|
||||||
|
autoUpdateUnitCostFlag: boolean;
|
||||||
|
autoUpdateUnitPriceFlag: boolean;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
customFields?: CWCustomField[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { prisma } from "../../../constants";
|
||||||
|
import { events } from "../../globalEvents";
|
||||||
|
import { catalogCw } from "./catalog";
|
||||||
|
|
||||||
|
export const refreshCatalog = async () => {
|
||||||
|
events.emit("cw:catalog:refresh:check");
|
||||||
|
|
||||||
|
// 1. Fetch lightweight summaries from CW (id + _info with lastUpdated)
|
||||||
|
const cwSummaries = await catalogCw.fetchAllCatalogSummary();
|
||||||
|
|
||||||
|
// 2. Fetch all DB items with their cwCatalogId and cwLastUpdated
|
||||||
|
const dbItems = await prisma.catalogItem.findMany({
|
||||||
|
select: { cwCatalogId: true, cwLastUpdated: true },
|
||||||
|
});
|
||||||
|
const dbMap = new Map(
|
||||||
|
dbItems.map((item) => [item.cwCatalogId, item.cwLastUpdated]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Compare CW lastUpdated vs DB cwLastUpdated — collect IDs that are stale or new
|
||||||
|
const staleIds: number[] = [];
|
||||||
|
|
||||||
|
for (const [cwId, summary] of cwSummaries) {
|
||||||
|
const cwLastUpdated = summary._info?.lastUpdated
|
||||||
|
? new Date(summary._info.lastUpdated)
|
||||||
|
: null;
|
||||||
|
const dbLastUpdated = dbMap.get(cwId) ?? null;
|
||||||
|
|
||||||
|
// New item (not in DB) or CW has a newer timestamp
|
||||||
|
if (!dbLastUpdated || (cwLastUpdated && cwLastUpdated > dbLastUpdated)) {
|
||||||
|
staleIds.push(cwId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (staleIds.length === 0) {
|
||||||
|
events.emit("cw:catalog:refresh:skipped", {
|
||||||
|
totalCw: cwSummaries.size,
|
||||||
|
totalDb: dbItems.length,
|
||||||
|
staleCount: 0,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
events.emit("cw:catalog:refresh:started", {
|
||||||
|
totalCw: cwSummaries.size,
|
||||||
|
totalDb: dbItems.length,
|
||||||
|
staleCount: staleIds.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Fetch full catalog data, then filter to only stale items
|
||||||
|
const staleIdSet = new Set(staleIds);
|
||||||
|
const allCwItems = await catalogCw.fetchAllItemsFromCw();
|
||||||
|
const allStaleItems = new Map<number, any>();
|
||||||
|
|
||||||
|
for (const [id, item] of allCwItems) {
|
||||||
|
if (staleIdSet.has(id)) {
|
||||||
|
allStaleItems.set(id, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Batch fetch inventory onHand for stale items (50 concurrent)
|
||||||
|
const onHandMap = new Map<number, number>();
|
||||||
|
const batchSize = 50;
|
||||||
|
|
||||||
|
for (let i = 0; i < staleIds.length; i += batchSize) {
|
||||||
|
const batch = staleIds.slice(i, i + batchSize);
|
||||||
|
await Promise.all(
|
||||||
|
batch.map(async (cwId) => {
|
||||||
|
try {
|
||||||
|
const onHand = await catalogCw.fetchInventoryOnHand(cwId);
|
||||||
|
onHandMap.set(cwId, onHand);
|
||||||
|
} catch {
|
||||||
|
onHandMap.set(cwId, 0);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Upsert only the stale/new items
|
||||||
|
const updatedCount = (
|
||||||
|
await Promise.all(
|
||||||
|
staleIds.map(async (cwId) => {
|
||||||
|
const item = allStaleItems.get(cwId);
|
||||||
|
if (!item) return null;
|
||||||
|
|
||||||
|
const cwLastUpdated = item._info?.lastUpdated
|
||||||
|
? new Date(item._info.lastUpdated)
|
||||||
|
: new Date();
|
||||||
|
const onHand = onHandMap.get(cwId) ?? 0;
|
||||||
|
|
||||||
|
return await prisma.catalogItem.upsert({
|
||||||
|
where: { cwCatalogId: cwId },
|
||||||
|
create: {
|
||||||
|
cwCatalogId: cwId,
|
||||||
|
name: item.description,
|
||||||
|
description: item.description,
|
||||||
|
customerDescription: item.customerDescription,
|
||||||
|
internalNotes: item.notes,
|
||||||
|
manufacturer: item.manufacturer?.name,
|
||||||
|
manufactureCwId: item.manufacturer?.id,
|
||||||
|
partNumber: item.manufacturerPartNumber,
|
||||||
|
vendorName: item.vendor?.name,
|
||||||
|
vendorSku: item.vendorSku,
|
||||||
|
vendorCwId: item.vendor?.id,
|
||||||
|
price: item.price,
|
||||||
|
cost: item.cost,
|
||||||
|
inactive: item.inactiveFlag,
|
||||||
|
salesTaxable: item.taxableFlag,
|
||||||
|
onHand,
|
||||||
|
cwLastUpdated,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: item.description,
|
||||||
|
description: item.description,
|
||||||
|
customerDescription: item.customerDescription,
|
||||||
|
internalNotes: item.notes,
|
||||||
|
manufacturer: item.manufacturer?.name,
|
||||||
|
manufactureCwId: item.manufacturer?.id,
|
||||||
|
partNumber: item.manufacturerPartNumber,
|
||||||
|
vendorName: item.vendor?.name,
|
||||||
|
vendorSku: item.vendorSku,
|
||||||
|
vendorCwId: item.vendor?.id,
|
||||||
|
price: item.price,
|
||||||
|
cost: item.cost,
|
||||||
|
inactive: item.inactiveFlag,
|
||||||
|
salesTaxable: item.taxableFlag,
|
||||||
|
onHand,
|
||||||
|
cwLastUpdated,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
).filter(Boolean).length;
|
||||||
|
|
||||||
|
events.emit("cw:catalog:refresh:completed", {
|
||||||
|
totalCw: cwSummaries.size,
|
||||||
|
totalDb: dbItems.length,
|
||||||
|
staleCount: staleIds.length,
|
||||||
|
itemsUpdated: updatedCount,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { prisma } from "../../../constants";
|
||||||
|
import { events } from "../../globalEvents";
|
||||||
|
import { catalogCw } from "./catalog";
|
||||||
|
|
||||||
|
export const refreshInventory = async () => {
|
||||||
|
events.emit("cw:inventory:refresh:check");
|
||||||
|
|
||||||
|
// 1. Get all active catalog items from DB
|
||||||
|
const dbItems = await prisma.catalogItem.findMany({
|
||||||
|
where: { inactive: false },
|
||||||
|
select: { cwCatalogId: true, onHand: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbItems.length === 0) {
|
||||||
|
events.emit("cw:inventory:refresh:skipped", {
|
||||||
|
totalItems: 0,
|
||||||
|
updatedCount: 0,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
events.emit("cw:inventory:refresh:started", {
|
||||||
|
totalItems: dbItems.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Batch fetch inventory onHand for all items (50 concurrent)
|
||||||
|
const onHandMap = new Map<number, number>();
|
||||||
|
const batchSize = 150;
|
||||||
|
|
||||||
|
for (let i = 0; i < dbItems.length; i += batchSize) {
|
||||||
|
const batch = dbItems.slice(i, i + batchSize);
|
||||||
|
await Promise.all(
|
||||||
|
batch.map(async (item) => {
|
||||||
|
try {
|
||||||
|
const onHand = await catalogCw.fetchInventoryOnHand(item.cwCatalogId);
|
||||||
|
onHandMap.set(item.cwCatalogId, onHand);
|
||||||
|
} catch {
|
||||||
|
onHandMap.set(item.cwCatalogId, item.onHand);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Only update items where onHand has changed
|
||||||
|
const updates = dbItems.filter((item) => {
|
||||||
|
const newOnHand = onHandMap.get(item.cwCatalogId) ?? item.onHand;
|
||||||
|
return newOnHand !== item.onHand;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updates.length === 0) {
|
||||||
|
events.emit("cw:inventory:refresh:skipped", {
|
||||||
|
totalItems: dbItems.length,
|
||||||
|
updatedCount: 0,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedCount = (
|
||||||
|
await Promise.all(
|
||||||
|
updates.map(async (item) => {
|
||||||
|
const newOnHand = onHandMap.get(item.cwCatalogId) ?? item.onHand;
|
||||||
|
return await prisma.catalogItem.update({
|
||||||
|
where: { cwCatalogId: item.cwCatalogId },
|
||||||
|
data: { onHand: newOnHand },
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
).length;
|
||||||
|
|
||||||
|
events.emit("cw:inventory:refresh:completed", {
|
||||||
|
totalItems: dbItems.length,
|
||||||
|
updatedCount,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -108,6 +108,56 @@ interface EventTypes {
|
|||||||
company: CompanyController;
|
company: CompanyController;
|
||||||
updatedFields: Partial<Company>;
|
updatedFields: Partial<Company>;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
|
||||||
|
// ConnectWise Catalog Events
|
||||||
|
"cw:catalog:refresh:check": () => void;
|
||||||
|
"cw:catalog:refresh:started": (data: {
|
||||||
|
totalCw: number;
|
||||||
|
totalDb: number;
|
||||||
|
staleCount: number;
|
||||||
|
}) => void;
|
||||||
|
"cw:catalog:refresh:completed": (data: {
|
||||||
|
totalCw: number;
|
||||||
|
totalDb: number;
|
||||||
|
staleCount: number;
|
||||||
|
itemsUpdated: number;
|
||||||
|
}) => void;
|
||||||
|
"cw:catalog:refresh:skipped": (data: {
|
||||||
|
totalCw: number;
|
||||||
|
totalDb: number;
|
||||||
|
staleCount: number;
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
// UniFi Events
|
||||||
|
"unifi:login:ok": (data: {
|
||||||
|
type: "unifi-os" | "legacy";
|
||||||
|
status: number;
|
||||||
|
}) => void;
|
||||||
|
"unifi:login:fallback": () => void;
|
||||||
|
"unifi:reauth": () => void;
|
||||||
|
"unifi:sites:sync:started": () => void;
|
||||||
|
"unifi:sites:sync:completed": (data: {
|
||||||
|
total: number;
|
||||||
|
created: number;
|
||||||
|
updated: number;
|
||||||
|
}) => void;
|
||||||
|
"unifi:wlan:fetched": (data: { path: string }) => void;
|
||||||
|
"unifi:wlan:fetch_failed": (data: {
|
||||||
|
path: string;
|
||||||
|
status: number | unknown;
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
// ConnectWise Inventory Events
|
||||||
|
"cw:inventory:refresh:check": () => void;
|
||||||
|
"cw:inventory:refresh:started": (data: { totalItems: number }) => void;
|
||||||
|
"cw:inventory:refresh:completed": (data: {
|
||||||
|
totalItems: number;
|
||||||
|
updatedCount: number;
|
||||||
|
}) => void;
|
||||||
|
"cw:inventory:refresh:skipped": (data: {
|
||||||
|
totalItems: number;
|
||||||
|
updatedCount: number;
|
||||||
|
}) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const events = new Eventra<EventTypes>();
|
export const events = new Eventra<EventTypes>();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance } from "axios";
|
||||||
import https from "https";
|
import https from "https";
|
||||||
|
import { events } from "../globalEvents";
|
||||||
import {
|
import {
|
||||||
ApGroup,
|
ApGroup,
|
||||||
ApRadioWifiUsage,
|
ApRadioWifiUsage,
|
||||||
@@ -57,13 +58,13 @@ export class UnifiClient {
|
|||||||
try {
|
try {
|
||||||
// UniFi OS
|
// UniFi OS
|
||||||
const res = await this.client.post("/api/auth/login", body);
|
const res = await this.client.post("/api/auth/login", body);
|
||||||
console.log("Login OK (UniFi OS)", res.status);
|
events.emit("unifi:login:ok", { type: "unifi-os", status: res.status });
|
||||||
this.persistSession(res);
|
this.persistSession(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Legacy controller
|
// Legacy controller
|
||||||
console.log("UniFi OS login failed, trying legacy...");
|
events.emit("unifi:login:fallback");
|
||||||
const res = await this.client.post("/api/login", body);
|
const res = await this.client.post("/api/login", body);
|
||||||
console.log("Login OK (legacy)", res.status);
|
events.emit("unifi:login:ok", { type: "legacy", status: res.status });
|
||||||
this.persistSession(res);
|
this.persistSession(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,13 +79,13 @@ export class UnifiClient {
|
|||||||
try {
|
try {
|
||||||
const res = await this.client.get(path);
|
const res = await this.client.get(path);
|
||||||
const data = (res.data?.data ?? res.data) as WlanConfRaw[];
|
const data = (res.data?.data ?? res.data) as WlanConfRaw[];
|
||||||
console.log(`Fetched wlan from ${path}`);
|
events.emit("unifi:wlan:fetched", { path });
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(
|
events.emit("unifi:wlan:fetch_failed", {
|
||||||
`Failed ${path}:`,
|
path,
|
||||||
axios.isAxiosError(e) ? e.response?.status : e,
|
status: axios.isAxiosError(e) ? e.response?.status : e,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const connectWiseApi = axios.create({
|
||||||
|
baseURL: `https://ttscw.totaltech.net/v4_6_release/apis/3.0/`,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${process.env.CW_BASIC_TOKEN}`,
|
||||||
|
clientId: `${process.env.CW_CLIENT_ID}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// Fetch inactive catalog items
|
||||||
|
const pageSize = 1000;
|
||||||
|
let page = 1;
|
||||||
|
const inactiveItems: any[] = [];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/procurement/catalog?page=${page}&pageSize=${pageSize}&conditions=inactiveFlag=true&fields=id,identifier,description,_info`,
|
||||||
|
);
|
||||||
|
if (response.data.length === 0) break;
|
||||||
|
inactiveItems.push(...response.data);
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found ${inactiveItems.length} inactive catalog items`);
|
||||||
|
console.log(`Checking inventory for each (batches of 50)...\n`);
|
||||||
|
|
||||||
|
const withStock: any[] = [];
|
||||||
|
const batchSize = 50;
|
||||||
|
|
||||||
|
for (let i = 0; i < inactiveItems.length; i += batchSize) {
|
||||||
|
const batch = inactiveItems.slice(i, i + batchSize);
|
||||||
|
await Promise.all(
|
||||||
|
batch.map(async (item) => {
|
||||||
|
try {
|
||||||
|
const res = await connectWiseApi.get(
|
||||||
|
`/procurement/catalog/${item.id}/inventory?fields=onHand`,
|
||||||
|
);
|
||||||
|
const totalOnHand = (res.data as { onHand: number }[]).reduce(
|
||||||
|
(sum, e) => sum + (e.onHand || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
if (totalOnHand > 0) {
|
||||||
|
withStock.push({
|
||||||
|
id: item.id,
|
||||||
|
identifier: item.identifier,
|
||||||
|
description: item.description,
|
||||||
|
totalOnHand,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const done = Math.min(i + batchSize, inactiveItems.length);
|
||||||
|
if (done % 500 === 0 || done === inactiveItems.length) {
|
||||||
|
console.log(` ${done}/${inactiveItems.length} checked`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\nInactive items with inventory: ${withStock.length}/${inactiveItems.length}\n`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (withStock.length > 0) {
|
||||||
|
console.log(JSON.stringify(withStock, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Environment setup & latest features
|
// Environment setup & latest features
|
||||||
"lib": ["ESNext"],
|
"lib": ["ESNext"],
|
||||||
|
"types": ["bun"],
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "Preserve",
|
"module": "Preserve",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import keypair from "keypair";
|
||||||
|
import { mkdirSync } from "fs";
|
||||||
|
|
||||||
|
const outputDir = "production-keys";
|
||||||
|
|
||||||
|
console.log(`
|
||||||
|
Generating Production Keys
|
||||||
|
-----------------
|
||||||
|
This script will generate all RSA key pairs needed for the production deployment.
|
||||||
|
Output directory: ${outputDir}/
|
||||||
|
-----------------`);
|
||||||
|
|
||||||
|
// Ensure output directory exists
|
||||||
|
mkdirSync(outputDir, { recursive: true });
|
||||||
|
|
||||||
|
const keyFiles = ["accessToken", "refreshToken", "permissions", "secureValues"];
|
||||||
|
|
||||||
|
const generatedKeys: Record<string, { private: string; public: string }> = {};
|
||||||
|
|
||||||
|
for (const name of keyFiles) {
|
||||||
|
console.log(`Generating '${name}' key pair (4096-bit RSA)...`);
|
||||||
|
const keys = keypair({ bits: 4096 });
|
||||||
|
generatedKeys[name] = keys;
|
||||||
|
|
||||||
|
const privPath = `${outputDir}/${name}.key`;
|
||||||
|
const pubPath = `${outputDir}/${name}.pub`;
|
||||||
|
|
||||||
|
await Bun.write(privPath, keys.private);
|
||||||
|
await Bun.write(pubPath, keys.public);
|
||||||
|
|
||||||
|
console.log(` ✔ ${privPath}`);
|
||||||
|
console.log(` ✔ ${pubPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Kubernetes Secret YAML
|
||||||
|
const toBase64 = (str: string) => Buffer.from(str).toString("base64");
|
||||||
|
|
||||||
|
const secretYaml = `apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: ttscm-keys
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
accessToken.key: ${toBase64(generatedKeys["accessToken"].private)}
|
||||||
|
refreshToken.key: ${toBase64(generatedKeys["refreshToken"].private)}
|
||||||
|
permissions.key: ${toBase64(generatedKeys["permissions"].private)}
|
||||||
|
secureValues.key: ${toBase64(generatedKeys["secureValues"].private)}
|
||||||
|
secureValues.pub: ${toBase64(generatedKeys["secureValues"].public)}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const secretPath = `${outputDir}/ttscm-keys-secret.yaml`;
|
||||||
|
await Bun.write(secretPath, secretYaml);
|
||||||
|
console.log(`\n ✔ ${secretPath}`);
|
||||||
|
|
||||||
|
console.log(`
|
||||||
|
-----------------
|
||||||
|
All production keys and K8s Secret manifest generated in '${outputDir}/'.
|
||||||
|
|
||||||
|
⚠️ Delete the '${outputDir}/' directory after applying to your cluster.
|
||||||
|
Do NOT commit these keys to version control.
|
||||||
|
-----------------
|
||||||
|
`);
|
||||||
Reference in New Issue
Block a user