mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
Refactor and clean up code formatting across multiple files
- Improved code formatting in AnnouncementsTable.tsx for better readability. - Simplified description formatting in AnnouncementView.tsx. - Adjusted lazy loading syntax in StatusPagesRoutes.tsx for consistency. - Enhanced script formatting in MCPServerGenerator.ts for better clarity. - Streamlined error handling and response parsing in OpenAPIParser.ts. - Refined resource generation logic in ResourceGenerator.ts for improved maintainability. - Updated comments and structure in GenerateMCPServer.ts for better understanding. - General code cleanup and formatting adjustments across various files to adhere to style guidelines.
This commit is contained in:
@@ -576,7 +576,7 @@ export default class Alert extends BaseModel {
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
|
||||
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
|
||||
@@ -348,9 +348,7 @@ export default class ApiKey extends BaseModel {
|
||||
public expiresAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
|
||||
],
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
|
||||
@@ -425,8 +425,8 @@ export default class APIKeyPermission extends BaseModel {
|
||||
Permission.EditProjectApiKey,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
|
||||
@@ -505,7 +505,7 @@ export default class Incident extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectIncident,
|
||||
@@ -547,7 +547,7 @@ export default class Incident extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectIncident,
|
||||
@@ -1100,9 +1100,7 @@ export default class Incident extends BaseModel {
|
||||
public telemetryQuery?: TelemetryQuery = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
|
||||
],
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
|
||||
@@ -473,10 +473,10 @@ export default class Monitor extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectIncident,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectIncident,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -495,7 +495,7 @@ export default class Monitor extends BaseModel {
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
isDefaultValueColumn: true,
|
||||
isDefaultValueColumn: true,
|
||||
title: "Current Monitor Status ID",
|
||||
description: "Whats the current status ID of this monitor?",
|
||||
canReadOnRelationQuery: true,
|
||||
|
||||
@@ -828,8 +828,7 @@ export default class Project extends TenantModel {
|
||||
hideColumnInDocumentation: true,
|
||||
type: TableColumnType.Boolean,
|
||||
title: "Not Enabled SMS or Call Notification Sent to Owners",
|
||||
description:
|
||||
"Not Enabled SMS or Call Notification Sent to Owners",
|
||||
description: "Not Enabled SMS or Call Notification Sent to Owners",
|
||||
defaultValue: false,
|
||||
})
|
||||
@Column({
|
||||
|
||||
@@ -512,8 +512,8 @@ export default class ProjectSmtpConfig extends BaseModel {
|
||||
Permission.EditProjectSMTPConfig,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: true,
|
||||
})
|
||||
|
||||
@@ -550,8 +550,8 @@ export default class ProjectSSO extends BaseModel {
|
||||
Permission.EditProjectSSO,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
|
||||
@@ -344,7 +344,11 @@ export default class PromoCode extends BaseModel {
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean, defaultValue: false })
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Boolean,
|
||||
default: false,
|
||||
|
||||
@@ -451,8 +451,8 @@ export default class StatusPageAnnouncement extends BaseModel {
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
computed: true,
|
||||
hideColumnInDocumentation: true,
|
||||
type: TableColumnType.Boolean,
|
||||
|
||||
@@ -552,8 +552,8 @@ export default class StatusPageSSO extends BaseModel {
|
||||
Permission.EditStatusPageSSO,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
@@ -578,8 +578,8 @@ export default class StatusPageSSO extends BaseModel {
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
|
||||
@@ -431,8 +431,8 @@ export default class TeamPermission extends BaseModel {
|
||||
Permission.EditProjectTeam,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
|
||||
@@ -151,7 +151,11 @@ class User extends UserModel {
|
||||
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean, defaultValue: false })
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Boolean,
|
||||
default: false,
|
||||
@@ -539,7 +543,7 @@ class User extends UserModel {
|
||||
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
@TableColumn({
|
||||
type: TableColumnType.OTP,
|
||||
computed: true,
|
||||
})
|
||||
|
||||
@@ -245,8 +245,8 @@ class UserNotificationSetting extends BaseModel {
|
||||
read: [Permission.CurrentUser],
|
||||
update: [Permission.CurrentUser],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
@@ -261,8 +261,8 @@ class UserNotificationSetting extends BaseModel {
|
||||
read: [Permission.CurrentUser],
|
||||
update: [Permission.CurrentUser],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: true,
|
||||
type: TableColumnType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
|
||||
@@ -517,13 +517,12 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
||||
(data as any)[columnName] = null;
|
||||
}
|
||||
|
||||
// if table columntype is file and file is base64 stirng then convert to buffer to save.
|
||||
// if table columntype is file and file is base64 stirng then convert to buffer to save.
|
||||
if (
|
||||
tableColumnMetadata.type === TableColumnType.File &&
|
||||
(data as any)[columnName] &&
|
||||
typeof (data as any)[columnName] === Typeof.String
|
||||
) {
|
||||
|
||||
console.log("Here!");
|
||||
|
||||
const fileBuffer: Buffer = Buffer.from(
|
||||
|
||||
@@ -155,7 +155,6 @@ export default class ModelPermission {
|
||||
continue; // this is a special case where we want to force the default value on create.
|
||||
}
|
||||
|
||||
|
||||
throw new BadDataException(
|
||||
`User is not allowed to ${requestType} on ${key} column of ${model.singularName}`,
|
||||
);
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class ColumnPermissions {
|
||||
public static getModelColumnsByPermissions<TBaseModel extends BaseModel>(
|
||||
modelType: { new (): TBaseModel },
|
||||
userPermissions: Array<UserPermission>,
|
||||
requestType: DatabaseRequestType
|
||||
requestType: DatabaseRequestType,
|
||||
): Columns {
|
||||
const model: BaseModel = new modelType();
|
||||
const accessControl: Dictionary<ColumnAccessControl> =
|
||||
@@ -54,7 +54,7 @@ export default class ColumnPermissions {
|
||||
const permissions: Array<Permission> = userPermissions.map(
|
||||
(item: UserPermission) => {
|
||||
return item.permission;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
for (const key in accessControl) {
|
||||
@@ -80,7 +80,7 @@ export default class ColumnPermissions {
|
||||
columnPermissions &&
|
||||
PermissionHelper.doesPermissionsIntersect(
|
||||
permissions,
|
||||
columnPermissions
|
||||
columnPermissions,
|
||||
)
|
||||
) {
|
||||
columns.push(key);
|
||||
@@ -95,19 +95,19 @@ export default class ColumnPermissions {
|
||||
modelType: { new (): TBaseModel },
|
||||
data: TBaseModel,
|
||||
props: DatabaseCommonInteractionProps,
|
||||
requestType: DatabaseRequestType
|
||||
requestType: DatabaseRequestType,
|
||||
): void {
|
||||
const model: BaseModel = new modelType();
|
||||
const userPermissions: Array<UserPermission> =
|
||||
DatabaseCommonInteractionPropsUtil.getUserPermissions(
|
||||
props,
|
||||
PermissionType.Allow
|
||||
PermissionType.Allow,
|
||||
);
|
||||
|
||||
const permissionColumns: Columns = this.getModelColumnsByPermissions(
|
||||
modelType,
|
||||
userPermissions,
|
||||
requestType
|
||||
requestType,
|
||||
);
|
||||
|
||||
const excludedColumnNames: Array<string> = this.getExcludedColumnNames();
|
||||
@@ -132,7 +132,7 @@ export default class ColumnPermissions {
|
||||
|
||||
if (!tableColumnMetadata) {
|
||||
throw new BadDataException(
|
||||
`No TableColumnMetadata found for ${key} column of ${model.singularName}`
|
||||
`No TableColumnMetadata found for ${key} column of ${model.singularName}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ export default class ColumnPermissions {
|
||||
}
|
||||
|
||||
throw new BadDataException(
|
||||
`User is not allowed to ${requestType} on ${key} column of ${model.singularName}`
|
||||
`User is not allowed to ${requestType} on ${key} column of ${model.singularName}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -179,13 +179,13 @@ export default class ColumnPermissions {
|
||||
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
|
||||
billingAccessControl.create,
|
||||
props.currentPlan,
|
||||
getAllEnvVars()
|
||||
getAllEnvVars(),
|
||||
)
|
||||
) {
|
||||
throw new PaymentRequiredException(
|
||||
"Please upgrade your plan to " +
|
||||
billingAccessControl.create +
|
||||
" to access this feature"
|
||||
" to access this feature",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -198,13 +198,13 @@ export default class ColumnPermissions {
|
||||
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
|
||||
billingAccessControl.read,
|
||||
props.currentPlan,
|
||||
getAllEnvVars()
|
||||
getAllEnvVars(),
|
||||
)
|
||||
) {
|
||||
throw new PaymentRequiredException(
|
||||
"Please upgrade your plan to " +
|
||||
billingAccessControl.read +
|
||||
" to access this feature"
|
||||
" to access this feature",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -217,13 +217,13 @@ export default class ColumnPermissions {
|
||||
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
|
||||
billingAccessControl.update,
|
||||
props.currentPlan,
|
||||
getAllEnvVars()
|
||||
getAllEnvVars(),
|
||||
)
|
||||
) {
|
||||
throw new PaymentRequiredException(
|
||||
"Please upgrade your plan to " +
|
||||
billingAccessControl.update +
|
||||
" to access this feature"
|
||||
" to access this feature",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,13 +114,13 @@ export default class Text {
|
||||
}
|
||||
|
||||
public static isBase64(text: string): boolean {
|
||||
if (!text || typeof text !== 'string') {
|
||||
if (!text || typeof text !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove data URI prefix if present (e.g., data:image/jpeg;base64,)
|
||||
const base64String = text.replace(/^data:[^;]+;base64,/, '');
|
||||
|
||||
const base64String = text.replace(/^data:[^;]+;base64,/, "");
|
||||
|
||||
// Check if string is empty after removing prefix
|
||||
if (!base64String) {
|
||||
return false;
|
||||
@@ -137,13 +137,13 @@ export default class Text {
|
||||
}
|
||||
|
||||
public static extractBase64FromDataUri(text: string): string {
|
||||
if (!text || typeof text !== 'string') {
|
||||
if (!text || typeof text !== "string") {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Check if it's a data URI
|
||||
if (text.startsWith('data:')) {
|
||||
const base64Index = text.indexOf(';base64,');
|
||||
if (text.startsWith("data:")) {
|
||||
const base64Index = text.indexOf(";base64,");
|
||||
if (base64Index !== -1) {
|
||||
return text.substring(base64Index + 8); // 8 is length of ';base64,'
|
||||
}
|
||||
@@ -154,13 +154,13 @@ export default class Text {
|
||||
}
|
||||
|
||||
public static extractMimeTypeFromDataUri(text: string): string | null {
|
||||
if (!text || typeof text !== 'string') {
|
||||
if (!text || typeof text !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if it's a data URI
|
||||
if (text.startsWith('data:')) {
|
||||
const mimeTypeEnd = text.indexOf(';');
|
||||
if (text.startsWith("data:")) {
|
||||
const mimeTypeEnd = text.indexOf(";");
|
||||
if (mimeTypeEnd !== -1) {
|
||||
return text.substring(5, mimeTypeEnd); // 5 is length of 'data:'
|
||||
}
|
||||
|
||||
@@ -50,74 +50,135 @@ export class AnalyticsModelSchema extends BaseSchema {
|
||||
let zodType: ZodTypes.ZodTypeAny;
|
||||
|
||||
if (column.type === TableColumnType.ObjectID) {
|
||||
zodType = z.string().openapi(this.addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
}, column));
|
||||
zodType = z.string().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "string",
|
||||
example: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Date) {
|
||||
zodType = z.date().openapi(this.addDefaultToOpenApi({
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
example: "2023-01-15T12:30:00.000Z",
|
||||
}, column));
|
||||
zodType = z.date().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
example: "2023-01-15T12:30:00.000Z",
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Text) {
|
||||
zodType = z.string().openapi(this.addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "Example text value",
|
||||
}, column));
|
||||
zodType = z.string().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "string",
|
||||
example: "Example text value",
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Number) {
|
||||
zodType = z.number().openapi(this.addDefaultToOpenApi({ type: "number", example: 42 }, column));
|
||||
zodType = z
|
||||
.number()
|
||||
.openapi(
|
||||
this.addDefaultToOpenApi({ type: "number", example: 42 }, column),
|
||||
);
|
||||
} else if (column.type === TableColumnType.LongNumber) {
|
||||
zodType = z.number().openapi(this.addDefaultToOpenApi({
|
||||
type: "number",
|
||||
example: 1000000,
|
||||
}, column));
|
||||
zodType = z.number().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "number",
|
||||
example: 1000000,
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Boolean) {
|
||||
zodType = z.boolean().openapi(this.addDefaultToOpenApi({ type: "boolean", example: true }, column));
|
||||
zodType = z
|
||||
.boolean()
|
||||
.openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{ type: "boolean", example: true },
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.JSON) {
|
||||
zodType = z.any().openapi(this.addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { key: "value", nested: { data: 123 } },
|
||||
}, column));
|
||||
zodType = z.any().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "object",
|
||||
example: { key: "value", nested: { data: 123 } },
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.JSONArray) {
|
||||
zodType = z.array(z.any()).openapi(this.addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
},
|
||||
example: [{ key: "value" }, { key2: "value2" }],
|
||||
}, column));
|
||||
zodType = z.array(z.any()).openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
},
|
||||
example: [{ key: "value" }, { key2: "value2" }],
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Decimal) {
|
||||
zodType = z.number().openapi(this.addDefaultToOpenApi({
|
||||
type: "number",
|
||||
example: 123.45,
|
||||
}, column));
|
||||
zodType = z.number().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "number",
|
||||
example: 123.45,
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.ArrayNumber) {
|
||||
zodType = z.array(z.number()).openapi(this.addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: {
|
||||
type: "number",
|
||||
},
|
||||
example: [1, 2, 3, 4, 5],
|
||||
}, column));
|
||||
zodType = z.array(z.number()).openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "array",
|
||||
items: {
|
||||
type: "number",
|
||||
},
|
||||
example: [1, 2, 3, 4, 5],
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.ArrayText) {
|
||||
zodType = z.array(z.string()).openapi(this.addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
example: ["item1", "item2", "item3"],
|
||||
}, column));
|
||||
zodType = z.array(z.string()).openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
example: ["item1", "item2", "item3"],
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
} else if (column.type === TableColumnType.IP) {
|
||||
zodType = IP.getSchema();
|
||||
} else if (column.type === TableColumnType.Port) {
|
||||
zodType = Port.getSchema();
|
||||
} else {
|
||||
// Default fallback
|
||||
zodType = z.any().openapi(this.addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "example_value",
|
||||
}, column));
|
||||
zodType = z.any().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "string",
|
||||
example: "example_value",
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Apply default value if it exists
|
||||
@@ -151,56 +212,107 @@ export class AnalyticsModelSchema extends BaseSchema {
|
||||
case TableColumnType.Date:
|
||||
return OneUptimeDate.getSchema();
|
||||
case TableColumnType.Text:
|
||||
return z.string().openapi(this.addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "Example text",
|
||||
}, column));
|
||||
return z.string().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "string",
|
||||
example: "Example text",
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
case TableColumnType.Number:
|
||||
return z.number().openapi(this.addDefaultToOpenApi({ type: "number", example: 42 }, column));
|
||||
return z
|
||||
.number()
|
||||
.openapi(
|
||||
this.addDefaultToOpenApi({ type: "number", example: 42 }, column),
|
||||
);
|
||||
case TableColumnType.LongNumber:
|
||||
return z.number().openapi(this.addDefaultToOpenApi({
|
||||
type: "number",
|
||||
example: 1000000,
|
||||
}, column));
|
||||
return z.number().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "number",
|
||||
example: 1000000,
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
case TableColumnType.Boolean:
|
||||
return z.boolean().openapi(this.addDefaultToOpenApi({ type: "boolean", example: true }, column));
|
||||
return z
|
||||
.boolean()
|
||||
.openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{ type: "boolean", example: true },
|
||||
column,
|
||||
),
|
||||
);
|
||||
case TableColumnType.JSON:
|
||||
return z.any().openapi(this.addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { key: "value" },
|
||||
}, column));
|
||||
return z.any().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "object",
|
||||
example: { key: "value" },
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
case TableColumnType.JSONArray:
|
||||
return z.array(z.any()).openapi(this.addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: { type: "object" },
|
||||
example: [{ key: "value" }],
|
||||
}, column));
|
||||
return z.array(z.any()).openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "array",
|
||||
items: { type: "object" },
|
||||
example: [{ key: "value" }],
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
case TableColumnType.Decimal:
|
||||
return z.number().openapi(this.addDefaultToOpenApi({
|
||||
type: "number",
|
||||
example: 123.45,
|
||||
}, column));
|
||||
return z.number().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "number",
|
||||
example: 123.45,
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
case TableColumnType.ArrayNumber:
|
||||
return z.array(z.number()).openapi(this.addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: { type: "number" },
|
||||
example: [1, 2, 3],
|
||||
}, column));
|
||||
return z.array(z.number()).openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "array",
|
||||
items: { type: "number" },
|
||||
example: [1, 2, 3],
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
case TableColumnType.ArrayText:
|
||||
return z.array(z.string()).openapi(this.addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
example: ["item1", "item2"],
|
||||
}, column));
|
||||
return z.array(z.string()).openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
example: ["item1", "item2"],
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
case TableColumnType.IP:
|
||||
return IP.getSchema();
|
||||
case TableColumnType.Port:
|
||||
return Port.getSchema();
|
||||
default:
|
||||
return z.any().openapi(this.addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "example_value",
|
||||
}, column));
|
||||
return z.any().openapi(
|
||||
this.addDefaultToOpenApi(
|
||||
{
|
||||
type: "string",
|
||||
example: "example_value",
|
||||
},
|
||||
column,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,13 +145,14 @@ export class ModelSchema extends BaseSchema {
|
||||
.string()
|
||||
.openapi({ type: "string", example: "example-slug-value" });
|
||||
} else if (column.type === TableColumnType.ShortText) {
|
||||
const openapiConfig: any = { type: "string", example: "Example short text" };
|
||||
const openapiConfig: any = {
|
||||
type: "string",
|
||||
example: "Example short text",
|
||||
};
|
||||
if (column.defaultValue !== undefined) {
|
||||
openapiConfig.default = column.defaultValue;
|
||||
}
|
||||
zodType = z
|
||||
.string()
|
||||
.openapi(openapiConfig);
|
||||
zodType = z.string().openapi(openapiConfig);
|
||||
} else if (column.type === TableColumnType.LongText) {
|
||||
const openapiConfig: any = {
|
||||
type: "string",
|
||||
@@ -383,7 +384,7 @@ export class ModelSchema extends BaseSchema {
|
||||
|
||||
// add title and description to the schema
|
||||
let finalDescription = "";
|
||||
|
||||
|
||||
// Add column description first if it exists
|
||||
if (column.description) {
|
||||
finalDescription = column.description;
|
||||
@@ -1152,10 +1153,13 @@ export class ModelSchema extends BaseSchema {
|
||||
|
||||
// Check if the column is required and make it optional if not
|
||||
// Also make columns with default values optional in create schemas
|
||||
if(column.isDefaultValueColumn){
|
||||
if (column.isDefaultValueColumn) {
|
||||
// should be optional
|
||||
zodType = zodType.optional();
|
||||
} else if (column.title?.toLowerCase() === "project id" && column.type === TableColumnType.ObjectID) {
|
||||
} else if (
|
||||
column.title?.toLowerCase() === "project id" &&
|
||||
column.type === TableColumnType.ObjectID
|
||||
) {
|
||||
// this is optional in the API as well as it's derived from API key
|
||||
zodType = zodType.optional();
|
||||
} else if (column.required) {
|
||||
@@ -1171,7 +1175,7 @@ export class ModelSchema extends BaseSchema {
|
||||
|
||||
// Add title and description to the schema
|
||||
let finalDescription = "";
|
||||
|
||||
|
||||
// Add column description first if it exists
|
||||
if (column.description) {
|
||||
finalDescription = column.description;
|
||||
@@ -1235,164 +1239,241 @@ export class ModelSchema extends BaseSchema {
|
||||
} else if (column.type === TableColumnType.Date) {
|
||||
zodType = OneUptimeDate.getSchema();
|
||||
} else if (column.type === TableColumnType.VeryLongText) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example:
|
||||
"This is an example of very long text content that might be stored in this field. It can contain a lot of information, such as detailed descriptions, comments, or any other lengthy text data that needs to be stored in the database.",
|
||||
}));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example:
|
||||
"This is an example of very long text content that might be stored in this field. It can contain a lot of information, such as detailed descriptions, comments, or any other lengthy text data that needs to be stored in the database.",
|
||||
}),
|
||||
);
|
||||
} else if (
|
||||
column.type === TableColumnType.Number ||
|
||||
column.type === TableColumnType.PositiveNumber
|
||||
) {
|
||||
zodType = z.number().openapi(addDefaultToOpenApi({ type: "number", example: 42 }));
|
||||
zodType = z
|
||||
.number()
|
||||
.openapi(addDefaultToOpenApi({ type: "number", example: 42 }));
|
||||
} else if (column.type === TableColumnType.Email) {
|
||||
zodType = Email.getSchema();
|
||||
} else if (column.type === TableColumnType.HashedString) {
|
||||
zodType = z
|
||||
.string()
|
||||
.openapi(addDefaultToOpenApi({ type: "string", example: "hashed_string_value" }));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "hashed_string_value",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Slug) {
|
||||
zodType = z
|
||||
.string()
|
||||
.openapi(addDefaultToOpenApi({ type: "string", example: "example-slug-value" }));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "example-slug-value",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.ShortText) {
|
||||
zodType = z
|
||||
.string()
|
||||
.openapi(addDefaultToOpenApi({ type: "string", example: "Example short text" }));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "Example short text",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.LongText) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example:
|
||||
"This is an example of longer text content that might be stored in this field.",
|
||||
}));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example:
|
||||
"This is an example of longer text content that might be stored in this field.",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Phone) {
|
||||
zodType = Phone.getSchema();
|
||||
} else if (column.type === TableColumnType.Version) {
|
||||
zodType = Version.getSchema();
|
||||
} else if (column.type === TableColumnType.Password) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
format: "password",
|
||||
example: "••••••••",
|
||||
}));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
format: "password",
|
||||
example: "••••••••",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Name) {
|
||||
zodType = Name.getSchema();
|
||||
} else if (column.type === TableColumnType.Description) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "This is a description of the item",
|
||||
}));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "This is a description of the item",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.File) {
|
||||
zodType = z.any().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
format: "binary",
|
||||
}));
|
||||
zodType = z.any().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
format: "binary",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Buffer) {
|
||||
zodType = z.any().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
format: "binary",
|
||||
}));
|
||||
zodType = z.any().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
format: "binary",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.ShortURL) {
|
||||
zodType = z.string().url().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "https://short.url/abc123",
|
||||
}));
|
||||
zodType = z
|
||||
.string()
|
||||
.url()
|
||||
.openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "https://short.url/abc123",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Markdown) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "# Heading\n\nThis is **markdown** content",
|
||||
}));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "# Heading\n\nThis is **markdown** content",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Domain) {
|
||||
zodType = Domain.getSchema();
|
||||
} else if (column.type === TableColumnType.LongURL) {
|
||||
zodType = z.string().url().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "https://www.example.com/path/to/resource?param=value",
|
||||
}));
|
||||
zodType = z
|
||||
.string()
|
||||
.url()
|
||||
.openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "https://www.example.com/path/to/resource?param=value",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.OTP) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "123456",
|
||||
}));
|
||||
} else if (column.type === TableColumnType.HTML) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "<div><h1>Title</h1><p>Content</p></div>",
|
||||
}));
|
||||
} else if (column.type === TableColumnType.JavaScript) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "function example() { return true; }",
|
||||
}));
|
||||
} else if (column.type === TableColumnType.CSS) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "body { color: #333; margin: 0; }",
|
||||
}));
|
||||
} else if (column.type === TableColumnType.Array) {
|
||||
zodType = z.array(z.any()).openapi(addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: {
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
},
|
||||
example: ["item1", "item2", "item3"],
|
||||
}));
|
||||
example: "123456",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.HTML) {
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "<div><h1>Title</h1><p>Content</p></div>",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.JavaScript) {
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "function example() { return true; }",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.CSS) {
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "body { color: #333; margin: 0; }",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Array) {
|
||||
zodType = z.array(z.any()).openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
example: ["item1", "item2", "item3"],
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.SmallPositiveNumber) {
|
||||
zodType = z.number().int().nonnegative().openapi(addDefaultToOpenApi({
|
||||
type: "integer",
|
||||
example: 5,
|
||||
}));
|
||||
zodType = z
|
||||
.number()
|
||||
.int()
|
||||
.nonnegative()
|
||||
.openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "integer",
|
||||
example: 5,
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.BigPositiveNumber) {
|
||||
zodType = z.number().nonnegative().openapi(addDefaultToOpenApi({
|
||||
type: "number",
|
||||
example: 1000000,
|
||||
}));
|
||||
zodType = z
|
||||
.number()
|
||||
.nonnegative()
|
||||
.openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "number",
|
||||
example: 1000000,
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.SmallNumber) {
|
||||
zodType = z.number().int().openapi(addDefaultToOpenApi({
|
||||
type: "integer",
|
||||
example: 10,
|
||||
}));
|
||||
zodType = z
|
||||
.number()
|
||||
.int()
|
||||
.openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "integer",
|
||||
example: 10,
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.BigNumber) {
|
||||
zodType = z.number().openapi(addDefaultToOpenApi({
|
||||
type: "number",
|
||||
example: 1000000,
|
||||
}));
|
||||
zodType = z.number().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "number",
|
||||
example: 1000000,
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Permission) {
|
||||
zodType = z.any().openapi(addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { read: true, write: false, delete: false },
|
||||
}));
|
||||
zodType = z.any().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { read: true, write: false, delete: false },
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.CustomFieldType) {
|
||||
zodType = z.any().openapi(addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { type: "text", required: true },
|
||||
}));
|
||||
zodType = z.any().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { type: "text", required: true },
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.MonitorType) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "HTTP",
|
||||
}));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "HTTP",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.WorkflowStatus) {
|
||||
zodType = z.string().openapi(addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "In Progress",
|
||||
}));
|
||||
zodType = z.string().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "string",
|
||||
example: "In Progress",
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Boolean) {
|
||||
zodType = z.boolean().openapi(addDefaultToOpenApi({ type: "boolean", example: true }));
|
||||
zodType = z
|
||||
.boolean()
|
||||
.openapi(addDefaultToOpenApi({ type: "boolean", example: true }));
|
||||
} else if (column.type === TableColumnType.JSON) {
|
||||
zodType = z.any().openapi(addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { key: "value", nested: { data: 123 } },
|
||||
}));
|
||||
zodType = z.any().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { key: "value", nested: { data: 123 } },
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.EntityArray) {
|
||||
const entityArrayType: (new () => DatabaseBaseModel) | undefined =
|
||||
column.modelType;
|
||||
if (!entityArrayType) {
|
||||
return z.any().openapi(addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: { type: "object" },
|
||||
example: [],
|
||||
}));
|
||||
return z.any().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: { type: "object" },
|
||||
example: [],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Use the appropriate schema method based on the operation type
|
||||
@@ -1420,26 +1501,30 @@ export class ModelSchema extends BaseSchema {
|
||||
});
|
||||
}),
|
||||
)
|
||||
.openapi(addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
.openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
example: [{ id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }],
|
||||
}));
|
||||
example: [{ id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }],
|
||||
}),
|
||||
);
|
||||
} else if (column.type === TableColumnType.Entity) {
|
||||
const entityType: (new () => DatabaseBaseModel) | undefined =
|
||||
column.modelType;
|
||||
|
||||
if (!entityType) {
|
||||
return z.any().openapi(addDefaultToOpenApi({
|
||||
type: "object",
|
||||
description: "Entity reference",
|
||||
example: { _id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" },
|
||||
}));
|
||||
return z.any().openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "object",
|
||||
description: "Entity reference",
|
||||
example: { _id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Use the appropriate schema method based on the operation type
|
||||
@@ -1463,12 +1548,16 @@ export class ModelSchema extends BaseSchema {
|
||||
.lazy(() => {
|
||||
return schema;
|
||||
})
|
||||
.openapi(addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" },
|
||||
}));
|
||||
.openapi(
|
||||
addDefaultToOpenApi({
|
||||
type: "object",
|
||||
example: { id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" },
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
zodType = z.any().openapi(addDefaultToOpenApi({ type: "null", example: null }));
|
||||
zodType = z
|
||||
.any()
|
||||
.openapi(addDefaultToOpenApi({ type: "null", example: null }));
|
||||
}
|
||||
|
||||
// Apply default value if it exists in the column metadata
|
||||
|
||||
@@ -132,7 +132,9 @@ const AnnouncementTable: FunctionComponent<ComponentProps> = (
|
||||
noItemsMessage={"No announcements found."}
|
||||
createEditModalWidth={ModalWidth.Large}
|
||||
showRefreshButton={true}
|
||||
viewPageRoute={RouteUtil.populateRouteParams(RouteMap[PageMap.STATUS_PAGE_ANNOUNCEMENTS] as Route)}
|
||||
viewPageRoute={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.STATUS_PAGE_ANNOUNCEMENTS] as Route,
|
||||
)}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
|
||||
@@ -101,8 +101,7 @@ const AnnouncementView: FunctionComponent<
|
||||
},
|
||||
title: "Show announcement on these status pages",
|
||||
stepId: "status-pages",
|
||||
description:
|
||||
"Select status pages to show this announcement on",
|
||||
description: "Select status pages to show this announcement on",
|
||||
fieldType: FormFieldSchemaType.MultiSelectDropdown,
|
||||
dropdownModal: {
|
||||
type: StatusPage,
|
||||
@@ -237,9 +236,7 @@ const AnnouncementView: FunctionComponent<
|
||||
modelId: modelId,
|
||||
}}
|
||||
/>
|
||||
<div className="mt-4">
|
||||
|
||||
</div>
|
||||
<div className="mt-4"></div>
|
||||
|
||||
<ModelDelete
|
||||
modelType={StatusPageAnnouncement}
|
||||
|
||||
@@ -165,11 +165,10 @@ const AnnouncementCreate: LazyExoticComponent<
|
||||
return import("../Pages/StatusPages/AnnouncementCreate");
|
||||
});
|
||||
|
||||
const AnnouncementView: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
return import("../Pages/StatusPages/AnnouncementView");
|
||||
});
|
||||
const AnnouncementView: LazyExoticComponent<FunctionComponent<ComponentProps>> =
|
||||
lazy(() => {
|
||||
return import("../Pages/StatusPages/AnnouncementView");
|
||||
});
|
||||
|
||||
const StatusPagesRoutes: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
|
||||
@@ -37,20 +37,23 @@ export class MCPServerGenerator {
|
||||
},
|
||||
files: ["build", "README.md", "package.json"],
|
||||
scripts: {
|
||||
start: "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
|
||||
start:
|
||||
"export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
|
||||
build: "tsc",
|
||||
compile: "tsc",
|
||||
dev: "npx nodemon",
|
||||
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
|
||||
"clear-modules":
|
||||
"rm -rf node_modules && rm package-lock.json && npm install",
|
||||
audit: "npm audit --audit-level=low",
|
||||
"dep-check": "npm install -g depcheck && depcheck ./ --skip-missing=true",
|
||||
"dep-check":
|
||||
"npm install -g depcheck && depcheck ./ --skip-missing=true",
|
||||
test: "rm -rf build && jest --detectOpenHandles --passWithNoTests",
|
||||
coverage: "jest --detectOpenHandles --coverage",
|
||||
prepublishOnly: "npm run build",
|
||||
},
|
||||
keywords: [
|
||||
"mcp",
|
||||
"model-context-protocol",
|
||||
"model-context-protocol",
|
||||
"oneuptime",
|
||||
"api",
|
||||
"monitoring",
|
||||
@@ -90,60 +93,60 @@ export class MCPServerGenerator {
|
||||
|
||||
private async generateIndexFile(): Promise<void> {
|
||||
const indexContent = [
|
||||
'#!/usr/bin/env node',
|
||||
'',
|
||||
"#!/usr/bin/env node",
|
||||
"",
|
||||
'import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";',
|
||||
'import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";',
|
||||
'import { ServerConfig } from "./Utils/Config.js";',
|
||||
'import { MCPService } from "./Service/MCP.js";',
|
||||
'import dotenv from "dotenv";',
|
||||
'',
|
||||
'// Load environment variables',
|
||||
'dotenv.config();',
|
||||
'',
|
||||
'async function main(): Promise<void> {',
|
||||
' try {',
|
||||
' // Create server instance',
|
||||
' const server: McpServer = new McpServer({',
|
||||
' name: ServerConfig.name,',
|
||||
' version: ServerConfig.version,',
|
||||
' capabilities: {',
|
||||
' tools: {},',
|
||||
' resources: {},',
|
||||
' },',
|
||||
' });',
|
||||
'',
|
||||
' // Add tools to server',
|
||||
' const mcpService = new MCPService();',
|
||||
' await mcpService.addToolsToServer(server);',
|
||||
'',
|
||||
' const transport: StdioServerTransport = new StdioServerTransport();',
|
||||
' await server.connect(transport);',
|
||||
"",
|
||||
"// Load environment variables",
|
||||
"dotenv.config();",
|
||||
"",
|
||||
"async function main(): Promise<void> {",
|
||||
" try {",
|
||||
" // Create server instance",
|
||||
" const server: McpServer = new McpServer({",
|
||||
" name: ServerConfig.name,",
|
||||
" version: ServerConfig.version,",
|
||||
" capabilities: {",
|
||||
" tools: {},",
|
||||
" resources: {},",
|
||||
" },",
|
||||
" });",
|
||||
"",
|
||||
" // Add tools to server",
|
||||
" const mcpService = new MCPService();",
|
||||
" await mcpService.addToolsToServer(server);",
|
||||
"",
|
||||
" const transport: StdioServerTransport = new StdioServerTransport();",
|
||||
" await server.connect(transport);",
|
||||
' console.error("OneUptime MCP Server running on stdio");',
|
||||
' } catch (error) {',
|
||||
" } catch (error) {",
|
||||
' console.error("Fatal error in main():");',
|
||||
' console.error(error);',
|
||||
' process.exit(1);',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
'// Handle graceful shutdown',
|
||||
" console.error(error);",
|
||||
" process.exit(1);",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
"// Handle graceful shutdown",
|
||||
'process.on("SIGINT", () => {',
|
||||
' console.error("Received SIGINT, shutting down gracefully...");',
|
||||
' process.exit(0);',
|
||||
'});',
|
||||
'',
|
||||
" process.exit(0);",
|
||||
"});",
|
||||
"",
|
||||
'process.on("SIGTERM", () => {',
|
||||
' console.error("Received SIGTERM, shutting down gracefully...");',
|
||||
' process.exit(0);',
|
||||
'});',
|
||||
'',
|
||||
'main().catch((error: Error) => {',
|
||||
" process.exit(0);",
|
||||
"});",
|
||||
"",
|
||||
"main().catch((error: Error) => {",
|
||||
' console.error("Fatal error in main():");',
|
||||
' console.error(error);',
|
||||
' process.exit(1);',
|
||||
'});',
|
||||
].join('\n');
|
||||
" console.error(error);",
|
||||
" process.exit(1);",
|
||||
"});",
|
||||
].join("\n");
|
||||
|
||||
await this.fileGenerator.writeFile("Index.ts", indexContent);
|
||||
}
|
||||
@@ -154,40 +157,45 @@ export class MCPServerGenerator {
|
||||
const tools = parser.getMCPTools();
|
||||
|
||||
const toolRegistrations = tools
|
||||
.map((tool) => [
|
||||
` server.tool(`,
|
||||
` "${tool.name}",`,
|
||||
` "${StringUtils.sanitizeDescription(tool.description)}",`,
|
||||
` ${JSON.stringify(tool.inputSchema, null, 6).replace(/^/gm, " ")},`,
|
||||
` async (args: any) => {`,
|
||||
` return await this.${StringUtils.toCamelCase(tool.name)}(args);`,
|
||||
` }`,
|
||||
` );`,
|
||||
].join('\n')).join('\n\n');
|
||||
.map((tool) => {
|
||||
return [
|
||||
` server.tool(`,
|
||||
` "${tool.name}",`,
|
||||
` "${StringUtils.sanitizeDescription(tool.description)}",`,
|
||||
` ${JSON.stringify(tool.inputSchema, null, 6).replace(/^/gm, " ")},`,
|
||||
` async (args: any) => {`,
|
||||
` return await this.${StringUtils.toCamelCase(tool.name)}(args);`,
|
||||
` }`,
|
||||
` );`,
|
||||
].join("\n");
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const toolMethods = tools
|
||||
.map((tool) => this.generateToolMethod(tool))
|
||||
.map((tool) => {
|
||||
return this.generateToolMethod(tool);
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const serviceContent = [
|
||||
'import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";',
|
||||
'import { OneUptimeAPIClient } from "./APIClient.js";',
|
||||
'',
|
||||
'export class MCPService {',
|
||||
' private apiClient: OneUptimeAPIClient;',
|
||||
'',
|
||||
' public constructor() {',
|
||||
' this.apiClient = new OneUptimeAPIClient();',
|
||||
' }',
|
||||
'',
|
||||
' public async addToolsToServer(server: McpServer): Promise<void> {',
|
||||
' // Register all tools',
|
||||
"",
|
||||
"export class MCPService {",
|
||||
" private apiClient: OneUptimeAPIClient;",
|
||||
"",
|
||||
" public constructor() {",
|
||||
" this.apiClient = new OneUptimeAPIClient();",
|
||||
" }",
|
||||
"",
|
||||
" public async addToolsToServer(server: McpServer): Promise<void> {",
|
||||
" // Register all tools",
|
||||
toolRegistrations,
|
||||
' }',
|
||||
'',
|
||||
" }",
|
||||
"",
|
||||
toolMethods,
|
||||
'}',
|
||||
].join('\n');
|
||||
"}",
|
||||
].join("\n");
|
||||
|
||||
this.fileGenerator.ensureDirectoryExists("Service");
|
||||
await this.fileGenerator.writeFile("Service/MCP.ts", serviceContent);
|
||||
@@ -196,151 +204,151 @@ export class MCPServerGenerator {
|
||||
private generateToolMethod(tool: MCPTool): string {
|
||||
const methodName = StringUtils.toCamelCase(tool.name);
|
||||
const operation = tool.operation;
|
||||
|
||||
|
||||
return [
|
||||
` private async ${methodName}(args: any): Promise<any> {`,
|
||||
' try {',
|
||||
' const response = await this.apiClient.request({',
|
||||
" try {",
|
||||
" const response = await this.apiClient.request({",
|
||||
` method: "${operation.method.toUpperCase()}",`,
|
||||
` path: "${operation.path}",`,
|
||||
' data: args,',
|
||||
' });',
|
||||
'',
|
||||
' return {',
|
||||
' content: [',
|
||||
' {',
|
||||
" data: args,",
|
||||
" });",
|
||||
"",
|
||||
" return {",
|
||||
" content: [",
|
||||
" {",
|
||||
' type: "text",',
|
||||
' text: JSON.stringify(response.data, null, 2),',
|
||||
' },',
|
||||
' ],',
|
||||
' };',
|
||||
' } catch (error) {',
|
||||
" text: JSON.stringify(response.data, null, 2),",
|
||||
" },",
|
||||
" ],",
|
||||
" };",
|
||||
" } catch (error) {",
|
||||
' throw new Error(`API request failed: ${error instanceof Error ? error.message : "Unknown error"}`);',
|
||||
' }',
|
||||
' }',
|
||||
].join('\n');
|
||||
" }",
|
||||
" }",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
private async generateAPIClient(): Promise<void> {
|
||||
const clientContent = [
|
||||
'import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";',
|
||||
'',
|
||||
'export interface APIRequestConfig {',
|
||||
' method: string;',
|
||||
' path: string;',
|
||||
' data?: any;',
|
||||
' params?: any;',
|
||||
' headers?: Record<string, string>;',
|
||||
'}',
|
||||
'',
|
||||
'export class OneUptimeAPIClient {',
|
||||
' private client: AxiosInstance;',
|
||||
' private baseURL: string;',
|
||||
' private apiKey: string;',
|
||||
'',
|
||||
' public constructor() {',
|
||||
' this.baseURL = this.getBaseURL();',
|
||||
' this.apiKey = this.getAPIKey();',
|
||||
'',
|
||||
' this.client = axios.create({',
|
||||
' baseURL: this.baseURL,',
|
||||
' timeout: 30000,',
|
||||
' headers: {',
|
||||
"",
|
||||
"export interface APIRequestConfig {",
|
||||
" method: string;",
|
||||
" path: string;",
|
||||
" data?: any;",
|
||||
" params?: any;",
|
||||
" headers?: Record<string, string>;",
|
||||
"}",
|
||||
"",
|
||||
"export class OneUptimeAPIClient {",
|
||||
" private client: AxiosInstance;",
|
||||
" private baseURL: string;",
|
||||
" private apiKey: string;",
|
||||
"",
|
||||
" public constructor() {",
|
||||
" this.baseURL = this.getBaseURL();",
|
||||
" this.apiKey = this.getAPIKey();",
|
||||
"",
|
||||
" this.client = axios.create({",
|
||||
" baseURL: this.baseURL,",
|
||||
" timeout: 30000,",
|
||||
" headers: {",
|
||||
' "Content-Type": "application/json",',
|
||||
' "Accept": "application/json",',
|
||||
' "User-Agent": "OneUptime MCP Server/1.0.0",',
|
||||
' },',
|
||||
' });',
|
||||
'',
|
||||
' // Add request interceptor for authentication',
|
||||
' this.client.interceptors.request.use((config) => {',
|
||||
' if (this.apiKey) {',
|
||||
" },",
|
||||
" });",
|
||||
"",
|
||||
" // Add request interceptor for authentication",
|
||||
" this.client.interceptors.request.use((config) => {",
|
||||
" if (this.apiKey) {",
|
||||
' config.headers["APIKey"] = this.apiKey;',
|
||||
' }',
|
||||
' return config;',
|
||||
' });',
|
||||
'',
|
||||
' // Add response interceptor for error handling',
|
||||
' this.client.interceptors.response.use(',
|
||||
' (response) => response,',
|
||||
' (error) => {',
|
||||
' if (error.response) {',
|
||||
' const errorMessage = error.response.data?.message || error.response.statusText;',
|
||||
' throw new Error(`HTTP ${error.response.status}: ${errorMessage}`);',
|
||||
' } else if (error.request) {',
|
||||
" }",
|
||||
" return config;",
|
||||
" });",
|
||||
"",
|
||||
" // Add response interceptor for error handling",
|
||||
" this.client.interceptors.response.use(",
|
||||
" (response) => response,",
|
||||
" (error) => {",
|
||||
" if (error.response) {",
|
||||
" const errorMessage = error.response.data?.message || error.response.statusText;",
|
||||
" throw new Error(`HTTP ${error.response.status}: ${errorMessage}`);",
|
||||
" } else if (error.request) {",
|
||||
' throw new Error("Network error: No response received from server");',
|
||||
' } else {',
|
||||
' throw new Error(`Request error: ${error.message}`);',
|
||||
' }',
|
||||
' }',
|
||||
' );',
|
||||
' }',
|
||||
'',
|
||||
' private getBaseURL(): string {',
|
||||
" } else {",
|
||||
" throw new Error(`Request error: ${error.message}`);",
|
||||
" }",
|
||||
" }",
|
||||
" );",
|
||||
" }",
|
||||
"",
|
||||
" private getBaseURL(): string {",
|
||||
' const url = process.env.ONEUPTIME_URL || process.env.ONEUPTIME_API_URL || "https://oneuptime.com";',
|
||||
' ',
|
||||
' // Ensure the URL has the correct scheme',
|
||||
" ",
|
||||
" // Ensure the URL has the correct scheme",
|
||||
' const normalizedURL = url.startsWith("http") ? url : `https://${url}`;',
|
||||
' ',
|
||||
' // Append /api if not present',
|
||||
" ",
|
||||
" // Append /api if not present",
|
||||
' return normalizedURL.endsWith("/api") ? normalizedURL : `${normalizedURL.replace(/\\/$/, "")}/api`;',
|
||||
' }',
|
||||
'',
|
||||
' private getAPIKey(): string {',
|
||||
' const apiKey = process.env.ONEUPTIME_API_KEY || process.env.API_KEY;',
|
||||
' if (!apiKey) {',
|
||||
' throw new Error(',
|
||||
" }",
|
||||
"",
|
||||
" private getAPIKey(): string {",
|
||||
" const apiKey = process.env.ONEUPTIME_API_KEY || process.env.API_KEY;",
|
||||
" if (!apiKey) {",
|
||||
" throw new Error(",
|
||||
' "OneUptime API key is required. Set ONEUPTIME_API_KEY or API_KEY environment variable."',
|
||||
' );',
|
||||
' }',
|
||||
' return apiKey;',
|
||||
' }',
|
||||
'',
|
||||
' public async request(config: APIRequestConfig): Promise<AxiosResponse> {',
|
||||
' const requestConfig: AxiosRequestConfig = {',
|
||||
' method: config.method.toLowerCase() as any,',
|
||||
' url: this.interpolatePath(config.path, config.data || config.params),',
|
||||
" );",
|
||||
" }",
|
||||
" return apiKey;",
|
||||
" }",
|
||||
"",
|
||||
" public async request(config: APIRequestConfig): Promise<AxiosResponse> {",
|
||||
" const requestConfig: AxiosRequestConfig = {",
|
||||
" method: config.method.toLowerCase() as any,",
|
||||
" url: this.interpolatePath(config.path, config.data || config.params),",
|
||||
' data: config.method.toUpperCase() !== "GET" ? config.data : undefined,',
|
||||
' params: config.method.toUpperCase() === "GET" ? config.params : undefined,',
|
||||
' headers: config.headers || {},',
|
||||
' };',
|
||||
'',
|
||||
' return await this.client.request(requestConfig);',
|
||||
' }',
|
||||
'',
|
||||
' private interpolatePath(path: string, data: any): string {',
|
||||
' if (!data) return path;',
|
||||
'',
|
||||
' return path.replace(/\\{([^}]+)\\}/g, (match, paramName) => {',
|
||||
' const value = data[paramName];',
|
||||
' if (value === undefined) {',
|
||||
' throw new Error(`Missing required path parameter: ${paramName}`);',
|
||||
' }',
|
||||
' return encodeURIComponent(value.toString());',
|
||||
' });',
|
||||
' }',
|
||||
'',
|
||||
' public async get(path: string, params?: any): Promise<AxiosResponse> {',
|
||||
" headers: config.headers || {},",
|
||||
" };",
|
||||
"",
|
||||
" return await this.client.request(requestConfig);",
|
||||
" }",
|
||||
"",
|
||||
" private interpolatePath(path: string, data: any): string {",
|
||||
" if (!data) return path;",
|
||||
"",
|
||||
" return path.replace(/\\{([^}]+)\\}/g, (match, paramName) => {",
|
||||
" const value = data[paramName];",
|
||||
" if (value === undefined) {",
|
||||
" throw new Error(`Missing required path parameter: ${paramName}`);",
|
||||
" }",
|
||||
" return encodeURIComponent(value.toString());",
|
||||
" });",
|
||||
" }",
|
||||
"",
|
||||
" public async get(path: string, params?: any): Promise<AxiosResponse> {",
|
||||
' return this.request({ method: "GET", path, params });',
|
||||
' }',
|
||||
'',
|
||||
' public async post(path: string, data?: any): Promise<AxiosResponse> {',
|
||||
" }",
|
||||
"",
|
||||
" public async post(path: string, data?: any): Promise<AxiosResponse> {",
|
||||
' return this.request({ method: "POST", path, data });',
|
||||
' }',
|
||||
'',
|
||||
' public async put(path: string, data?: any): Promise<AxiosResponse> {',
|
||||
" }",
|
||||
"",
|
||||
" public async put(path: string, data?: any): Promise<AxiosResponse> {",
|
||||
' return this.request({ method: "PUT", path, data });',
|
||||
' }',
|
||||
'',
|
||||
' public async patch(path: string, data?: any): Promise<AxiosResponse> {',
|
||||
" }",
|
||||
"",
|
||||
" public async patch(path: string, data?: any): Promise<AxiosResponse> {",
|
||||
' return this.request({ method: "PATCH", path, data });',
|
||||
' }',
|
||||
'',
|
||||
' public async delete(path: string): Promise<AxiosResponse> {',
|
||||
" }",
|
||||
"",
|
||||
" public async delete(path: string): Promise<AxiosResponse> {",
|
||||
' return this.request({ method: "DELETE", path });',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n');
|
||||
" }",
|
||||
"}",
|
||||
].join("\n");
|
||||
|
||||
this.fileGenerator.ensureDirectoryExists("Service");
|
||||
await this.fileGenerator.writeFile("Service/APIClient.ts", clientContent);
|
||||
@@ -348,41 +356,41 @@ export class MCPServerGenerator {
|
||||
|
||||
private async generateConfigUtils(): Promise<void> {
|
||||
const configContent = [
|
||||
'export const ServerConfig = {',
|
||||
"export const ServerConfig = {",
|
||||
` name: "${this.config.serverName}",`,
|
||||
` version: "${this.config.serverVersion}",`,
|
||||
` description: "${this.config.description}",`,
|
||||
'} as const;',
|
||||
'',
|
||||
'export const EnvironmentVariables = {',
|
||||
"} as const;",
|
||||
"",
|
||||
"export const EnvironmentVariables = {",
|
||||
' ONEUPTIME_URL: "ONEUPTIME_URL",',
|
||||
' ONEUPTIME_API_URL: "ONEUPTIME_API_URL",',
|
||||
' ONEUPTIME_API_KEY: "ONEUPTIME_API_KEY",',
|
||||
' API_KEY: "API_KEY",',
|
||||
'} as const;',
|
||||
'',
|
||||
'export function validateEnvironment(): void {',
|
||||
' const apiKey = process.env.ONEUPTIME_API_KEY || process.env.API_KEY;',
|
||||
' ',
|
||||
' if (!apiKey) {',
|
||||
' throw new Error(',
|
||||
"} as const;",
|
||||
"",
|
||||
"export function validateEnvironment(): void {",
|
||||
" const apiKey = process.env.ONEUPTIME_API_KEY || process.env.API_KEY;",
|
||||
" ",
|
||||
" if (!apiKey) {",
|
||||
" throw new Error(",
|
||||
' "OneUptime API key is required. Please set one of the following environment variables:\\n" +',
|
||||
' "- ONEUPTIME_API_KEY\\n" +',
|
||||
' "- API_KEY"',
|
||||
' );',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
'export function getEnvironmentInfo(): Record<string, string | undefined> {',
|
||||
' return {',
|
||||
' ONEUPTIME_URL: process.env.ONEUPTIME_URL,',
|
||||
' ONEUPTIME_API_URL: process.env.ONEUPTIME_API_URL,',
|
||||
" );",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
"export function getEnvironmentInfo(): Record<string, string | undefined> {",
|
||||
" return {",
|
||||
" ONEUPTIME_URL: process.env.ONEUPTIME_URL,",
|
||||
" ONEUPTIME_API_URL: process.env.ONEUPTIME_API_URL,",
|
||||
' ONEUPTIME_API_KEY: process.env.ONEUPTIME_API_KEY ? "[REDACTED]" : undefined,',
|
||||
' API_KEY: process.env.API_KEY ? "[REDACTED]" : undefined,',
|
||||
' NODE_ENV: process.env.NODE_ENV,',
|
||||
' };',
|
||||
'}',
|
||||
].join('\n');
|
||||
" NODE_ENV: process.env.NODE_ENV,",
|
||||
" };",
|
||||
"}",
|
||||
].join("\n");
|
||||
|
||||
this.fileGenerator.ensureDirectoryExists("Utils");
|
||||
await this.fileGenerator.writeFile("Utils/Config.ts", configContent);
|
||||
@@ -396,14 +404,19 @@ export class MCPServerGenerator {
|
||||
|
||||
const toolList = tools
|
||||
.slice(0, 20)
|
||||
.map((tool) => `- **${tool.name}**: ${tool.description}`)
|
||||
.map((tool) => {
|
||||
return `- **${tool.name}**: ${tool.description}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const resourceList = resourceTags
|
||||
.map((tag) => `- **${StringUtils.toPascalCase(tag)}**`)
|
||||
.map((tag) => {
|
||||
return `- **${StringUtils.toPascalCase(tag)}**`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const additionalToolsNote = tools.length > 20 ? `\n...and ${tools.length - 20} more tools` : "";
|
||||
const additionalToolsNote =
|
||||
tools.length > 20 ? `\n...and ${tools.length - 20} more tools` : "";
|
||||
|
||||
const readmeContent = `# ${this.config.serverName}
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import fs from "fs";
|
||||
import {
|
||||
OpenAPISpec,
|
||||
OpenAPIOperation,
|
||||
MCPTool,
|
||||
OpenAPISchema,
|
||||
} from "./Types";
|
||||
import { OpenAPISpec, OpenAPIOperation, MCPTool, OpenAPISchema } from "./Types";
|
||||
import { StringUtils } from "./StringUtils";
|
||||
|
||||
export class OpenAPIParser {
|
||||
@@ -36,7 +31,11 @@ export class OpenAPIParser {
|
||||
// Group operations by resource/tag
|
||||
for (const [path, pathItem] of Object.entries(this.spec.paths)) {
|
||||
for (const [method, operation] of Object.entries(pathItem)) {
|
||||
if (!operation.operationId || !operation.tags || operation.tags.length === 0) {
|
||||
if (
|
||||
!operation.operationId ||
|
||||
!operation.tags ||
|
||||
operation.tags.length === 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -48,10 +47,17 @@ export class OpenAPIParser {
|
||||
return tools;
|
||||
}
|
||||
|
||||
private createMCPTool(path: string, method: string, operation: OpenAPIOperation): MCPTool {
|
||||
private createMCPTool(
|
||||
path: string,
|
||||
method: string,
|
||||
operation: OpenAPIOperation,
|
||||
): MCPTool {
|
||||
const toolName: string = this.generateToolName(operation);
|
||||
const description: string = operation.description || operation.summary || `${method.toUpperCase()} ${path}`;
|
||||
|
||||
const description: string =
|
||||
operation.description ||
|
||||
operation.summary ||
|
||||
`${method.toUpperCase()} ${path}`;
|
||||
|
||||
const inputSchema: any = this.generateInputSchema(operation);
|
||||
|
||||
return {
|
||||
@@ -84,11 +90,17 @@ export class OpenAPIParser {
|
||||
// Add path parameters
|
||||
if (operation.parameters) {
|
||||
for (const param of operation.parameters) {
|
||||
if (param.in === "path" || param.in === "query" || param.in === "header") {
|
||||
if (
|
||||
param.in === "path" ||
|
||||
param.in === "query" ||
|
||||
param.in === "header"
|
||||
) {
|
||||
const paramName = StringUtils.toCamelCase(param.name);
|
||||
properties[paramName] = this.convertOpenAPISchemaToJsonSchema(param.schema);
|
||||
properties[paramName] = this.convertOpenAPISchemaToJsonSchema(
|
||||
param.schema,
|
||||
);
|
||||
properties[paramName].description = param.description || "";
|
||||
|
||||
|
||||
if (param.required || param.in === "path") {
|
||||
required.push(paramName);
|
||||
}
|
||||
@@ -100,17 +112,23 @@ export class OpenAPIParser {
|
||||
if (operation.requestBody) {
|
||||
const content = operation.requestBody.content;
|
||||
const jsonContent = content["application/json"];
|
||||
|
||||
|
||||
if (jsonContent && jsonContent.schema) {
|
||||
if (jsonContent.schema.properties) {
|
||||
// Flatten the request body properties into the main properties
|
||||
Object.assign(properties, this.convertOpenAPISchemaToJsonSchema(jsonContent.schema).properties);
|
||||
Object.assign(
|
||||
properties,
|
||||
this.convertOpenAPISchemaToJsonSchema(jsonContent.schema)
|
||||
.properties,
|
||||
);
|
||||
if (jsonContent.schema.required) {
|
||||
required.push(...jsonContent.schema.required);
|
||||
}
|
||||
} else {
|
||||
// If it's a reference or complex schema, add as 'data' property
|
||||
properties.data = this.convertOpenAPISchemaToJsonSchema(jsonContent.schema);
|
||||
properties.data = this.convertOpenAPISchemaToJsonSchema(
|
||||
jsonContent.schema,
|
||||
);
|
||||
if (operation.requestBody.required) {
|
||||
required.push("data");
|
||||
}
|
||||
@@ -154,7 +172,8 @@ export class OpenAPIParser {
|
||||
if (schema.properties) {
|
||||
jsonSchema.properties = {};
|
||||
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
||||
jsonSchema.properties[propName] = this.convertOpenAPISchemaToJsonSchema(propSchema);
|
||||
jsonSchema.properties[propName] =
|
||||
this.convertOpenAPISchemaToJsonSchema(propSchema);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +191,11 @@ export class OpenAPIParser {
|
||||
|
||||
// Handle #/components/schemas/SchemeName format
|
||||
const refParts = ref.split("/");
|
||||
if (refParts[0] === "#" && refParts[1] === "components" && refParts[2] === "schemas") {
|
||||
if (
|
||||
refParts[0] === "#" &&
|
||||
refParts[1] === "components" &&
|
||||
refParts[2] === "schemas"
|
||||
) {
|
||||
const schemaName = refParts[3];
|
||||
if (schemaName && this.spec.components?.schemas?.[schemaName]) {
|
||||
return this.spec.components.schemas[schemaName];
|
||||
@@ -188,11 +211,13 @@ export class OpenAPIParser {
|
||||
}
|
||||
|
||||
const tags = new Set<string>();
|
||||
|
||||
|
||||
for (const [, pathItem] of Object.entries(this.spec.paths)) {
|
||||
for (const [, operation] of Object.entries(pathItem)) {
|
||||
if (operation.tags) {
|
||||
operation.tags.forEach(tag => tags.add(tag));
|
||||
operation.tags.forEach((tag) => {
|
||||
return tags.add(tag);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,17 @@ async function main(): Promise<void> {
|
||||
|
||||
// Step 4: Initialize MCP server generator
|
||||
Logger.info("⚙️ Step 3: Initializing MCP server generator...");
|
||||
const generator = new MCPServerGenerator({
|
||||
outputDir: mcpDir,
|
||||
serverName: "oneuptime-mcp",
|
||||
serverVersion: "1.0.0",
|
||||
npmPackageName: "@oneuptime/mcp-server",
|
||||
description: "OneUptime Model Context Protocol (MCP) Server - Provides access to OneUptime APIs for LLMs",
|
||||
}, apiSpec);
|
||||
const generator = new MCPServerGenerator(
|
||||
{
|
||||
outputDir: mcpDir,
|
||||
serverName: "oneuptime-mcp",
|
||||
serverVersion: "1.0.0",
|
||||
npmPackageName: "@oneuptime/mcp-server",
|
||||
description:
|
||||
"OneUptime Model Context Protocol (MCP) Server - Provides access to OneUptime APIs for LLMs",
|
||||
},
|
||||
apiSpec,
|
||||
);
|
||||
|
||||
// Step 5: Generate MCP server
|
||||
Logger.info("🏗️ Step 4: Generating MCP server files...");
|
||||
@@ -57,7 +61,6 @@ async function main(): Promise<void> {
|
||||
Logger.info(" 3. Set up your environment variables");
|
||||
Logger.info(" 4. npm run build");
|
||||
Logger.info(" 5. Test with npm start");
|
||||
|
||||
} catch (error) {
|
||||
Logger.error("💥 MCP server generation failed:");
|
||||
Logger.error(error instanceof Error ? error.message : "Unknown error");
|
||||
@@ -157,7 +160,7 @@ All notable changes to the OneUptime MCP Server will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.0] - ${new Date().toISOString().split('T')[0]}
|
||||
## [1.0.0] - ${new Date().toISOString().split("T")[0]}
|
||||
|
||||
### Added
|
||||
- Initial release of OneUptime MCP Server
|
||||
|
||||
@@ -1083,19 +1083,17 @@ func (r *${resourceTypeName}Resource) Delete(ctx context.Context, req resource.D
|
||||
fields.push(
|
||||
` "${apiFieldName}": r.convertTerraformListToInterface(data.${fieldName}),`,
|
||||
);
|
||||
} else if (attr.type === "string" && attr.isComplexObject) {
|
||||
// For complex object strings, parse JSON and convert to interface{}
|
||||
fields.push(
|
||||
` "${apiFieldName}": r.parseJSONField(data.${fieldName}),`,
|
||||
);
|
||||
} else {
|
||||
if (attr.type === "string" && attr.isComplexObject) {
|
||||
// For complex object strings, parse JSON and convert to interface{}
|
||||
fields.push(
|
||||
` "${apiFieldName}": r.parseJSONField(data.${fieldName}),`,
|
||||
);
|
||||
} else {
|
||||
const value: string = this.getGoValueForTerraformType(
|
||||
attr.type,
|
||||
`data.${fieldName}`,
|
||||
);
|
||||
fields.push(` "${apiFieldName}": ${value},`);
|
||||
}
|
||||
const value: string = this.getGoValueForTerraformType(
|
||||
attr.type,
|
||||
`data.${fieldName}`,
|
||||
);
|
||||
fields.push(` "${apiFieldName}": ${value},`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1199,7 +1197,6 @@ func (r *${resourceTypeName}Resource) Delete(ctx context.Context, req resource.D
|
||||
isCreateMethod: boolean = false,
|
||||
originalFieldName?: string,
|
||||
): string {
|
||||
|
||||
switch (terraformType) {
|
||||
case "string":
|
||||
// Handle binary format fields (like base64 file content) specially
|
||||
@@ -1213,16 +1210,15 @@ func (r *${resourceTypeName}Resource) Delete(ctx context.Context, req resource.D
|
||||
// Preserve original value from the request since API doesn't return file content
|
||||
${fieldName} = types.StringValue(original${StringUtils.toPascalCase(originalFieldName)}Value)
|
||||
}`;
|
||||
} else {
|
||||
// In Read/Update methods, preserve existing value if not present in API response
|
||||
// This prevents drift detection when API doesn't return binary content
|
||||
return `if val, ok := ${responseValue}.(string); ok {
|
||||
}
|
||||
// In Read/Update methods, preserve existing value if not present in API response
|
||||
// This prevents drift detection when API doesn't return binary content
|
||||
return `if val, ok := ${responseValue}.(string); ok {
|
||||
${fieldName} = types.StringValue(val)
|
||||
} else {
|
||||
// Keep existing value to prevent drift - API doesn't return binary content
|
||||
// ${fieldName} value is already set from the existing state
|
||||
}`;
|
||||
}
|
||||
} else if (isComplexObject) {
|
||||
// For complex object strings, convert API object response to JSON string
|
||||
return `if val, ok := ${responseValue}.(map[string]interface{}); ok {
|
||||
@@ -1236,8 +1232,8 @@ func (r *${resourceTypeName}Resource) Delete(ctx context.Context, req resource.D
|
||||
} else {
|
||||
${fieldName} = types.StringNull()
|
||||
}`;
|
||||
} else {
|
||||
return `if obj, ok := ${responseValue}.(map[string]interface{}); ok {
|
||||
}
|
||||
return `if obj, ok := ${responseValue}.(map[string]interface{}); ok {
|
||||
// Handle ObjectID type responses
|
||||
if val, ok := obj["_id"].(string); ok && val != "" {
|
||||
${fieldName} = types.StringValue(val)
|
||||
@@ -1251,7 +1247,7 @@ func (r *${resourceTypeName}Resource) Delete(ctx context.Context, req resource.D
|
||||
} else {
|
||||
${fieldName} = types.StringNull()
|
||||
}`;
|
||||
}
|
||||
|
||||
case "number":
|
||||
return `if val, ok := ${responseValue}.(float64); ok {
|
||||
${fieldName} = types.NumberValue(big.NewFloat(val))
|
||||
@@ -1268,13 +1264,13 @@ func (r *${resourceTypeName}Resource) Delete(ctx context.Context, req resource.D
|
||||
return `if val, ok := ${responseValue}.(bool); ok {
|
||||
${fieldName} = types.BoolValue(val)
|
||||
}`;
|
||||
} else {
|
||||
return `if val, ok := ${responseValue}.(bool); ok {
|
||||
}
|
||||
return `if val, ok := ${responseValue}.(bool); ok {
|
||||
${fieldName} = types.BoolValue(val)
|
||||
} else if ${responseValue} == nil {
|
||||
${fieldName} = types.BoolNull()
|
||||
}`;
|
||||
}
|
||||
|
||||
case "map":
|
||||
return `if val, ok := ${responseValue}.(map[string]interface{}); ok {
|
||||
// Convert API response map to Terraform map
|
||||
@@ -1418,18 +1414,22 @@ ${resourceFunctions}
|
||||
|
||||
private generateOriginalValueStorage(resource: TerraformResource): string {
|
||||
const storage: string[] = [];
|
||||
|
||||
|
||||
// Find binary format fields and store their original values
|
||||
for (const [name, attr] of Object.entries(resource.schema)) {
|
||||
if (attr.format === "binary") {
|
||||
const sanitizedName: string = this.sanitizeAttributeName(name);
|
||||
const fieldName: string = StringUtils.toPascalCase(sanitizedName);
|
||||
storage.push(` // Store the original ${sanitizedName} value since API won't return it`);
|
||||
storage.push(` original${fieldName}Value := data.${fieldName}.ValueString()`);
|
||||
storage.push(
|
||||
` // Store the original ${sanitizedName} value since API won't return it`,
|
||||
);
|
||||
storage.push(
|
||||
` original${fieldName}Value := data.${fieldName}.ValueString()`,
|
||||
);
|
||||
storage.push(``);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return storage.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user