Lots of updates and cleaning up.
This commit is contained in:
@@ -23,8 +23,6 @@ export default createRoute("get", ["/redirect"], async (c) => {
|
||||
refreshToken: tokens.refreshToken,
|
||||
});
|
||||
|
||||
console.log("Emitted auth callback for key:", callbackKey);
|
||||
|
||||
// Close the window because duh
|
||||
return c.html(
|
||||
`<script>
|
||||
|
||||
@@ -8,8 +8,6 @@ export default createRoute("post", ["/refresh"], async (c) => {
|
||||
|
||||
const refreshToken = c.req.header("x-refresh-token") || "";
|
||||
|
||||
console.log("Received refresh token:", refreshToken);
|
||||
|
||||
const session = await sessions.fetch({
|
||||
refreshToken: refreshToken,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Hono } from "hono/tiny";
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { companies } from "../../../managers/companies";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* /v1/company/companies/[id]/configurations */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/companies/:identifier/configurations"],
|
||||
|
||||
async (c) => {
|
||||
const company = await companies.fetch(c.req.param("identifier"));
|
||||
const configurations = await company.fetchConfigurations();
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"Company Configurations Fetched Successfully!",
|
||||
configurations,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["company.fetch", "company.fetch.configurations"],
|
||||
}),
|
||||
);
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Hono } from "hono/tiny";
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { companies } from "../../managers/companies";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { companies } from "../../../managers/companies";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
/* /v1/company/[id] */
|
||||
/* /v1/company/companies/[id] */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/company/:identifier"],
|
||||
["/companies/:identifier"],
|
||||
|
||||
async (c) => {
|
||||
const company = await companies.fetch(c.req.param("identifier"));
|
||||
|
||||
@@ -12,9 +12,15 @@ export default createRoute(
|
||||
async (c) => {
|
||||
const page = new Number(c.req.query("page") ?? 1) as number;
|
||||
const rpp = new Number(c.req.query("rpp") ?? 30) as number; // Records Per Page
|
||||
const companyQty = await companies.count();
|
||||
const search = c.req.query("search") as string;
|
||||
|
||||
const data = await companies.fetchPages(page, rpp);
|
||||
const data = search
|
||||
? await companies.search(search, page, rpp)
|
||||
: await companies.fetchPages(page, rpp);
|
||||
|
||||
const companyQty = search
|
||||
? (await companies.search(search, 1, 999999)).length
|
||||
: await companies.count();
|
||||
|
||||
let response = apiResponse.successful(
|
||||
"Companies Fetched Successfully!",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { default as fetchAll } from "./fetchAll";
|
||||
import { default as fetch } from "./fetch";
|
||||
import { default as fetch } from "./[id]/fetch";
|
||||
import { default as configurations } from "./[id]/configurations";
|
||||
|
||||
export { fetch, fetchAll };
|
||||
export { configurations, fetch, fetchAll };
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ app.notFound((c) => {
|
||||
v1.route("/teapot", teapot);
|
||||
v1.route("/auth", require("./routers/authRouter").default);
|
||||
v1.route("/user", require("./routers/user").default);
|
||||
v1.route("/", require("./routers/companyRouter").default);
|
||||
v1.route("/company", require("./routers/companyRouter").default);
|
||||
app.route("/v1", v1);
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Company } from "../../generated/prisma/client";
|
||||
import { fetchCwCompanyById } from "../modules/cw-utils/fetchCompany";
|
||||
import { fetchCompanyConfigurations } from "../modules/cw-utils/fetchCompanyConfigurations";
|
||||
import { updateCwInternalCompany } from "../modules/cw-utils/updateCompany";
|
||||
import { Company as CWCompany } from "../types/ConnectWiseTypes";
|
||||
|
||||
/**
|
||||
* Company Controller
|
||||
@@ -36,6 +39,32 @@ export class CompanyController {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch ConnectWise Company Data
|
||||
*
|
||||
* This method retrieves the latest company data directly from ConnectWise
|
||||
* using the stored ConnectWise Company ID.
|
||||
*
|
||||
* @returns {Company}
|
||||
*/
|
||||
public async fetchCwData(): Promise<CWCompany | null> {
|
||||
const data = await fetchCwCompanyById(this.cw_CompanyId);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Company Configurations
|
||||
*
|
||||
* This method retrieves the configurations associated with
|
||||
* the company from ConnectWise.
|
||||
*
|
||||
* @returns {ProcessedConfiguration}
|
||||
*/
|
||||
public async fetchConfigurations() {
|
||||
const data = await fetchCompanyConfigurations(this.cw_CompanyId);
|
||||
return data;
|
||||
}
|
||||
|
||||
public toJson() {
|
||||
return {
|
||||
id: this.id,
|
||||
|
||||
@@ -138,12 +138,11 @@ export class RoleController {
|
||||
data: { permissions: newPermissionsToken },
|
||||
});
|
||||
|
||||
events.emit("role:permissions:updated", {
|
||||
events.emit("role:permissions:set", {
|
||||
current: permissions,
|
||||
currentSigned: newPermissionsToken,
|
||||
previous: previous.permissions,
|
||||
previousSigned: this._permissionsToken,
|
||||
action: "set",
|
||||
role: this,
|
||||
});
|
||||
|
||||
@@ -178,12 +177,11 @@ export class RoleController {
|
||||
data: { permissions: newPermissionsToken },
|
||||
});
|
||||
|
||||
events.emit("role:permissions:updated", {
|
||||
current: permissions,
|
||||
currentSigned: newPermissionsToken,
|
||||
events.emit("role:permissions:added", {
|
||||
previous: previous.permissions,
|
||||
previousSigned: this._permissionsToken,
|
||||
action: "added",
|
||||
added: permissions,
|
||||
currentSigned: newPermissionsToken,
|
||||
role: this,
|
||||
});
|
||||
|
||||
@@ -218,12 +216,11 @@ export class RoleController {
|
||||
data: { permissions: newPermissionsToken },
|
||||
});
|
||||
|
||||
events.emit("role:permissions:updated", {
|
||||
current: permissions,
|
||||
currentSigned: newPermissionsToken,
|
||||
events.emit("role:permissions:removed", {
|
||||
previous: previous.permissions,
|
||||
previousSigned: this._permissionsToken,
|
||||
action: "removed",
|
||||
removed: permissions,
|
||||
currentSigned: newPermissionsToken,
|
||||
role: this,
|
||||
});
|
||||
|
||||
|
||||
@@ -156,8 +156,6 @@ export default class UserController {
|
||||
)
|
||||
: [];
|
||||
|
||||
// console.log(implicitPermissions);
|
||||
|
||||
let checks = [
|
||||
(await this.fetchRoles()).map((v) => v.checkPermission(permission)),
|
||||
].flatMap((v) => v);
|
||||
|
||||
@@ -40,4 +40,35 @@ export const companies = {
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Search Companies
|
||||
*
|
||||
* Search companies by identifier, name, or id with pagination support.
|
||||
*
|
||||
* @param query - Search query string
|
||||
* @param page - Page number
|
||||
* @param rpp - Records Per Page
|
||||
*/
|
||||
async search(query: string, page: number, rpp: number) {
|
||||
page = page.valueOf();
|
||||
rpp = rpp.valueOf();
|
||||
|
||||
const skip = (page > 1 ? page : 0) * rpp;
|
||||
const take = rpp ?? 30;
|
||||
|
||||
const data = prisma.company.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ cw_Identifier: { contains: query, mode: "insensitive" } },
|
||||
{ name: { contains: query, mode: "insensitive" } },
|
||||
{ id: { contains: query, mode: "insensitive" } },
|
||||
],
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { connectWiseApi } from "../../constants";
|
||||
import { ConfigurationResponse } from "../../types/ConnectWiseTypes";
|
||||
import {
|
||||
processConfigurationResponse,
|
||||
ProcessedConfiguration,
|
||||
} from "./processConfigurationResponse";
|
||||
import GenericError from "../../Errors/GenericError";
|
||||
|
||||
export const fetchCompanyConfigurations = async (
|
||||
cwCompanyId: number,
|
||||
): Promise<ProcessedConfiguration> => {
|
||||
try {
|
||||
const response = await connectWiseApi.get(
|
||||
`/company/configurations?conditions=company/id=${cwCompanyId}`,
|
||||
);
|
||||
return processConfigurationResponse(response.data);
|
||||
} catch (error) {
|
||||
const errBody = (error as any).response?.data || error;
|
||||
console.error(
|
||||
`Error fetching configurations for company ID ${cwCompanyId}:`,
|
||||
errBody,
|
||||
);
|
||||
throw new GenericError({
|
||||
name: "FetchCompanyConfigurationsError",
|
||||
message: `Failed to fetch configurations for company ${cwCompanyId}`,
|
||||
cause: typeof errBody === "string" ? errBody : JSON.stringify(errBody),
|
||||
status: 502,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ConfigurationResponse } from "../../types/ConnectWiseTypes";
|
||||
|
||||
export type ProcessedConfiguration = ReturnType<
|
||||
typeof processConfigurationResponse
|
||||
>;
|
||||
|
||||
export const processConfigurationResponse = (c: ConfigurationResponse) => {
|
||||
return c.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
active: item.activeFlag,
|
||||
serialNumber: item.serialNumber,
|
||||
type: item.type,
|
||||
questions: item.questions.map((q) => ({
|
||||
id: q.questionId,
|
||||
question: q.question,
|
||||
answer: q.answer,
|
||||
fieldType: q.fieldType,
|
||||
})),
|
||||
info: item._info,
|
||||
}));
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import { events } from "../globalEvents";
|
||||
import { fetchAllCwCompanies } from "./fetchAllCompanies";
|
||||
|
||||
export const refreshCompanies = async () => {
|
||||
events.emit("cw:companies:refreshed:check");
|
||||
events.emit("cw:companies:refresh:check");
|
||||
|
||||
// Dynamically import to avoid circular dependency
|
||||
const internalCompanyCount = await prisma.company.count();
|
||||
@@ -11,28 +11,35 @@ export const refreshCompanies = async () => {
|
||||
(await connectWiseApi.get("/company/companies/count")).data.count - 1;
|
||||
|
||||
if (internalCompanyCount !== externalCompanyCount) {
|
||||
console.log(
|
||||
`Company count mismatch detected. Internal: ${internalCompanyCount}, External: ${externalCompanyCount}. Refreshing...`,
|
||||
);
|
||||
events.emit("cw:companies:refresh:started");
|
||||
|
||||
const allCompanies = await fetchAllCwCompanies();
|
||||
|
||||
await Promise.all(
|
||||
allCompanies.map(async (company) => {
|
||||
return await prisma.company.upsert({
|
||||
where: { cw_CompanyId: company.id },
|
||||
create: {
|
||||
cw_CompanyId: company.id,
|
||||
cw_Identifier: company.identifier,
|
||||
name: company.name,
|
||||
},
|
||||
update: {
|
||||
name: company.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
events.emit("cw:companies:refreshed", {
|
||||
const updatedCount = (
|
||||
await Promise.all(
|
||||
allCompanies.map(async (company) => {
|
||||
return await prisma.company.upsert({
|
||||
where: { cw_CompanyId: company.id },
|
||||
create: {
|
||||
cw_CompanyId: company.id,
|
||||
cw_Identifier: company.identifier,
|
||||
name: company.name,
|
||||
},
|
||||
update: {
|
||||
name: company.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
)
|
||||
).length;
|
||||
|
||||
events.emit("cw:companies:refresh:completed", {
|
||||
internalCompaniesCount: internalCompanyCount,
|
||||
externalCompaniesCount: externalCompanyCount,
|
||||
companiesUpdated: updatedCount,
|
||||
});
|
||||
} else {
|
||||
events.emit("cw:companies:refresh:skipped", {
|
||||
internalCompaniesCount: internalCompanyCount,
|
||||
externalCompaniesCount: externalCompanyCount,
|
||||
});
|
||||
|
||||
@@ -5,11 +5,15 @@ import {
|
||||
SessionTokensObject,
|
||||
} from "../controllers/SessionController";
|
||||
import { RoleController } from "../controllers/RoleController";
|
||||
import { CompanyController } from "../controllers/CompanyController";
|
||||
import { JsonWebTokenError } from "jsonwebtoken";
|
||||
import { User } from "../../generated/prisma/client";
|
||||
import { User, Company } from "../../generated/prisma/client";
|
||||
|
||||
interface EventTypes {
|
||||
// API Lifecycle
|
||||
"api:started": () => void;
|
||||
|
||||
// User Events
|
||||
"user:created": (user: UserController) => void;
|
||||
"user:updated": (data: {
|
||||
user: UserController;
|
||||
@@ -19,6 +23,16 @@ interface EventTypes {
|
||||
user: UserController;
|
||||
tokens: SessionTokensObject;
|
||||
}) => void;
|
||||
"user:role:assigned": (data: {
|
||||
user: UserController;
|
||||
role: RoleController;
|
||||
}) => void;
|
||||
"user:role:removed": (data: {
|
||||
user: UserController;
|
||||
role: RoleController;
|
||||
}) => void;
|
||||
|
||||
// Session Events
|
||||
"session:created": (data: {
|
||||
user: UserController;
|
||||
session: SessionController;
|
||||
@@ -33,18 +47,33 @@ interface EventTypes {
|
||||
}) => void;
|
||||
"session:invalidated": (session: SessionController) => void;
|
||||
"session:terminated": (session: SessionController) => void;
|
||||
|
||||
// Role Events
|
||||
"role:created": (role: RoleController) => void;
|
||||
"role:deleted": (role: RoleController) => void;
|
||||
"role:updated": (data: {
|
||||
role: RoleController;
|
||||
updateData: Parameters<typeof RoleController.prototype.update>["0"];
|
||||
}) => void;
|
||||
"role:permissions:updated": (data: {
|
||||
"role:permissions:set": (data: {
|
||||
previous: string[];
|
||||
previousSigned: string;
|
||||
current: string[];
|
||||
currentSigned: string;
|
||||
action: "set" | "added" | "removed";
|
||||
role: RoleController;
|
||||
}) => void;
|
||||
"role:permissions:added": (data: {
|
||||
previous: string[];
|
||||
previousSigned: string;
|
||||
added: string[];
|
||||
currentSigned: string;
|
||||
role: RoleController;
|
||||
}) => void;
|
||||
"role:permissions:removed": (data: {
|
||||
previous: string[];
|
||||
previousSigned: string;
|
||||
removed: string[];
|
||||
currentSigned: string;
|
||||
role: RoleController;
|
||||
}) => void;
|
||||
"role:permissions:verification_error": (data: {
|
||||
@@ -53,10 +82,30 @@ interface EventTypes {
|
||||
err: Error;
|
||||
role: RoleController;
|
||||
}) => void;
|
||||
"cw:companies:refreshed:check": () => void;
|
||||
"cw:companies:refreshed": (data: {
|
||||
|
||||
// Company Events
|
||||
"company:fetched": (company: CompanyController) => void;
|
||||
"company:refreshed_from_cw": (company: CompanyController) => void;
|
||||
"company:configurations_fetched": (data: {
|
||||
company: CompanyController;
|
||||
configurationCount: number;
|
||||
}) => void;
|
||||
|
||||
// ConnectWise Integration Events
|
||||
"cw:companies:refresh:check": () => void;
|
||||
"cw:companies:refresh:started": () => void;
|
||||
"cw:companies:refresh:completed": (data: {
|
||||
internalCompaniesCount: number;
|
||||
externalCompaniesCount: number;
|
||||
companiesUpdated: number;
|
||||
}) => void;
|
||||
"cw:companies:refresh:skipped": (data: {
|
||||
internalCompaniesCount: number;
|
||||
externalCompaniesCount: number;
|
||||
}) => void;
|
||||
"cw:company:data:updated": (data: {
|
||||
company: CompanyController;
|
||||
updatedFields: Partial<Company>;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,3 +137,169 @@ export interface CustomField {
|
||||
userDefinedFieldRecId: number;
|
||||
podId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* CONFIGURATION INTERFACES
|
||||
*
|
||||
*/
|
||||
|
||||
export interface ConfigurationItem {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
type: ConfigurationTypeRef;
|
||||
status: ConfigurationStatusRef;
|
||||
|
||||
company: CompanyRef;
|
||||
contact: ContactRef;
|
||||
site: SiteRef;
|
||||
|
||||
locationId: number;
|
||||
location: LocationRef;
|
||||
|
||||
businessUnitId: number;
|
||||
department: DepartmentRef;
|
||||
|
||||
deviceIdentifier: string;
|
||||
serialNumber: string;
|
||||
modelNumber: string;
|
||||
tagNumber: string;
|
||||
|
||||
installationDate: string; // ISO-8601
|
||||
warrantyExpirationDate?: string; // ISO-8601 (only present on some items)
|
||||
|
||||
installedBy?: MemberRef; // only present on some items
|
||||
|
||||
vendorNotes: string;
|
||||
notes: string;
|
||||
|
||||
macAddress: string;
|
||||
lastLoginName: string;
|
||||
|
||||
billFlag: boolean;
|
||||
|
||||
backupSuccesses: number;
|
||||
backupIncomplete: number;
|
||||
backupFailed: number;
|
||||
backupRestores: number;
|
||||
backupServerName: string;
|
||||
backupBillableSpaceGb: number;
|
||||
backupProtectedDeviceList: string;
|
||||
backupYear: number;
|
||||
backupMonth: number;
|
||||
|
||||
ipAddress: string;
|
||||
defaultGateway: string;
|
||||
|
||||
osType: string;
|
||||
osInfo: string;
|
||||
cpuSpeed: string;
|
||||
ram: string;
|
||||
localHardDrives: string;
|
||||
|
||||
questions: ConfigurationQuestion[];
|
||||
|
||||
activeFlag: boolean;
|
||||
mobileGuid: string;
|
||||
|
||||
companyLocationId: number;
|
||||
showRemoteFlag: boolean;
|
||||
showAutomateFlag: boolean;
|
||||
needsRenewalFlag: boolean;
|
||||
|
||||
manufacturerPartNumber?: string; // only present on some items
|
||||
|
||||
_info: ConfigurationInfo;
|
||||
}
|
||||
|
||||
export interface ConfigurationTypeRef {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
type_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ConfigurationStatusRef {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
status_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CompanyRef {
|
||||
id: number;
|
||||
identifier: string;
|
||||
name: string;
|
||||
_info: {
|
||||
company_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContactRef {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
contact_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SiteRef {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
site_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LocationRef {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
location_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DepartmentRef {
|
||||
id: number;
|
||||
identifier: string;
|
||||
name: string;
|
||||
_info: {
|
||||
department_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MemberRef {
|
||||
id: number;
|
||||
identifier: string;
|
||||
name: string;
|
||||
_info: {
|
||||
member_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ConfigurationQuestion {
|
||||
answerId: number;
|
||||
questionId: number;
|
||||
question: string;
|
||||
|
||||
answer?: string | boolean; // absent on some, boolean for Checkbox in your sample
|
||||
|
||||
sequenceNumber: number;
|
||||
numberOfDecimals: number;
|
||||
|
||||
fieldType: "Text" | "TextArea" | "Password" | "Date" | "Checkbox" | string;
|
||||
requiredFlag: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigurationInfo {
|
||||
lastUpdated: string; // ISO-8601
|
||||
updatedBy: string;
|
||||
dateEntered: string; // ISO-8601
|
||||
enteredBy: string;
|
||||
}
|
||||
|
||||
// Your payload is an array:
|
||||
export type ConfigurationResponse = ConfigurationItem[];
|
||||
|
||||
Reference in New Issue
Block a user