mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-27 10:39:53 +00:00
Compare commits
6 Commits
exit-node-
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81ed391efb | ||
|
|
f3bee70c23 | ||
|
|
15a9eb28d9 | ||
|
|
a0a093ed0b | ||
|
|
9cec711427 | ||
|
|
82745c701a |
@@ -255,23 +255,6 @@
|
||||
"resourceGoTo": "Go to Resource",
|
||||
"resourceDelete": "Delete Resource",
|
||||
"resourceDeleteConfirm": "Confirm Delete Resource",
|
||||
"labelDelete": "Delete Label",
|
||||
"labelAdd": "Add Label",
|
||||
"labelCreateSuccessMessage": "Label Created Successfully",
|
||||
"labelEditSuccessMessage": "Label Modified Successfully",
|
||||
"labelNameField": "Label Name",
|
||||
"labelColorField": "Label Color",
|
||||
"labelPlaceholder": "Ex: homelab",
|
||||
"labelCreate": "Create Label",
|
||||
"createLabelDialogTitle": "Create Label",
|
||||
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
|
||||
"labelEdit": "Edit Label",
|
||||
"editLabelDialogTitle": "Update Label",
|
||||
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
|
||||
"labelDeleteConfirm": "Confirm Delete Label",
|
||||
"labelErrorDelete": "Failed to delete label",
|
||||
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
|
||||
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
|
||||
"visibility": "Visibility",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
@@ -1157,15 +1140,6 @@
|
||||
"idpErrorConnectingTo": "There was a problem connecting to {name}. Please contact your administrator.",
|
||||
"idpErrorNotFound": "IdP not found",
|
||||
"inviteInvalid": "Invalid Invite",
|
||||
"labels": "Labels",
|
||||
"orgLabelsDescription": "Manage labels in this organization.",
|
||||
"addLabels": "Add labels",
|
||||
"siteLabelsTab": "Labels",
|
||||
"siteLabelsDescription": "Manage labels associated with this site.",
|
||||
"labelsNotFound": "Labels not found",
|
||||
"labelSearch": "Search labels",
|
||||
"selectColor": "Select color",
|
||||
"createNewLabel": "Create new org label \"{label}\"",
|
||||
"inviteInvalidDescription": "The invite link is invalid.",
|
||||
"inviteErrorWrongUser": "Invite is not for this user",
|
||||
"inviteErrorUserNotExists": "User does not exist. Please create an account first.",
|
||||
|
||||
@@ -148,12 +148,6 @@ export enum ActionsEnum {
|
||||
updateAlertRule = "updateAlertRule",
|
||||
deleteAlertRule = "deleteAlertRule",
|
||||
listAlertRules = "listAlertRules",
|
||||
listOrgLabels = "listOrgLabels",
|
||||
createOrgLabel = "createOrgLabel",
|
||||
updateOrgLabel = "updateOrgLabel",
|
||||
deleteOrgLabel = "deleteOrgLabel",
|
||||
attachLabelToItem = "attachLabelToItem",
|
||||
detachLabelFromItem = "detachLabelFromItem",
|
||||
getAlertRule = "getAlertRule",
|
||||
createHealthCheck = "createHealthCheck",
|
||||
updateHealthCheck = "updateHealthCheck",
|
||||
|
||||
@@ -162,89 +162,6 @@ export const resources = pgTable("resources", {
|
||||
wildcard: boolean("wildcard").notNull().default(false)
|
||||
});
|
||||
|
||||
export const labels = pgTable("labels", {
|
||||
labelId: serial("labelId").primaryKey(),
|
||||
name: varchar("name").notNull(),
|
||||
color: varchar("color").notNull(),
|
||||
orgId: varchar("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
});
|
||||
|
||||
export const siteLabels = pgTable(
|
||||
"siteLabels",
|
||||
{
|
||||
siteLabelId: serial("siteLabelId").primaryKey(),
|
||||
siteId: integer("siteId")
|
||||
.references(() => sites.siteId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
labelId: integer("labelId")
|
||||
.references(() => labels.labelId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
},
|
||||
(t) => [unique("site_label_uniq").on(t.siteId, t.labelId)]
|
||||
);
|
||||
|
||||
export const resourceLabels = pgTable(
|
||||
"resourceLabels",
|
||||
{
|
||||
resourceLabelId: serial("resourceLabelId").primaryKey(),
|
||||
resourceId: integer("resourceId")
|
||||
.references(() => resources.resourceId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
labelId: integer("labelId")
|
||||
.references(() => labels.labelId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
},
|
||||
(t) => [unique("resource_label_uniq").on(t.resourceId, t.labelId)]
|
||||
);
|
||||
|
||||
export const siteResourceLabels = pgTable(
|
||||
"siteResourceLabels",
|
||||
{
|
||||
siteResourceLabelId: serial("siteResourceLabelId").primaryKey(),
|
||||
siteResourceId: integer("siteResourceId")
|
||||
.references(() => siteResources.siteResourceId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
labelId: integer("labelId")
|
||||
.references(() => labels.labelId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
},
|
||||
(t) => [unique("site_resource_label_uniq").on(t.siteResourceId, t.labelId)]
|
||||
);
|
||||
|
||||
export const clientLabels = pgTable(
|
||||
"clientLabels",
|
||||
{
|
||||
clientLabelId: serial("clientLabelId").primaryKey(),
|
||||
clientId: integer("clientId")
|
||||
.references(() => clients.clientId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
labelId: integer("labelId")
|
||||
.references(() => labels.labelId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
},
|
||||
(t) => [unique("client_label_uniq").on(t.clientId, t.labelId)]
|
||||
);
|
||||
|
||||
export const targets = pgTable("targets", {
|
||||
targetId: serial("targetId").primaryKey(),
|
||||
resourceId: integer("resourceId")
|
||||
@@ -279,11 +196,9 @@ export const targetHealthCheck = pgTable("targetHealthCheck", {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
siteId: integer("siteId")
|
||||
.references(() => sites.siteId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
siteId: integer("siteId").references(() => sites.siteId, {
|
||||
onDelete: "cascade"
|
||||
}).notNull(),
|
||||
name: varchar("name"),
|
||||
hcEnabled: boolean("hcEnabled").notNull().default(false),
|
||||
hcPath: varchar("hcPath"),
|
||||
@@ -1182,30 +1097,19 @@ export const roundTripMessageTracker = pgTable("roundTripMessageTracker", {
|
||||
complete: boolean("complete").notNull().default(false)
|
||||
});
|
||||
|
||||
export const statusHistory = pgTable(
|
||||
"statusHistory",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
entityType: varchar("entityType").notNull(),
|
||||
entityId: integer("entityId").notNull(),
|
||||
orgId: varchar("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
status: varchar("status").notNull(),
|
||||
timestamp: integer("timestamp").notNull()
|
||||
},
|
||||
(table) => [
|
||||
index("idx_statusHistory_entity").on(
|
||||
table.entityType,
|
||||
table.entityId,
|
||||
table.timestamp
|
||||
),
|
||||
index("idx_statusHistory_org_timestamp").on(
|
||||
table.orgId,
|
||||
table.timestamp
|
||||
)
|
||||
]
|
||||
);
|
||||
export const statusHistory = pgTable("statusHistory", {
|
||||
id: serial("id").primaryKey(),
|
||||
entityType: varchar("entityType").notNull(),
|
||||
entityId: integer("entityId").notNull(),
|
||||
orgId: varchar("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
status: varchar("status").notNull(),
|
||||
timestamp: integer("timestamp").notNull(),
|
||||
}, (table) => [
|
||||
index("idx_statusHistory_entity").on(table.entityType, table.entityId, table.timestamp),
|
||||
index("idx_statusHistory_org_timestamp").on(table.orgId, table.timestamp),
|
||||
]);
|
||||
|
||||
export type Org = InferSelectModel<typeof orgs>;
|
||||
export type User = InferSelectModel<typeof users>;
|
||||
@@ -1275,4 +1179,3 @@ export type RoundTripMessageTracker = InferSelectModel<
|
||||
>;
|
||||
export type Network = InferSelectModel<typeof networks>;
|
||||
export type StatusHistory = InferSelectModel<typeof statusHistory>;
|
||||
export type Label = InferSelectModel<typeof labels>;
|
||||
|
||||
@@ -183,95 +183,6 @@ export const resources = sqliteTable("resources", {
|
||||
wildcard: integer("wildcard", { mode: "boolean" }).notNull().default(false)
|
||||
});
|
||||
|
||||
export const labels = sqliteTable("labels", {
|
||||
labelId: integer("labelId").primaryKey({ autoIncrement: true }),
|
||||
name: text("name").notNull(),
|
||||
color: text("color").notNull(),
|
||||
orgId: text("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
});
|
||||
|
||||
export const siteLabels = sqliteTable(
|
||||
"siteLabels",
|
||||
{
|
||||
siteLabelId: integer("siteLabelId").primaryKey({ autoIncrement: true }),
|
||||
siteId: integer("siteId")
|
||||
.references(() => sites.siteId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
labelId: integer("labelId")
|
||||
.references(() => labels.labelId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
},
|
||||
(t) => [unique("site_label_uniq").on(t.siteId, t.labelId)]
|
||||
);
|
||||
|
||||
export const resourceLabels = sqliteTable(
|
||||
"resourceLabels",
|
||||
{
|
||||
resourceLabelId: integer("resourceLabelId").primaryKey({
|
||||
autoIncrement: true
|
||||
}),
|
||||
resourceId: integer("resourceId")
|
||||
.references(() => resources.resourceId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
labelId: integer("labelId")
|
||||
.references(() => labels.labelId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
},
|
||||
(t) => [unique("resource_label_uniq").on(t.resourceId, t.labelId)]
|
||||
);
|
||||
|
||||
export const siteResourceLabels = sqliteTable(
|
||||
"siteResourceLabels",
|
||||
{
|
||||
siteResourceLabelId: integer("siteResourceLabelId").primaryKey({
|
||||
autoIncrement: true
|
||||
}),
|
||||
siteResourceId: integer("siteResourceId")
|
||||
.references(() => siteResources.siteResourceId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
labelId: integer("labelId")
|
||||
.references(() => labels.labelId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
},
|
||||
(t) => [unique("site_resource_label_uniq").on(t.siteResourceId, t.labelId)]
|
||||
);
|
||||
|
||||
export const clientLabels = sqliteTable(
|
||||
"clientLabels",
|
||||
{
|
||||
clientLabelId: integer("clientLabelId").primaryKey({
|
||||
autoIncrement: true
|
||||
}),
|
||||
clientId: integer("clientId")
|
||||
.references(() => clients.clientId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
labelId: integer("labelId")
|
||||
.references(() => labels.labelId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
},
|
||||
(t) => [unique("client_label_uniq").on(t.clientId, t.labelId)]
|
||||
);
|
||||
|
||||
export const targets = sqliteTable("targets", {
|
||||
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
|
||||
resourceId: integer("resourceId")
|
||||
@@ -308,11 +219,9 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
siteId: integer("siteId")
|
||||
.references(() => sites.siteId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
siteId: integer("siteId").references(() => sites.siteId, {
|
||||
onDelete: "cascade"
|
||||
}).notNull(),
|
||||
name: text("name"),
|
||||
hcEnabled: integer("hcEnabled", { mode: "boolean" })
|
||||
.notNull()
|
||||
@@ -1287,30 +1196,19 @@ export const roundTripMessageTracker = sqliteTable("roundTripMessageTracker", {
|
||||
complete: integer("complete", { mode: "boolean" }).notNull().default(false)
|
||||
});
|
||||
|
||||
export const statusHistory = sqliteTable(
|
||||
"statusHistory",
|
||||
{
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
entityType: text("entityType").notNull(), // "site" | "healthCheck"
|
||||
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
|
||||
orgId: text("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
|
||||
timestamp: integer("timestamp").notNull() // unix epoch seconds
|
||||
},
|
||||
(table) => [
|
||||
index("idx_statusHistory_entity").on(
|
||||
table.entityType,
|
||||
table.entityId,
|
||||
table.timestamp
|
||||
),
|
||||
index("idx_statusHistory_org_timestamp").on(
|
||||
table.orgId,
|
||||
table.timestamp
|
||||
)
|
||||
]
|
||||
);
|
||||
export const statusHistory = sqliteTable("statusHistory", {
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
entityType: text("entityType").notNull(), // "site" | "healthCheck"
|
||||
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
|
||||
orgId: text("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
|
||||
timestamp: integer("timestamp").notNull(), // unix epoch seconds
|
||||
}, (table) => [
|
||||
index("idx_statusHistory_entity").on(table.entityType, table.entityId, table.timestamp),
|
||||
index("idx_statusHistory_org_timestamp").on(table.orgId, table.timestamp),
|
||||
]);
|
||||
|
||||
export type Org = InferSelectModel<typeof orgs>;
|
||||
export type User = InferSelectModel<typeof users>;
|
||||
@@ -1380,4 +1278,3 @@ export type RoundTripMessageTracker = InferSelectModel<
|
||||
typeof roundTripMessageTracker
|
||||
>;
|
||||
export type StatusHistory = InferSelectModel<typeof statusHistory>;
|
||||
export type Label = InferSelectModel<typeof labels>;
|
||||
|
||||
@@ -152,11 +152,17 @@ function getOpenApiDocumentation() {
|
||||
|
||||
if (!hasExistingResponses) {
|
||||
def.route.responses = {
|
||||
"*": {
|
||||
description: "",
|
||||
"200": {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({})
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,10 @@ export enum TierFeature {
|
||||
DomainNamespaces = "domainNamespaces", // handle downgrade by removing custom domain namespaces
|
||||
StandaloneHealthChecks = "standaloneHealthChecks",
|
||||
AlertingRules = "alertingRules",
|
||||
WildcardSubdomain = "wildcardSubdomain",
|
||||
Labels = "labels"
|
||||
WildcardSubdomain = "wildcardSubdomain"
|
||||
}
|
||||
|
||||
export const tierMatrix: Record<TierFeature, Tier[]> = {
|
||||
[TierFeature.Labels]: ["tier2", "tier3", "enterprise"],
|
||||
[TierFeature.OrgOidc]: ["tier1", "tier2", "tier3", "enterprise"],
|
||||
[TierFeature.LoginPageDomain]: ["tier1", "tier2", "tier3", "enterprise"],
|
||||
[TierFeature.DeviceApprovals]: ["tier1", "tier3", "enterprise"],
|
||||
|
||||
@@ -873,7 +873,13 @@ export const portRangeStringSchema = z
|
||||
message:
|
||||
'Port range must be "*" for all ports, or a comma-separated list of ports and ranges (e.g., "80,443,8000-9000"). Ports must be between 1 and 65535, and ranges must have start <= end.'
|
||||
}
|
||||
);
|
||||
)
|
||||
.openapi({
|
||||
type: "string",
|
||||
description:
|
||||
'Port range string. Use "*" for all ports, a comma-separated list of ports, or ranges (e.g., "80,443,8000-9000"). Ports must be between 1 and 65535.',
|
||||
example: "80,443,8000-9000"
|
||||
});
|
||||
|
||||
/**
|
||||
* Parses a port range string into an array of port range objects
|
||||
|
||||
11
server/lib/openapi/createApiResponseSchema.ts
Normal file
11
server/lib/openapi/createApiResponseSchema.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export function createApiResponseSchema<T extends z.ZodTypeAny>(dataSchema: T) {
|
||||
return z.object({
|
||||
data: dataSchema.nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
});
|
||||
}
|
||||
@@ -24,8 +24,7 @@ import { LogStreamingManager } from "./LogStreamingManager";
|
||||
*/
|
||||
export const logStreamingManager = new LogStreamingManager();
|
||||
|
||||
if (build !== "saas") {
|
||||
// this is handled separately in the saas build, so we don't want to start it here
|
||||
if (build != "saas") { // this is handled separately in the saas build, so we don't want to start it here
|
||||
logStreamingManager.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export function verifyValidSubscription(tiers: Tier[]) {
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
if (build !== "saas") {
|
||||
if (build != "saas") {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,22 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function createAlertRule(
|
||||
|
||||
@@ -38,7 +38,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function deleteAlertRule(
|
||||
|
||||
@@ -49,7 +49,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function getAlertRule(
|
||||
|
||||
@@ -95,7 +95,22 @@ registry.registerPath({
|
||||
query: querySchema,
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listAlertRules(
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db } from "@server/db";
|
||||
import {
|
||||
alertRules,
|
||||
@@ -148,6 +149,10 @@ const bodySchema = z
|
||||
export type UpdateAlertRuleResponse = {
|
||||
alertRuleId: number;
|
||||
};
|
||||
const UpdateAlertRuleResponseDataSchema = z.object({
|
||||
alertRuleId: z.number()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
@@ -164,7 +169,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(UpdateAlertRuleResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function updateAlertRule(
|
||||
|
||||
@@ -24,7 +24,7 @@ import type { NextFunction, Request, Response } from "express";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string(),
|
||||
approvalId: z.string().transform(Number).pipe(z.int().positive())
|
||||
approvalId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
const bodySchema = z.strictObject({
|
||||
|
||||
@@ -18,6 +18,7 @@ import { OpenAPITags } from "@server/openApi";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { z } from "zod";
|
||||
import logger from "@server/logger";
|
||||
import {
|
||||
queryAccessAuditLogsParams,
|
||||
@@ -37,7 +38,22 @@ registry.registerPath({
|
||||
query: queryAccessAuditLogsQuery,
|
||||
params: queryAccessAuditLogsParams
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function exportAccessAuditLogs(
|
||||
|
||||
@@ -18,6 +18,7 @@ import { OpenAPITags } from "@server/openApi";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { z } from "zod";
|
||||
import logger from "@server/logger";
|
||||
import {
|
||||
queryActionAuditLogsParams,
|
||||
@@ -37,7 +38,22 @@ registry.registerPath({
|
||||
query: queryActionAuditLogsQuery,
|
||||
params: queryActionAuditLogsParams
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function exportActionAuditLogs(
|
||||
|
||||
@@ -18,6 +18,7 @@ import { OpenAPITags } from "@server/openApi";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { z } from "zod";
|
||||
import logger from "@server/logger";
|
||||
import {
|
||||
queryConnectionAuditLogsParams,
|
||||
@@ -37,7 +38,22 @@ registry.registerPath({
|
||||
query: queryConnectionAuditLogsQuery,
|
||||
params: queryConnectionAuditLogsParams
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function exportConnectionAuditLogs(
|
||||
|
||||
@@ -324,7 +324,22 @@ registry.registerPath({
|
||||
query: queryAccessAuditLogsQuery,
|
||||
params: queryAccessAuditLogsParams
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function queryAccessAuditLogs(
|
||||
|
||||
@@ -165,7 +165,22 @@ registry.registerPath({
|
||||
query: queryActionAuditLogsQuery,
|
||||
params: queryActionAuditLogsParams
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function queryActionAuditLogs(
|
||||
|
||||
@@ -439,7 +439,22 @@ registry.registerPath({
|
||||
query: queryConnectionAuditLogsQuery,
|
||||
params: queryConnectionAuditLogsParams
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function queryConnectionAuditLogs(
|
||||
|
||||
@@ -39,7 +39,22 @@ const getOrgSchema = z.strictObject({
|
||||
// request: {
|
||||
// params: getOrgSchema
|
||||
// },
|
||||
// responses: {}
|
||||
// responses: {
|
||||
// 200: {
|
||||
// description: "Successful response",
|
||||
// content: {
|
||||
// "application/json": {
|
||||
// schema: z.object({
|
||||
// data: z.unknown().nullable(),
|
||||
// success: z.boolean(),
|
||||
// error: z.boolean(),
|
||||
// message: z.string(),
|
||||
// status: z.number()
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
export async function getOrgUsage(
|
||||
|
||||
@@ -115,7 +115,22 @@ registry.registerPath({
|
||||
orgId: z.string()
|
||||
})
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function getCertificate(
|
||||
|
||||
@@ -25,7 +25,7 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const restartCertificateParamsSchema = z.strictObject({
|
||||
certId: z.string().transform(stoi).pipe(z.int().positive()),
|
||||
certId: z.coerce.number().int().positive(),
|
||||
orgId: z.string()
|
||||
});
|
||||
|
||||
@@ -36,11 +36,26 @@ registry.registerPath({
|
||||
tags: ["Certificate"],
|
||||
request: {
|
||||
params: z.object({
|
||||
certId: z.string().transform(stoi).pipe(z.int().positive()),
|
||||
certId: z.coerce.number().int().positive(),
|
||||
orgId: z.string()
|
||||
})
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function restartCertificate(
|
||||
|
||||
@@ -42,7 +42,22 @@ registry.registerPath({
|
||||
params: paramsSchema,
|
||||
query: querySchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function checkDomainNamespaceAvailability(
|
||||
|
||||
@@ -25,6 +25,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||
import { build } from "@server/build";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
|
||||
const paramsSchema = z.strictObject({});
|
||||
|
||||
@@ -65,6 +66,20 @@ export type ListDomainNamespacesResponse = {
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
};
|
||||
|
||||
const ListDomainNamespacesResponseDataSchema = z.object({
|
||||
domainNamespaces: z.array(
|
||||
z.object({
|
||||
domainNamespaceId: z.string(),
|
||||
domainId: z.string()
|
||||
})
|
||||
),
|
||||
pagination: z.object({
|
||||
total: z.number(),
|
||||
limit: z.number(),
|
||||
offset: z.number()
|
||||
})
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/domains/namepaces",
|
||||
@@ -73,7 +88,18 @@ registry.registerPath({
|
||||
request: {
|
||||
query: querySchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(
|
||||
ListDomainNamespacesResponseDataSchema
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listDomainNamespaces(
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db } from "@server/db";
|
||||
import { eventStreamingDestinations } from "@server/db";
|
||||
import { logStreamingManager } from "#private/lib/logStreaming";
|
||||
@@ -42,6 +43,10 @@ const bodySchema = z.strictObject({
|
||||
export type CreateEventStreamingDestinationResponse = {
|
||||
destinationId: number;
|
||||
};
|
||||
const CreateEventStreamingDestinationResponseDataSchema = z.object({
|
||||
destinationId: z.number()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "put",
|
||||
@@ -58,7 +63,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(CreateEventStreamingDestinationResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function createEventStreamingDestination(
|
||||
|
||||
@@ -38,7 +38,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function deleteEventStreamingDestination(
|
||||
|
||||
@@ -24,6 +24,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { decrypt } from "@server/lib/crypto";
|
||||
import config from "@server/lib/config";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty()
|
||||
@@ -67,6 +68,31 @@ export type ListEventStreamingDestinationsResponse = {
|
||||
};
|
||||
};
|
||||
|
||||
const ListEventStreamingDestinationsResponseDataSchema = z.object({
|
||||
destinations: z.array(
|
||||
z.object({
|
||||
destinationId: z.number(),
|
||||
orgId: z.string(),
|
||||
type: z.string(),
|
||||
config: z.string(),
|
||||
enabled: z.boolean(),
|
||||
lastError: z.string().nullable(),
|
||||
lastErrorAt: z.number().nullable(),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
sendConnectionLogs: z.boolean(),
|
||||
sendRequestLogs: z.boolean(),
|
||||
sendActionLogs: z.boolean(),
|
||||
sendAccessLogs: z.boolean()
|
||||
})
|
||||
),
|
||||
pagination: z.object({
|
||||
total: z.number(),
|
||||
limit: z.number(),
|
||||
offset: z.number()
|
||||
})
|
||||
});
|
||||
|
||||
async function query(orgId: string, limit: number, offset: number) {
|
||||
const res = await db
|
||||
.select()
|
||||
@@ -88,7 +114,18 @@ registry.registerPath({
|
||||
query: querySchema,
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(
|
||||
ListEventStreamingDestinationsResponseDataSchema
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listEventStreamingDestinations(
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db } from "@server/db";
|
||||
import { eventStreamingDestinations } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
@@ -45,6 +46,10 @@ const bodySchema = z.strictObject({
|
||||
export type UpdateEventStreamingDestinationResponse = {
|
||||
destinationId: number;
|
||||
};
|
||||
const UpdateEventStreamingDestinationResponseDataSchema = z.object({
|
||||
destinationId: z.number()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
@@ -61,7 +66,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(UpdateEventStreamingDestinationResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function updateEventStreamingDestination(
|
||||
|
||||
@@ -31,7 +31,6 @@ import * as siteProvisioning from "#private/routers/siteProvisioning";
|
||||
import * as eventStreamingDestination from "#private/routers/eventStreamingDestination";
|
||||
import * as alertRule from "#private/routers/alertRule";
|
||||
import * as healthChecks from "#private/routers/healthChecks";
|
||||
import * as labels from "#private/routers/labels";
|
||||
|
||||
import {
|
||||
verifyOrgAccess,
|
||||
@@ -733,59 +732,6 @@ authenticated.get(
|
||||
alertRule.getAlertRule
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/labels",
|
||||
verifyValidLicense,
|
||||
verifyOrgAccess,
|
||||
verifyValidSubscription(tierMatrix.labels),
|
||||
verifyUserHasAction(ActionsEnum.listOrgLabels),
|
||||
labels.listOrgLabels
|
||||
);
|
||||
|
||||
authenticated.post(
|
||||
"/org/:orgId/labels",
|
||||
verifyValidLicense,
|
||||
verifyOrgAccess,
|
||||
verifyValidSubscription(tierMatrix.labels),
|
||||
verifyUserHasAction(ActionsEnum.createOrgLabel),
|
||||
labels.createOrgLabel
|
||||
);
|
||||
|
||||
authenticated.patch(
|
||||
"/org/:orgId/label/:labelId",
|
||||
verifyValidLicense,
|
||||
verifyOrgAccess,
|
||||
verifyValidSubscription(tierMatrix.labels),
|
||||
verifyUserHasAction(ActionsEnum.updateOrgLabel),
|
||||
labels.updateOrgLabel
|
||||
);
|
||||
|
||||
authenticated.delete(
|
||||
"/org/:orgId/label/:labelId",
|
||||
verifyValidLicense,
|
||||
verifyOrgAccess,
|
||||
verifyUserHasAction(ActionsEnum.deleteOrgLabel),
|
||||
labels.deleteOrgLabel
|
||||
);
|
||||
|
||||
authenticated.put(
|
||||
"/org/:orgId/label/:labelId/attach",
|
||||
verifyValidLicense,
|
||||
verifyOrgAccess,
|
||||
verifyValidSubscription(tierMatrix.labels),
|
||||
verifyUserHasAction(ActionsEnum.attachLabelToItem),
|
||||
labels.attachLabelToItem
|
||||
);
|
||||
|
||||
authenticated.put(
|
||||
"/org/:orgId/label/:labelId/detach",
|
||||
verifyValidLicense,
|
||||
verifyOrgAccess,
|
||||
verifyValidSubscription(tierMatrix.labels),
|
||||
verifyUserHasAction(ActionsEnum.detachLabelFromItem),
|
||||
labels.detachLabelFromItem
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/health-checks",
|
||||
verifyValidLicense,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db, targetHealthCheck, newts, sites } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
@@ -52,6 +53,10 @@ const bodySchema = z.strictObject({
|
||||
export type CreateHealthCheckResponse = {
|
||||
targetHealthCheckId: number;
|
||||
};
|
||||
const CreateHealthCheckResponseDataSchema = z.object({
|
||||
targetHealthCheckId: z.number()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "put",
|
||||
@@ -68,7 +73,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(CreateHealthCheckResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function createHealthCheck(
|
||||
|
||||
@@ -41,7 +41,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function deleteHealthCheck(
|
||||
|
||||
@@ -68,7 +68,22 @@ registry.registerPath({
|
||||
params: paramsSchema,
|
||||
query: querySchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listHealthChecks(
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db, targetHealthCheck, newts, sites } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -81,6 +82,29 @@ export type UpdateHealthCheckResponse = {
|
||||
hcHealthyThreshold: number | null;
|
||||
hcUnhealthyThreshold: number | null;
|
||||
};
|
||||
const UpdateHealthCheckResponseDataSchema = z.object({
|
||||
targetHealthCheckId: z.number(),
|
||||
name: z.string().nullable(),
|
||||
siteId: z.number().nullable(),
|
||||
hcEnabled: z.boolean(),
|
||||
hcHealth: z.string().nullable(),
|
||||
hcMode: z.string().nullable(),
|
||||
hcHostname: z.string().nullable(),
|
||||
hcPort: z.number().nullable(),
|
||||
hcPath: z.string().nullable(),
|
||||
hcScheme: z.string().nullable(),
|
||||
hcMethod: z.string().nullable(),
|
||||
hcInterval: z.number().nullable(),
|
||||
hcUnhealthyInterval: z.number().nullable(),
|
||||
hcTimeout: z.number().nullable(),
|
||||
hcHeaders: z.string().nullable(),
|
||||
hcFollowRedirects: z.boolean().nullable(),
|
||||
hcStatus: z.number().nullable(),
|
||||
hcTlsServerName: z.string().nullable(),
|
||||
hcHealthyThreshold: z.number().nullable(),
|
||||
hcUnhealthyThreshold: z.number().nullable()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
@@ -97,7 +121,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(UpdateHealthCheckResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function updateHealthCheck(
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import {
|
||||
clients,
|
||||
clientLabels,
|
||||
db,
|
||||
labels,
|
||||
resourceLabels,
|
||||
resources,
|
||||
siteLabels,
|
||||
siteResourceLabels,
|
||||
siteResources,
|
||||
sites
|
||||
} from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty(),
|
||||
labelId: z.string().transform(Number).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
const attachLabelBodySchema = z.strictObject({
|
||||
siteId: z.number().int().optional(),
|
||||
resourceId: z.number().int().optional(),
|
||||
siteResourceId: z.number().int().optional(),
|
||||
clientId: z.number().int().optional()
|
||||
});
|
||||
|
||||
export async function attachLabelToItem(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
try {
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { orgId, labelId } = parsedParams.data;
|
||||
|
||||
const parsedBody = attachLabelBodySchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedBody.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { siteId, resourceId, siteResourceId, clientId } =
|
||||
parsedBody.data;
|
||||
|
||||
if (!siteId && !resourceId && !siteResourceId && !clientId) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"At least one of `siteId`, `resourceId`, `siteResourceId` or `clientId` should be provided."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(labels)
|
||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
||||
|
||||
if (!existing) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Label with Id ${labelId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (siteId) {
|
||||
const siteCount = await db.$count(
|
||||
sites,
|
||||
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
||||
);
|
||||
|
||||
if (siteCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Site with Id ${siteId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// idempotent, calling this endpoint multiple times should attach the label only once
|
||||
await db
|
||||
.insert(siteLabels)
|
||||
.values({
|
||||
labelId,
|
||||
siteId
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
}
|
||||
|
||||
if (resourceId) {
|
||||
const resourceCount = await db.$count(
|
||||
resources,
|
||||
and(
|
||||
eq(resources.resourceId, resourceId),
|
||||
eq(resources.orgId, orgId)
|
||||
)
|
||||
);
|
||||
|
||||
if (resourceCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Resource with Id ${resourceId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// idempotent, calling this endpoint multiple times should attach the label only once
|
||||
await db
|
||||
.insert(resourceLabels)
|
||||
.values({
|
||||
labelId,
|
||||
resourceId
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
}
|
||||
|
||||
if (siteResourceId) {
|
||||
const resourceCount = await db.$count(
|
||||
siteResources,
|
||||
and(
|
||||
eq(siteResources.siteResourceId, siteResourceId),
|
||||
eq(siteResources.orgId, orgId)
|
||||
)
|
||||
);
|
||||
|
||||
if (resourceCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`SiteResource with Id ${siteResourceId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// idempotent, calling this endpoint multiple times should attach the label only once
|
||||
await db
|
||||
.insert(siteResourceLabels)
|
||||
.values({
|
||||
labelId,
|
||||
siteResourceId
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
}
|
||||
|
||||
if (clientId) {
|
||||
const clientCount = await db.$count(
|
||||
clients,
|
||||
and(
|
||||
eq(clients.clientId, clientId),
|
||||
eq(clients.orgId, orgId),
|
||||
isNull(clients.userId)
|
||||
)
|
||||
);
|
||||
|
||||
if (clientCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Client with Id ${clientId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// idempotent, calling this endpoint multiple times should attach the label only once
|
||||
await db
|
||||
.insert(clientLabels)
|
||||
.values({
|
||||
labelId,
|
||||
clientId
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: {},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Label attached successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
import {
|
||||
db,
|
||||
labels,
|
||||
resourceLabels,
|
||||
resources,
|
||||
siteLabels,
|
||||
sites
|
||||
} from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty()
|
||||
});
|
||||
|
||||
const bodySchema = z.strictObject({
|
||||
name: z.string().nonempty(),
|
||||
color: z
|
||||
.string()
|
||||
.regex(/^#?([0-9a-f]{6}|[0-9a-f]{3})$/i)
|
||||
.nonempty(),
|
||||
siteId: z.number().int().optional(),
|
||||
resourceId: z.number().int().optional()
|
||||
});
|
||||
|
||||
export async function createOrgLabel(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
try {
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { orgId } = parsedParams.data;
|
||||
|
||||
const parsedBody = bodySchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedBody.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { name, color, siteId, resourceId } = parsedBody.data;
|
||||
|
||||
if (siteId) {
|
||||
const siteCount = await db.$count(
|
||||
sites,
|
||||
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
||||
);
|
||||
|
||||
if (siteCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
`Site with Id ${siteId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceId) {
|
||||
const resourceCount = await db.$count(
|
||||
resources,
|
||||
and(
|
||||
eq(resources.resourceId, resourceId),
|
||||
eq(resources.orgId, orgId)
|
||||
)
|
||||
);
|
||||
|
||||
if (resourceCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
`Resource with Id ${resourceId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const label = await db.transaction(async (tx) => {
|
||||
const [label] = await tx
|
||||
.insert(labels)
|
||||
.values({
|
||||
name,
|
||||
color,
|
||||
orgId
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (siteId) {
|
||||
await tx.insert(siteLabels).values({
|
||||
siteId,
|
||||
labelId: label.labelId
|
||||
});
|
||||
}
|
||||
|
||||
if (resourceId) {
|
||||
await tx.insert(resourceLabels).values({
|
||||
resourceId,
|
||||
labelId: label.labelId
|
||||
});
|
||||
}
|
||||
return label;
|
||||
});
|
||||
|
||||
return response<CreateOrEditLabelResponse>(res, {
|
||||
data: { label },
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Org Label created successfully",
|
||||
status: HttpCode.CREATED
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
import { db, labels } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty(),
|
||||
labelId: z.string().transform(Number).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
export async function deleteOrgLabel(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
try {
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { orgId, labelId } = parsedParams.data;
|
||||
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(labels)
|
||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
||||
|
||||
if (!existing) {
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "Label not found"));
|
||||
}
|
||||
|
||||
await db
|
||||
.delete(labels)
|
||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
||||
|
||||
return response(res, {
|
||||
data: null,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Label deleted successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import {
|
||||
clients,
|
||||
clientLabels,
|
||||
db,
|
||||
labels,
|
||||
resourceLabels,
|
||||
resources,
|
||||
siteLabels,
|
||||
siteResourceLabels,
|
||||
siteResources,
|
||||
sites
|
||||
} from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty(),
|
||||
labelId: z.string().transform(Number).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
const detachLabelBodySchema = z.strictObject({
|
||||
siteId: z.number().int().optional(),
|
||||
resourceId: z.number().int().optional(),
|
||||
siteResourceId: z.number().int().optional(),
|
||||
clientId: z.number().int().optional()
|
||||
});
|
||||
|
||||
export async function detachLabelFromItem(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
try {
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { orgId, labelId } = parsedParams.data;
|
||||
|
||||
const parsedBody = detachLabelBodySchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedBody.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { siteId, resourceId, siteResourceId, clientId } =
|
||||
parsedBody.data;
|
||||
|
||||
if (!siteId && !resourceId && !siteResourceId && !clientId) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"At least one of `siteId`, `resourceId`, `siteResourceId` or `clientId` should be provided."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(labels)
|
||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
||||
|
||||
if (!existing) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Label with Id ${labelId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (siteId) {
|
||||
const siteCount = await db.$count(
|
||||
sites,
|
||||
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
||||
);
|
||||
|
||||
if (siteCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Site with Id ${siteId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await db
|
||||
.delete(siteLabels)
|
||||
.where(
|
||||
and(
|
||||
eq(siteLabels.labelId, labelId),
|
||||
eq(siteLabels.siteId, siteId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (resourceId) {
|
||||
const resourceCount = await db.$count(
|
||||
resources,
|
||||
and(
|
||||
eq(resources.resourceId, resourceId),
|
||||
eq(resources.orgId, orgId)
|
||||
)
|
||||
);
|
||||
|
||||
if (resourceCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Resource with Id ${resourceId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await db
|
||||
.delete(resourceLabels)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceLabels.labelId, labelId),
|
||||
eq(resourceLabels.resourceId, resourceId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (siteResourceId) {
|
||||
const resourceCount = await db.$count(
|
||||
siteResources,
|
||||
and(
|
||||
eq(siteResources.siteResourceId, siteResourceId),
|
||||
eq(siteResources.orgId, orgId)
|
||||
)
|
||||
);
|
||||
|
||||
if (resourceCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`SiteResource with Id ${siteResourceId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await db
|
||||
.delete(siteResourceLabels)
|
||||
.where(
|
||||
and(
|
||||
eq(siteResourceLabels.labelId, labelId),
|
||||
eq(siteResourceLabels.siteResourceId, siteResourceId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (clientId) {
|
||||
const clientCount = await db.$count(
|
||||
clients,
|
||||
and(
|
||||
eq(clients.clientId, clientId),
|
||||
eq(clients.orgId, orgId),
|
||||
isNull(clients.userId)
|
||||
)
|
||||
);
|
||||
|
||||
if (clientCount === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Client with Id ${clientId} doesn't exist.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await db
|
||||
.delete(clientLabels)
|
||||
.where(
|
||||
and(
|
||||
eq(clientLabels.labelId, labelId),
|
||||
eq(clientLabels.clientId, clientId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: {},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Label detached successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
export * from "./listOrgLabels";
|
||||
export * from "./createOrgLabel";
|
||||
export * from "./updateOrgLabel";
|
||||
export * from "./attachLabelToItem";
|
||||
export * from "./detachLabelFromItem";
|
||||
export * from "./deleteOrgLabel";
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import { db, labels } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import type { ListOrgLabelsResponse } from "@server/routers/labels/types";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { and, asc, eq, like, sql } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty()
|
||||
});
|
||||
|
||||
const listLabelsSchema = z.object({
|
||||
pageSize: z.coerce
|
||||
.number<string>() // for prettier formatting
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.catch(20)
|
||||
.default(20)
|
||||
.openapi({
|
||||
type: "integer",
|
||||
default: 20,
|
||||
description: "Number of items per page"
|
||||
}),
|
||||
page: z.coerce
|
||||
.number<string>() // for prettier formatting
|
||||
.int()
|
||||
.min(0)
|
||||
.optional()
|
||||
.catch(1)
|
||||
.default(1)
|
||||
.openapi({
|
||||
type: "integer",
|
||||
default: 1,
|
||||
description: "Page number to retrieve"
|
||||
}),
|
||||
query: z.string().optional()
|
||||
});
|
||||
|
||||
function queryLabelsBase() {
|
||||
return db
|
||||
.select({
|
||||
labelId: labels.labelId,
|
||||
name: labels.name,
|
||||
color: labels.color
|
||||
})
|
||||
.from(labels);
|
||||
}
|
||||
|
||||
export async function listOrgLabels(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedQuery = listLabelsSchema.safeParse(req.query);
|
||||
if (!parsedQuery.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedQuery.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
const { orgId } = parsedParams.data;
|
||||
|
||||
if (req.user && orgId && orgId !== req.userOrgId) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.FORBIDDEN,
|
||||
"User does not have access to this organization"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { pageSize, page, query } = parsedQuery.data;
|
||||
|
||||
const conditions = [and(eq(labels.orgId, orgId))];
|
||||
|
||||
if (query) {
|
||||
conditions.push(
|
||||
like(
|
||||
sql`LOWER(${labels.name})`,
|
||||
"%" + query.toLowerCase() + "%"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const baseQuery = queryLabelsBase().where(and(...conditions));
|
||||
|
||||
// we need to add `as` so that drizzle filters the result as a subquery
|
||||
const countQuery = db.$count(
|
||||
queryLabelsBase()
|
||||
.where(and(...conditions))
|
||||
.as("filtered_labels")
|
||||
);
|
||||
|
||||
const labelListQuery = baseQuery
|
||||
.limit(pageSize)
|
||||
.offset(pageSize * (page - 1))
|
||||
.orderBy(asc(labels.name));
|
||||
|
||||
const [totalCount, rows] = await Promise.all([
|
||||
countQuery,
|
||||
labelListQuery
|
||||
]);
|
||||
|
||||
return response<ListOrgLabelsResponse>(res, {
|
||||
data: {
|
||||
labels: rows,
|
||||
pagination: {
|
||||
total: totalCount,
|
||||
pageSize,
|
||||
page
|
||||
}
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Labels retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import { db, labels } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty(),
|
||||
labelId: z.string().transform(Number).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
const updateLabelBodySchema = z.strictObject({
|
||||
name: z.string().min(1).max(255).optional(),
|
||||
color: z
|
||||
.string()
|
||||
.regex(/^#?([0-9a-f]{6}|[0-9a-f]{3})$/i)
|
||||
.nonempty()
|
||||
});
|
||||
|
||||
export async function updateOrgLabel(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
try {
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { orgId, labelId } = parsedParams.data;
|
||||
|
||||
const parsedBody = updateLabelBodySchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedBody.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(labels)
|
||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
||||
|
||||
if (!existing) {
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "Label not found"));
|
||||
}
|
||||
|
||||
const { name, color } = parsedBody.data;
|
||||
|
||||
const [label] = await db
|
||||
.update(labels)
|
||||
.set({
|
||||
name,
|
||||
color
|
||||
})
|
||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)))
|
||||
.returning();
|
||||
|
||||
return response<CreateOrEditLabelResponse>(res, {
|
||||
data: {
|
||||
label
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Label updated successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,22 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function createOrgOidcIdp(
|
||||
|
||||
@@ -38,7 +38,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function deleteOrgIdp(
|
||||
|
||||
@@ -56,7 +56,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function getOrgIdp(
|
||||
|
||||
@@ -72,7 +72,22 @@ registry.registerPath({
|
||||
query: querySchema,
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listOrgIdps(
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db, idpOrg } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -54,6 +55,10 @@ const bodySchema = z.strictObject({
|
||||
export type UpdateOrgIdpResponse = {
|
||||
idpId: number;
|
||||
};
|
||||
const UpdateOrgIdpResponseDataSchema = z.object({
|
||||
idpId: z.number()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
@@ -70,7 +75,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(UpdateOrgIdpResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function updateOrgOidcIdp(
|
||||
|
||||
@@ -28,7 +28,7 @@ import { OlmErrorCodes, sendOlmError } from "@server/routers/olm/error";
|
||||
import { sendTerminateClient } from "@server/routers/client/terminate";
|
||||
|
||||
const reGenerateSecretParamsSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
clientId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
const reGenerateSecretBodySchema = z.strictObject({
|
||||
|
||||
@@ -27,7 +27,7 @@ import { getAllowedIps } from "@server/routers/target/helpers";
|
||||
import { disconnectClient, sendToClient } from "#private/routers/ws";
|
||||
|
||||
const updateSiteParamsSchema = z.strictObject({
|
||||
siteId: z.string().transform(Number).pipe(z.int().positive())
|
||||
siteId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
const updateSiteBodySchema = z.strictObject({
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import axios from "axios";
|
||||
import { db, exitNodes, newts, sites } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import redisManager from "#private/lib/redis";
|
||||
import { sendToClient } from "#private/routers/ws";
|
||||
|
||||
const INITIAL_DELAY_MS = 15 * 1000; // 15 seconds before first check
|
||||
const CHECK_INTERVAL_MS = 10 * 1000; // Check every 10 seconds
|
||||
const MAX_DURATION_MS = 5 * 60 * 1000; // Give up after 5 minutes
|
||||
const REDIS_PENDING_SET = "exit-node-reconnect-pending";
|
||||
const REDIS_HASH_PREFIX = "exit-node-reconnect:";
|
||||
|
||||
interface PendingReconnect {
|
||||
startTime: number;
|
||||
reachableAt: string;
|
||||
}
|
||||
|
||||
// In-memory tracking for this node
|
||||
const pendingReconnects = new Map<number, PendingReconnect>();
|
||||
|
||||
let schedulerInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
/**
|
||||
* Schedules a reconnect check for newts connected to the given exit node.
|
||||
* Called when an exit node transitions from offline to online.
|
||||
*/
|
||||
export async function scheduleExitNodeReconnect(
|
||||
exitNodeId: number,
|
||||
reachableAt: string
|
||||
): Promise<void> {
|
||||
logger.info(
|
||||
`Scheduling newt reconnect for exit node ${exitNodeId} (reachableAt: ${reachableAt})`
|
||||
);
|
||||
|
||||
const entry: PendingReconnect = {
|
||||
startTime: Date.now(),
|
||||
reachableAt
|
||||
};
|
||||
|
||||
pendingReconnects.set(exitNodeId, entry);
|
||||
|
||||
// Store in Redis if available for cross-node coordination
|
||||
if (redisManager.isRedisEnabled()) {
|
||||
await redisManager.sadd(REDIS_PENDING_SET, exitNodeId.toString());
|
||||
await redisManager.hset(
|
||||
`${REDIS_HASH_PREFIX}${exitNodeId}`,
|
||||
"startTime",
|
||||
entry.startTime.toString()
|
||||
);
|
||||
await redisManager.hset(
|
||||
`${REDIS_HASH_PREFIX}${exitNodeId}`,
|
||||
"reachableAt",
|
||||
reachableAt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the background interval that checks pending exit node reconnects.
|
||||
*/
|
||||
export function startExitNodeReconnectScheduler(): void {
|
||||
if (schedulerInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
schedulerInterval = setInterval(async () => {
|
||||
try {
|
||||
await processPendingReconnects();
|
||||
} catch (error) {
|
||||
logger.error("Error in exit node reconnect scheduler", { error });
|
||||
}
|
||||
}, CHECK_INTERVAL_MS);
|
||||
|
||||
logger.debug("Started exit node reconnect scheduler");
|
||||
}
|
||||
|
||||
async function processPendingReconnects(): Promise<void> {
|
||||
// Merge in-memory and Redis-tracked pending reconnects
|
||||
const toProcess = new Map(pendingReconnects);
|
||||
|
||||
if (redisManager.isRedisEnabled()) {
|
||||
const redisIds = await redisManager.smembers(REDIS_PENDING_SET);
|
||||
for (const idStr of redisIds) {
|
||||
const id = parseInt(idStr, 10);
|
||||
if (!toProcess.has(id)) {
|
||||
const startTimeStr = await redisManager.hget(
|
||||
`${REDIS_HASH_PREFIX}${id}`,
|
||||
"startTime"
|
||||
);
|
||||
const reachableAt = await redisManager.hget(
|
||||
`${REDIS_HASH_PREFIX}${id}`,
|
||||
"reachableAt"
|
||||
);
|
||||
if (startTimeStr && reachableAt) {
|
||||
toProcess.set(id, {
|
||||
startTime: parseInt(startTimeStr, 10),
|
||||
reachableAt
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
for (const [exitNodeId, entry] of toProcess) {
|
||||
const elapsed = now - entry.startTime;
|
||||
|
||||
// Give up after max duration
|
||||
if (elapsed >= MAX_DURATION_MS) {
|
||||
logger.warn(
|
||||
`Exit node reconnect check timed out for exit node ${exitNodeId} after 5 minutes`
|
||||
);
|
||||
await removePending(exitNodeId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Respect initial delay
|
||||
if (elapsed < INITIAL_DELAY_MS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the exit node HTTP endpoint is reachable
|
||||
const pingUrl = `${entry.reachableAt}/ping`;
|
||||
try {
|
||||
await axios.get(pingUrl, { timeout: 5000 });
|
||||
} catch {
|
||||
logger.debug(
|
||||
`Exit node ${exitNodeId} not yet reachable at ${pingUrl}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Node is reachable — send reconnect to all connected newts
|
||||
logger.info(
|
||||
`Exit node ${exitNodeId} is reachable. Sending newt/wg/reconnect to connected newts.`
|
||||
);
|
||||
|
||||
await sendReconnectToNewts(exitNodeId);
|
||||
await removePending(exitNodeId);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendReconnectToNewts(exitNodeId: number): Promise<void> {
|
||||
try {
|
||||
const connectedNewts = await db
|
||||
.select({ newtId: newts.newtId })
|
||||
.from(newts)
|
||||
.innerJoin(sites, eq(newts.siteId, sites.siteId))
|
||||
.where(eq(sites.exitNodeId, exitNodeId));
|
||||
|
||||
if (connectedNewts.length === 0) {
|
||||
logger.debug(
|
||||
`No newts found for exit node ${exitNodeId}, nothing to reconnect`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Sending newt/wg/reconnect to ${connectedNewts.length} newt(s) for exit node ${exitNodeId}`
|
||||
);
|
||||
|
||||
const reconnectMessage = {
|
||||
type: "newt/wg/reconnect",
|
||||
data: {}
|
||||
};
|
||||
|
||||
await Promise.allSettled(
|
||||
connectedNewts.map(({ newtId }) =>
|
||||
sendToClient(newtId, reconnectMessage)
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to send reconnect messages for exit node ${exitNodeId}`,
|
||||
{ error }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function removePending(exitNodeId: number): Promise<void> {
|
||||
pendingReconnects.delete(exitNodeId);
|
||||
|
||||
if (redisManager.isRedisEnabled()) {
|
||||
await redisManager.srem(REDIS_PENDING_SET, exitNodeId.toString());
|
||||
await redisManager.del(`${REDIS_HASH_PREFIX}${exitNodeId}`);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import { MessageHandler } from "@server/routers/ws";
|
||||
import { RemoteExitNode } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import { scheduleExitNodeReconnect } from "./exitNodeReconnectScheduler";
|
||||
|
||||
/**
|
||||
* Handles ping messages from clients and responds with pong
|
||||
@@ -38,13 +37,6 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch the current state before updating so we can detect the offline→online transition
|
||||
const [currentExitNode] = await db
|
||||
.select({ online: exitNodes.online, reachableAt: exitNodes.reachableAt })
|
||||
.from(exitNodes)
|
||||
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId))
|
||||
.limit(1);
|
||||
|
||||
// Update the exit node's last ping timestamp
|
||||
await db
|
||||
.update(exitNodes)
|
||||
@@ -53,16 +45,6 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
|
||||
online: true
|
||||
})
|
||||
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId));
|
||||
|
||||
// If the exit node was offline and is now coming online, schedule newt reconnects
|
||||
if (currentExitNode && !currentExitNode.online && currentExitNode.reachableAt) {
|
||||
scheduleExitNodeReconnect(
|
||||
remoteExitNode.exitNodeId,
|
||||
currentExitNode.reachableAt
|
||||
).catch((error) => {
|
||||
logger.error("Failed to schedule exit node reconnect", { error });
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error handling ping message", { error });
|
||||
}
|
||||
|
||||
@@ -22,4 +22,3 @@ export * from "./listRemoteExitNodes";
|
||||
export * from "./pickRemoteExitNodeDefaults";
|
||||
export * from "./quickStartRemoteExitNode";
|
||||
export * from "./offlineChecker";
|
||||
export * from "./exitNodeReconnectScheduler";
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
logsDb,
|
||||
newts,
|
||||
roles,
|
||||
roleSiteResources,
|
||||
roundTripMessageTracker,
|
||||
siteResources,
|
||||
siteNetworks,
|
||||
@@ -93,7 +92,22 @@ export type SignSshKeyResponse = {
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// responses: {}
|
||||
// responses: {
|
||||
// 200: {
|
||||
// description: "Successful response",
|
||||
// content: {
|
||||
// "application/json": {
|
||||
// schema: z.object({
|
||||
// data: z.unknown().nullable(),
|
||||
// success: z.boolean(),
|
||||
// error: z.boolean(),
|
||||
// message: z.string(),
|
||||
// status: z.number()
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
export async function signSshKey(
|
||||
@@ -362,26 +376,9 @@ export async function signSshKey(
|
||||
}
|
||||
|
||||
const roleRows = await db
|
||||
.select({
|
||||
sshSudoCommands: roles.sshSudoCommands,
|
||||
sshUnixGroups: roles.sshUnixGroups,
|
||||
sshCreateHomeDir: roles.sshCreateHomeDir,
|
||||
sshSudoMode: roles.sshSudoMode
|
||||
})
|
||||
.select()
|
||||
.from(roles)
|
||||
.innerJoin(
|
||||
roleSiteResources,
|
||||
eq(roleSiteResources.roleId, roles.roleId)
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
inArray(roles.roleId, roleIds),
|
||||
eq(
|
||||
roleSiteResources.siteResourceId,
|
||||
resource.siteResourceId
|
||||
)
|
||||
)
|
||||
);
|
||||
.where(inArray(roles.roleId, roleIds));
|
||||
|
||||
const parsedSudoCommands: string[] = [];
|
||||
const parsedGroupsSet = new Set<string>();
|
||||
@@ -397,17 +394,13 @@ export async function signSshKey(
|
||||
}
|
||||
try {
|
||||
const grps = JSON.parse(roleRow?.sshUnixGroups ?? "[]");
|
||||
if (Array.isArray(grps))
|
||||
grps.forEach((g: string) => parsedGroupsSet.add(g));
|
||||
if (Array.isArray(grps)) grps.forEach((g: string) => parsedGroupsSet.add(g));
|
||||
} catch {
|
||||
// skip
|
||||
}
|
||||
if (roleRow?.sshCreateHomeDir === true) homedir = true;
|
||||
const m = roleRow?.sshSudoMode ?? "none";
|
||||
if (
|
||||
sudoModeOrder[m as keyof typeof sudoModeOrder] >
|
||||
sudoModeOrder[sudoMode]
|
||||
) {
|
||||
if (sudoModeOrder[m as keyof typeof sudoModeOrder] > sudoModeOrder[sudoMode]) {
|
||||
sudoMode = m as "none" | "commands" | "full";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAs
|
||||
|
||||
const addUserRoleParamsSchema = z.strictObject({
|
||||
userId: z.string(),
|
||||
roleId: z.string().transform(stoi).pipe(z.number())
|
||||
roleId: z.coerce.number()
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -38,7 +38,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: addUserRoleParamsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function addUserRole(
|
||||
|
||||
@@ -27,7 +27,7 @@ import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAs
|
||||
|
||||
const removeUserRoleParamsSchema = z.strictObject({
|
||||
userId: z.string(),
|
||||
roleId: z.string().transform(stoi).pipe(z.number())
|
||||
roleId: z.coerce.number()
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -39,7 +39,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: removeUserRoleParamsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function removeUserRole(
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
import {
|
||||
handleRemoteExitNodeRegisterMessage,
|
||||
handleRemoteExitNodePingMessage,
|
||||
startRemoteExitNodeOfflineChecker,
|
||||
startExitNodeReconnectScheduler
|
||||
startRemoteExitNodeOfflineChecker
|
||||
} from "#private/routers/remoteExitNode";
|
||||
import { MessageHandler } from "@server/routers/ws";
|
||||
import { build } from "@server/build";
|
||||
@@ -30,5 +29,4 @@ export const messageHandlers: Record<string, MessageHandler> = {
|
||||
|
||||
if (build != "saas") {
|
||||
startRemoteExitNodeOfflineChecker(); // this is to handle the offline check for remote exit nodes
|
||||
startExitNodeReconnectScheduler(); // check pending exit node reconnects and notify newts
|
||||
}
|
||||
|
||||
@@ -22,7 +22,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: deleteAccessTokenParamsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function deleteAccessToken(
|
||||
|
||||
@@ -31,7 +31,7 @@ export const generateAccessTokenBodySchema = z.strictObject({
|
||||
});
|
||||
|
||||
export const generateAccssTokenParamsSchema = z.strictObject({
|
||||
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
||||
resourceId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
export type GenerateAccessTokenResponse = Omit<
|
||||
@@ -54,7 +54,22 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function generateAccessToken(
|
||||
|
||||
@@ -129,7 +129,22 @@ registry.registerPath({
|
||||
}),
|
||||
query: listAccessTokensSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -143,7 +158,22 @@ registry.registerPath({
|
||||
}),
|
||||
query: listAccessTokensSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listAccessTokens(
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from "express";
|
||||
import { db } from "@server/db";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { apiKeyOrg, apiKeys } from "@server/db";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import createHttpError from "http-errors";
|
||||
@@ -32,6 +33,14 @@ export type CreateOrgApiKeyResponse = {
|
||||
lastChars: string;
|
||||
createdAt: string;
|
||||
};
|
||||
const CreateOrgApiKeyResponseDataSchema = z.object({
|
||||
apiKeyId: z.string(),
|
||||
name: z.string(),
|
||||
apiKey: z.string(),
|
||||
lastChars: z.string(),
|
||||
createdAt: z.string()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "put",
|
||||
@@ -48,7 +57,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(CreateOrgApiKeyResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function createOrgApiKey(
|
||||
|
||||
@@ -22,7 +22,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function deleteApiKey(
|
||||
|
||||
@@ -9,6 +9,7 @@ import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
|
||||
const paramsSchema = z.object({
|
||||
apiKeyId: z.string().nonempty()
|
||||
@@ -44,6 +45,19 @@ export type ListApiKeyActionsResponse = {
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
};
|
||||
|
||||
const ListApiKeyActionsResponseDataSchema = z.object({
|
||||
actions: z.array(
|
||||
z.object({
|
||||
actionId: z.string()
|
||||
})
|
||||
),
|
||||
pagination: z.object({
|
||||
total: z.number(),
|
||||
limit: z.number(),
|
||||
offset: z.number()
|
||||
})
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/org/{orgId}/api-key/{apiKeyId}/actions",
|
||||
@@ -53,7 +67,18 @@ registry.registerPath({
|
||||
params: paramsSchema,
|
||||
query: querySchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(
|
||||
ListApiKeyActionsResponseDataSchema
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listApiKeyActions(
|
||||
|
||||
@@ -9,6 +9,7 @@ import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
|
||||
const querySchema = z.object({
|
||||
limit: z
|
||||
@@ -48,6 +49,23 @@ export type ListOrgApiKeysResponse = {
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
};
|
||||
|
||||
const ListOrgApiKeysResponseDataSchema = z.object({
|
||||
apiKeys: z.array(
|
||||
z.object({
|
||||
apiKeyId: z.string(),
|
||||
orgId: z.string(),
|
||||
lastChars: z.string(),
|
||||
createdAt: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
),
|
||||
pagination: z.object({
|
||||
total: z.number(),
|
||||
limit: z.number(),
|
||||
offset: z.number()
|
||||
})
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/org/{orgId}/api-keys",
|
||||
@@ -57,7 +75,18 @@ registry.registerPath({
|
||||
params: paramsSchema,
|
||||
query: querySchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(
|
||||
ListOrgApiKeysResponseDataSchema
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listOrgApiKeys(
|
||||
|
||||
@@ -36,7 +36,22 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function setApiKeyActions(
|
||||
|
||||
@@ -5,6 +5,7 @@ import { OpenAPITags } from "@server/openApi";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { z } from "zod";
|
||||
import logger from "@server/logger";
|
||||
import {
|
||||
queryAccessAuditLogsQuery,
|
||||
@@ -28,7 +29,22 @@ registry.registerPath({
|
||||
}),
|
||||
params: queryRequestAuditLogsParams
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function exportRequestAuditLogs(
|
||||
|
||||
@@ -156,7 +156,22 @@ registry.registerPath({
|
||||
query: queryAccessAuditLogsQuery,
|
||||
params: queryRequestAuditLogsParams
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export type QueryRequestAnalyticsResponse = Awaited<ReturnType<typeof query>>;
|
||||
|
||||
@@ -227,7 +227,22 @@ registry.registerPath({
|
||||
query: queryAccessAuditLogsQuery,
|
||||
params: queryRequestAuditLogsParams
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function queryUniqueFilterAttributes(
|
||||
|
||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
||||
|
||||
export const params = z.strictObject({
|
||||
token: z.string(),
|
||||
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
||||
resourceId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
export type CheckResourceSessionParams = z.infer<typeof params>;
|
||||
|
||||
@@ -51,7 +51,22 @@ export type LookupUserResponse = {
|
||||
// request: {
|
||||
// body: lookupBodySchema
|
||||
// },
|
||||
// responses: {}
|
||||
// responses: {
|
||||
// 200: {
|
||||
// description: "Successful response",
|
||||
// content: {
|
||||
// "application/json": {
|
||||
// schema: z.object({
|
||||
// data: z.unknown().nullable(),
|
||||
// success: z.boolean(),
|
||||
// error: z.boolean(),
|
||||
// message: z.string(),
|
||||
// status: z.number()
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
export async function lookupUser(
|
||||
|
||||
@@ -31,7 +31,22 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function applyJSONBlueprint(
|
||||
|
||||
@@ -54,7 +54,22 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function applyYAMLBlueprint(
|
||||
|
||||
@@ -7,13 +7,12 @@ import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import stoi from "@server/lib/stoi";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { BlueprintData } from "./types";
|
||||
|
||||
const getBlueprintSchema = z.strictObject({
|
||||
blueprintId: z.string().transform(stoi).pipe(z.int().positive()),
|
||||
blueprintId: z.coerce.number().int().positive(),
|
||||
orgId: z.string()
|
||||
});
|
||||
|
||||
@@ -57,7 +56,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: getBlueprintSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function getBlueprint(
|
||||
|
||||
@@ -74,7 +74,22 @@ registry.registerPath({
|
||||
}),
|
||||
query: listBluePrintsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listBlueprints(
|
||||
|
||||
@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const archiveClientSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
clientId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -22,7 +22,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: archiveClientSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function archiveClient(
|
||||
|
||||
@@ -13,7 +13,7 @@ import { sendTerminateClient } from "./terminate";
|
||||
import { OlmErrorCodes } from "../olm/error";
|
||||
|
||||
const blockClientSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
clientId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -24,7 +24,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: blockClientSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function blockClient(
|
||||
|
||||
@@ -59,7 +59,22 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function createClient(
|
||||
|
||||
@@ -60,7 +60,22 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function createUserClient(
|
||||
|
||||
@@ -14,7 +14,7 @@ import { sendTerminateClient } from "./terminate";
|
||||
import { OlmErrorCodes } from "../olm/error";
|
||||
|
||||
const deleteClientSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
clientId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -25,7 +25,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: deleteClientSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function deleteClient(
|
||||
|
||||
@@ -253,7 +253,22 @@ registry.registerPath({
|
||||
niceId: z.string()
|
||||
})
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -266,7 +281,22 @@ registry.registerPath({
|
||||
clientId: z.number()
|
||||
})
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function getClient(
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import {
|
||||
clientLabels,
|
||||
clients,
|
||||
clientSitesAssociationsCache,
|
||||
currentFingerprint,
|
||||
db,
|
||||
labels,
|
||||
olms,
|
||||
orgs,
|
||||
roleClients,
|
||||
sites,
|
||||
userClients,
|
||||
users,
|
||||
type Label
|
||||
users
|
||||
} from "@server/db";
|
||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
@@ -174,7 +169,6 @@ type ClientWithSites = Awaited<ReturnType<typeof queryClientsBase>>[0] & {
|
||||
siteNiceId: string | null;
|
||||
}>;
|
||||
olmUpdateAvailable?: boolean;
|
||||
labels?: Array<Pick<Label, "labelId" | "name" | "color">>;
|
||||
};
|
||||
|
||||
type OlmWithUpdateAvailable = ClientWithSites;
|
||||
@@ -192,7 +186,22 @@ registry.registerPath({
|
||||
query: listClientsSchema,
|
||||
params: listClientsParamsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listClients(
|
||||
@@ -261,11 +270,6 @@ export async function listClients(
|
||||
(client) => client.clientId
|
||||
);
|
||||
|
||||
const isLabelFeatureEnabled = await isLicensedOrSubscribed(
|
||||
orgId,
|
||||
tierMatrix.labels
|
||||
);
|
||||
|
||||
// Get client count with filter
|
||||
const conditions = [
|
||||
and(
|
||||
@@ -299,29 +303,18 @@ export async function listClients(
|
||||
}
|
||||
|
||||
if (query) {
|
||||
const q = "%" + query.toLowerCase() + "%";
|
||||
const queryList = [
|
||||
like(sql`LOWER(${clients.name})`, q),
|
||||
like(sql`LOWER(${clients.niceId})`, q)
|
||||
];
|
||||
|
||||
if (isLabelFeatureEnabled) {
|
||||
queryList.push(
|
||||
inArray(
|
||||
clients.clientId,
|
||||
db
|
||||
.select({ id: clientLabels.clientId })
|
||||
.from(clientLabels)
|
||||
.innerJoin(
|
||||
labels,
|
||||
eq(labels.labelId, clientLabels.labelId)
|
||||
)
|
||||
.where(like(sql`LOWER(${labels.name})`, q))
|
||||
conditions.push(
|
||||
or(
|
||||
like(
|
||||
sql`LOWER(${clients.name})`,
|
||||
"%" + query.toLowerCase() + "%"
|
||||
),
|
||||
like(
|
||||
sql`LOWER(${clients.niceId})`,
|
||||
"%" + query.toLowerCase() + "%"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
conditions.push(or(...queryList));
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const baseQuery = queryClientsBase().where(and(...conditions));
|
||||
@@ -348,30 +341,6 @@ export async function listClients(
|
||||
const clientIds = clientsList.map((client) => client.clientId);
|
||||
const siteAssociations = await getSiteAssociations(clientIds);
|
||||
|
||||
let labelsForClients: Array<{
|
||||
labelId: number;
|
||||
name: string;
|
||||
color: string;
|
||||
clientId: number;
|
||||
}> = [];
|
||||
|
||||
if (isLabelFeatureEnabled && clientIds.length > 0) {
|
||||
labelsForClients = await db
|
||||
.select({
|
||||
labelId: labels.labelId,
|
||||
name: labels.name,
|
||||
color: labels.color,
|
||||
clientId: clientLabels.clientId
|
||||
})
|
||||
.from(labels)
|
||||
.innerJoin(
|
||||
clientLabels,
|
||||
eq(clientLabels.labelId, labels.labelId)
|
||||
)
|
||||
.where(inArray(clientLabels.clientId, clientIds))
|
||||
.orderBy(asc(clientLabels.clientLabelId));
|
||||
}
|
||||
|
||||
// Group site associations by client ID
|
||||
const sitesByClient = siteAssociations.reduce(
|
||||
(acc, association) => {
|
||||
@@ -399,10 +368,7 @@ export async function listClients(
|
||||
const clientsWithSites = clientsList.map((client) => {
|
||||
return {
|
||||
...client,
|
||||
sites: sitesByClient[client.clientId] || [],
|
||||
labels: labelsForClients.filter(
|
||||
(l) => l.clientId === client.clientId
|
||||
)
|
||||
sites: sitesByClient[client.clientId] || []
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -213,7 +213,22 @@ registry.registerPath({
|
||||
query: listUserDevicesSchema,
|
||||
params: listUserDevicesParamsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listUserDevices(
|
||||
|
||||
@@ -6,6 +6,7 @@ import logger from "@server/logger";
|
||||
import { generateId } from "@server/auth/sessions/app";
|
||||
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
@@ -14,6 +15,12 @@ export type PickClientDefaultsResponse = {
|
||||
olmSecret: string;
|
||||
subnet: string;
|
||||
};
|
||||
const PickClientDefaultsResponseDataSchema = z.object({
|
||||
olmId: z.string(),
|
||||
olmSecret: z.string(),
|
||||
subnet: z.string()
|
||||
});
|
||||
|
||||
|
||||
const pickClientDefaultsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
@@ -27,7 +34,16 @@ registry.registerPath({
|
||||
request: {
|
||||
params: pickClientDefaultsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(PickClientDefaultsResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function pickClientDefaults(
|
||||
|
||||
@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const unarchiveClientSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
clientId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -22,7 +22,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: unarchiveClientSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function unarchiveClient(
|
||||
|
||||
@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const unblockClientSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
clientId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
@@ -22,7 +22,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: unblockClientSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function unblockClient(
|
||||
|
||||
@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const updateClientParamsSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
clientId: z.coerce.number().int().positive()
|
||||
});
|
||||
|
||||
const updateClientSchema = z.strictObject({
|
||||
@@ -36,7 +36,22 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function updateClient(
|
||||
|
||||
@@ -37,7 +37,22 @@ registry.registerPath({
|
||||
orgId: z.string()
|
||||
})
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function getDNSRecords(
|
||||
|
||||
@@ -39,7 +39,22 @@ registry.registerPath({
|
||||
orgId: z.string()
|
||||
})
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function getDomain(
|
||||
|
||||
@@ -9,6 +9,7 @@ import { eq, sql } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
|
||||
const listDomainsParamsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
@@ -56,6 +57,28 @@ export type ListDomainsResponse = {
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
};
|
||||
|
||||
const ListDomainsResponseDataSchema = z.object({
|
||||
domains: z.array(
|
||||
z.object({
|
||||
domainId: z.string(),
|
||||
baseDomain: z.string(),
|
||||
verified: z.boolean(),
|
||||
type: z.string().nullable(),
|
||||
failed: z.boolean(),
|
||||
tries: z.number(),
|
||||
configManaged: z.boolean(),
|
||||
certResolver: z.string().nullable(),
|
||||
preferWildcardCert: z.boolean().nullable(),
|
||||
errorMessage: z.string().nullable()
|
||||
})
|
||||
),
|
||||
pagination: z.object({
|
||||
total: z.number(),
|
||||
limit: z.number(),
|
||||
offset: z.number()
|
||||
})
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/org/{orgId}/domains",
|
||||
@@ -67,7 +90,16 @@ registry.registerPath({
|
||||
}),
|
||||
query: listDomainsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(ListDomainsResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listDomains(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db, domains, orgDomains } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -24,6 +25,12 @@ export type UpdateDomainResponse = {
|
||||
certResolver: string | null;
|
||||
preferWildcardCert: boolean | null;
|
||||
};
|
||||
const UpdateDomainResponseDataSchema = z.object({
|
||||
domainId: z.string(),
|
||||
certResolver: z.string().nullable(),
|
||||
preferWildcardCert: z.boolean().nullable()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "patch",
|
||||
@@ -36,7 +43,16 @@ registry.registerPath({
|
||||
orgId: z.string()
|
||||
})
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(UpdateDomainResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function updateOrgDomain(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -22,6 +23,8 @@ const bodySchema = z.strictObject({
|
||||
});
|
||||
|
||||
export type CreateIdpOrgPolicyResponse = {};
|
||||
const CreateIdpOrgPolicyResponseDataSchema = z.object({});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "put",
|
||||
@@ -38,7 +41,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(CreateIdpOrgPolicyResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function createIdpOrgPolicy(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -33,6 +34,11 @@ export type CreateIdpResponse = {
|
||||
idpId: number;
|
||||
redirectUrl: string;
|
||||
};
|
||||
const CreateIdpResponseDataSchema = z.object({
|
||||
idpId: z.number(),
|
||||
redirectUrl: z.string()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "put",
|
||||
@@ -48,7 +54,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(CreateIdpResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function createOidcIdp(
|
||||
|
||||
@@ -25,7 +25,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function deleteIdp(
|
||||
|
||||
@@ -23,7 +23,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function deleteIdpOrgPolicy(
|
||||
|
||||
@@ -38,7 +38,22 @@ registry.registerPath({
|
||||
request: {
|
||||
params: paramsSchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.unknown().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function getIdp(
|
||||
|
||||
@@ -9,6 +9,7 @@ import { eq, sql } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
|
||||
const paramsSchema = z.object({
|
||||
idpId: z.coerce.number<number>()
|
||||
@@ -44,6 +45,21 @@ export type ListIdpOrgPoliciesResponse = {
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
};
|
||||
|
||||
const ListIdpOrgPoliciesResponseDataSchema = z.object({
|
||||
policies: z.array(
|
||||
z.object({
|
||||
idpId: z.number(),
|
||||
orgId: z.string(),
|
||||
assignDefaultOrgRoleId: z.number().nullable()
|
||||
})
|
||||
),
|
||||
pagination: z.object({
|
||||
total: z.number(),
|
||||
limit: z.number(),
|
||||
offset: z.number()
|
||||
})
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/idp/{idpId}/org",
|
||||
@@ -53,7 +69,18 @@ registry.registerPath({
|
||||
params: paramsSchema,
|
||||
query: querySchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(
|
||||
ListIdpOrgPoliciesResponseDataSchema
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listIdpOrgPolicies(
|
||||
|
||||
@@ -9,6 +9,7 @@ import { eq, sql } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
|
||||
const querySchema = z.strictObject({
|
||||
limit: z
|
||||
@@ -54,6 +55,25 @@ export type ListIdpsResponse = {
|
||||
};
|
||||
};
|
||||
|
||||
const ListIdpsResponseDataSchema = z.object({
|
||||
idps: z.array(
|
||||
z.object({
|
||||
idpId: z.number(),
|
||||
name: z.string(),
|
||||
type: z.string(),
|
||||
variant: z.string().nullable(),
|
||||
orgCount: z.number(),
|
||||
autoProvision: z.boolean().nullable(),
|
||||
tags: z.string().nullable()
|
||||
})
|
||||
),
|
||||
pagination: z.object({
|
||||
total: z.number(),
|
||||
limit: z.number(),
|
||||
offset: z.number()
|
||||
})
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/idp",
|
||||
@@ -62,7 +82,16 @@ registry.registerPath({
|
||||
request: {
|
||||
query: querySchema
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(ListIdpsResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function listIdps(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -21,6 +22,8 @@ const bodySchema = z.strictObject({
|
||||
});
|
||||
|
||||
export type UpdateIdpOrgPolicyResponse = {};
|
||||
const UpdateIdpOrgPolicyResponseDataSchema = z.object({});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
@@ -37,7 +40,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(UpdateIdpOrgPolicyResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function updateIdpOrgPolicy(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
||||
import { db } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -38,6 +39,10 @@ const bodySchema = z.strictObject({
|
||||
export type UpdateIdpResponse = {
|
||||
idpId: number;
|
||||
};
|
||||
const UpdateIdpResponseDataSchema = z.object({
|
||||
idpId: z.number()
|
||||
});
|
||||
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
@@ -54,7 +59,16 @@ registry.registerPath({
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: createApiResponseSchema(UpdateIdpResponseDataSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function updateOidcIdp(
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { Label } from "@server/db";
|
||||
import type { PaginatedResponse } from "@server/types/Pagination";
|
||||
|
||||
export type ListOrgLabelsResponse = PaginatedResponse<{
|
||||
labels: Omit<Label, "orgId">[];
|
||||
}>;
|
||||
|
||||
export type CreateOrEditLabelResponse = {
|
||||
label: Label;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user