@checkmate-monitor/catalog-common 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,57 @@
1
+ # @checkmate-monitor/catalog-common
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ffc28f6: ### Anonymous Role and Public Access
8
+
9
+ Introduces a configurable "anonymous" role for managing permissions available to unauthenticated users.
10
+
11
+ **Core Changes:**
12
+
13
+ - Added `userType: "public"` - endpoints accessible by both authenticated users (with their permissions) and anonymous users (with anonymous role permissions)
14
+ - Renamed `userType: "both"` to `"authenticated"` for clarity
15
+ - Renamed `isDefault` to `isAuthenticatedDefault` on Permission interface
16
+ - Added `isPublicDefault` flag for permissions that should be granted to the anonymous role by default
17
+
18
+ **Backend Infrastructure:**
19
+
20
+ - New `anonymous` system role created during auth-backend initialization
21
+ - New `disabled_public_default_permission` table tracks admin-disabled public defaults
22
+ - `autoAuthMiddleware` now checks anonymous role permissions for unauthenticated public endpoint access
23
+ - `AuthService.getAnonymousPermissions()` with 1-minute caching for performance
24
+ - Anonymous role filtered from `getRoles` endpoint (not assignable to users)
25
+ - Validation prevents assigning anonymous role to users
26
+
27
+ **Catalog Integration:**
28
+
29
+ - `catalog.read` permission now has both `isAuthenticatedDefault` and `isPublicDefault`
30
+ - Read endpoints (`getSystems`, `getGroups`, `getEntities`) now use `userType: "public"`
31
+
32
+ **UI:**
33
+
34
+ - New `PermissionGate` component for conditionally rendering content based on permissions
35
+
36
+ - 4dd644d: Enable external application (API key) access to management endpoints
37
+
38
+ Changed `userType: "user"` to `userType: "authenticated"` for 52 endpoints across 5 packages, allowing external applications (service accounts with API keys) to call these endpoints programmatically while maintaining RBAC permission checks:
39
+
40
+ - **incident-common**: createIncident, updateIncident, addUpdate, resolveIncident, deleteIncident
41
+ - **maintenance-common**: createMaintenance, updateMaintenance, addUpdate, closeMaintenance, deleteMaintenance
42
+ - **catalog-common**: System CRUD, Group CRUD, addSystemToGroup, removeSystemFromGroup
43
+ - **healthcheck-common**: Configuration management, system associations, retention config, detailed history
44
+ - **integration-common**: Subscription management, connection management, event discovery, delivery logs
45
+
46
+ This enables automation use cases such as:
47
+
48
+ - Creating incidents from external monitoring systems (Prometheus, Grafana)
49
+ - Scheduling maintenances from CI/CD pipelines
50
+ - Managing catalog systems from infrastructure-as-code tools
51
+ - Configuring health checks from deployment scripts
52
+
53
+ ### Patch Changes
54
+
55
+ - Updated dependencies [ffc28f6]
56
+ - @checkmate-monitor/common@0.1.0
57
+ - @checkmate-monitor/frontend-api@0.0.2
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@checkmate-monitor/catalog-common",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./src/index.ts"
8
+ }
9
+ },
10
+ "dependencies": {
11
+ "@checkmate-monitor/common": "workspace:*",
12
+ "@checkmate-monitor/frontend-api": "workspace:*",
13
+ "@orpc/contract": "^1.13.2",
14
+ "zod": "^4.2.1"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.7.2",
18
+ "@checkmate-monitor/tsconfig": "workspace:*",
19
+ "@checkmate-monitor/scripts": "workspace:*"
20
+ },
21
+ "scripts": {
22
+ "typecheck": "tsc --noEmit",
23
+ "lint": "bun run lint:code",
24
+ "lint:code": "eslint . --max-warnings 0"
25
+ }
26
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { permissions, permissionList } from "./permissions";
2
+ export * from "./rpc-contract";
3
+ export * from "./types";
4
+ export * from "./slots";
5
+ export * from "./plugin-metadata";
6
+ export { catalogRoutes } from "./routes";
@@ -0,0 +1,17 @@
1
+ import { createPermission } from "@checkmate-monitor/common";
2
+
3
+ export const permissions = {
4
+ catalogRead: createPermission(
5
+ "catalog",
6
+ "read",
7
+ "Read Catalog (Systems and Groups)",
8
+ { isAuthenticatedDefault: true, isPublicDefault: true }
9
+ ),
10
+ catalogManage: createPermission(
11
+ "catalog",
12
+ "manage",
13
+ "Full management of Catalog (Systems and Groups)"
14
+ ),
15
+ };
16
+
17
+ export const permissionList = Object.values(permissions);
@@ -0,0 +1,9 @@
1
+ import { definePluginMetadata } from "@checkmate-monitor/common";
2
+
3
+ /**
4
+ * Plugin metadata for the catalog plugin.
5
+ * Exported from the common package so both backend and frontend can reference it.
6
+ */
7
+ export const pluginMetadata = definePluginMetadata({
8
+ pluginId: "catalog",
9
+ });
package/src/routes.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { createRoutes } from "@checkmate-monitor/common";
2
+
3
+ /**
4
+ * Route definitions for the catalog plugin.
5
+ */
6
+ export const catalogRoutes = createRoutes("catalog", {
7
+ home: "/",
8
+ config: "/config",
9
+ systemDetail: "/system/:systemId",
10
+ });
@@ -0,0 +1,228 @@
1
+ import { oc } from "@orpc/contract";
2
+ import {
3
+ createClientDefinition,
4
+ type ProcedureMetadata,
5
+ } from "@checkmate-monitor/common";
6
+ import { pluginMetadata } from "./plugin-metadata";
7
+ import { z } from "zod";
8
+ import { SystemSchema, GroupSchema, ViewSchema } from "./types";
9
+ import { permissions } from "./permissions";
10
+
11
+ // Base builder with full metadata support
12
+ const _base = oc.$meta<ProcedureMetadata>({});
13
+
14
+ // Input schemas that match the service layer expectations
15
+ const CreateSystemInputSchema = z.object({
16
+ name: z.string(),
17
+ description: z.string().optional(),
18
+ owner: z.string().optional(),
19
+ metadata: z.record(z.string(), z.unknown()).optional(),
20
+ });
21
+
22
+ const UpdateSystemInputSchema = z.object({
23
+ id: z.string(),
24
+ data: z.object({
25
+ name: z.string().optional(),
26
+ description: z.string().nullable().optional(), // Allow nullable for updates
27
+ owner: z.string().nullable().optional(),
28
+ metadata: z.record(z.string(), z.unknown()).nullable().optional(), // Allow nullable
29
+ }),
30
+ });
31
+
32
+ const CreateGroupInputSchema = z.object({
33
+ name: z.string(),
34
+ metadata: z.record(z.string(), z.unknown()).optional(),
35
+ });
36
+
37
+ const UpdateGroupInputSchema = z.object({
38
+ id: z.string(),
39
+ data: z.object({
40
+ name: z.string().optional(),
41
+ metadata: z.record(z.string(), z.unknown()).nullable().optional(), // Allow nullable
42
+ }),
43
+ });
44
+
45
+ const CreateViewInputSchema = z.object({
46
+ name: z.string(),
47
+ description: z.string().optional(),
48
+ configuration: z.unknown(),
49
+ });
50
+
51
+ // Catalog RPC Contract using oRPC's contract-first pattern
52
+ export const catalogContract = {
53
+ // ==========================================================================
54
+ // ENTITY READ ENDPOINTS (userType: "public" - accessible by anyone with permission)
55
+ // ==========================================================================
56
+
57
+ getEntities: _base
58
+ .meta({ userType: "public", permissions: [permissions.catalogRead.id] })
59
+ .output(
60
+ z.object({
61
+ systems: z.array(SystemSchema),
62
+ groups: z.array(GroupSchema),
63
+ })
64
+ ),
65
+
66
+ getSystems: _base
67
+ .meta({ userType: "public", permissions: [permissions.catalogRead.id] })
68
+ .output(z.array(SystemSchema)),
69
+
70
+ getSystem: _base
71
+ .meta({ userType: "public", permissions: [permissions.catalogRead.id] })
72
+ .input(z.object({ systemId: z.string() }))
73
+ .output(SystemSchema.nullable()),
74
+
75
+ getGroups: _base
76
+ .meta({ userType: "public", permissions: [permissions.catalogRead.id] })
77
+ .output(z.array(GroupSchema)),
78
+
79
+ // ==========================================================================
80
+ // SYSTEM MANAGEMENT (userType: "authenticated" with manage permission)
81
+ // ==========================================================================
82
+
83
+ createSystem: _base
84
+ .meta({
85
+ userType: "authenticated",
86
+ permissions: [permissions.catalogManage.id],
87
+ })
88
+ .input(CreateSystemInputSchema)
89
+ .output(SystemSchema),
90
+
91
+ updateSystem: _base
92
+ .meta({
93
+ userType: "authenticated",
94
+ permissions: [permissions.catalogManage.id],
95
+ })
96
+ .input(UpdateSystemInputSchema)
97
+ .output(SystemSchema),
98
+
99
+ deleteSystem: _base
100
+ .meta({
101
+ userType: "authenticated",
102
+ permissions: [permissions.catalogManage.id],
103
+ })
104
+ .input(z.string())
105
+ .output(z.object({ success: z.boolean() })),
106
+
107
+ // ==========================================================================
108
+ // GROUP MANAGEMENT (userType: "authenticated" with manage permission)
109
+ // ==========================================================================
110
+
111
+ createGroup: _base
112
+ .meta({
113
+ userType: "authenticated",
114
+ permissions: [permissions.catalogManage.id],
115
+ })
116
+ .input(CreateGroupInputSchema)
117
+ .output(GroupSchema),
118
+
119
+ updateGroup: _base
120
+ .meta({
121
+ userType: "authenticated",
122
+ permissions: [permissions.catalogManage.id],
123
+ })
124
+ .input(UpdateGroupInputSchema)
125
+ .output(GroupSchema),
126
+
127
+ deleteGroup: _base
128
+ .meta({
129
+ userType: "authenticated",
130
+ permissions: [permissions.catalogManage.id],
131
+ })
132
+ .input(z.string())
133
+ .output(z.object({ success: z.boolean() })),
134
+
135
+ // ==========================================================================
136
+ // SYSTEM-GROUP RELATIONSHIPS (userType: "authenticated" with manage permission)
137
+ // ==========================================================================
138
+
139
+ addSystemToGroup: _base
140
+ .meta({
141
+ userType: "authenticated",
142
+ permissions: [permissions.catalogManage.id],
143
+ })
144
+ .input(
145
+ z.object({
146
+ groupId: z.string(),
147
+ systemId: z.string(),
148
+ })
149
+ )
150
+ .output(z.object({ success: z.boolean() })),
151
+
152
+ removeSystemFromGroup: _base
153
+ .meta({
154
+ userType: "authenticated",
155
+ permissions: [permissions.catalogManage.id],
156
+ })
157
+ .input(
158
+ z.object({
159
+ groupId: z.string(),
160
+ systemId: z.string(),
161
+ })
162
+ )
163
+ .output(z.object({ success: z.boolean() })),
164
+
165
+ // ==========================================================================
166
+ // VIEW MANAGEMENT (userType: "user")
167
+ // ==========================================================================
168
+
169
+ getViews: _base
170
+ .meta({ userType: "user", permissions: [permissions.catalogRead.id] })
171
+ .output(z.array(ViewSchema)),
172
+
173
+ createView: _base
174
+ .meta({ userType: "user", permissions: [permissions.catalogManage.id] })
175
+ .input(CreateViewInputSchema)
176
+ .output(ViewSchema),
177
+
178
+ // ==========================================================================
179
+ // SERVICE INTERFACE (userType: "service" - backend-to-backend only)
180
+ // ==========================================================================
181
+
182
+ /**
183
+ * Notify all users subscribed to a system (and optionally its groups).
184
+ * This is used by other plugins (e.g., maintenance) to send notifications
185
+ * to system subscribers without needing direct access to the notification service.
186
+ *
187
+ * Deduplication: If includeGroupSubscribers is true, subscribers are
188
+ * deduplicated so users subscribed to both the system AND its groups
189
+ * receive only one notification.
190
+ */
191
+ notifySystemSubscribers: _base
192
+ .meta({ userType: "service" })
193
+ .input(
194
+ z.object({
195
+ systemId: z
196
+ .string()
197
+ .describe("The system ID to notify subscribers for"),
198
+ title: z.string().describe("Notification title"),
199
+ /** Notification body in markdown format */
200
+ body: z.string().describe("Notification body (supports markdown)"),
201
+ importance: z.enum(["info", "warning", "critical"]).optional(),
202
+ /** Primary action button */
203
+ action: z
204
+ .object({
205
+ label: z.string(),
206
+ url: z.string(),
207
+ })
208
+ .optional(),
209
+ includeGroupSubscribers: z
210
+ .boolean()
211
+ .optional()
212
+ .describe(
213
+ "If true, also notify subscribers of groups that contain this system"
214
+ ),
215
+ })
216
+ )
217
+ .output(z.object({ notifiedCount: z.number() })),
218
+ };
219
+
220
+ // Export contract type
221
+ export type CatalogContract = typeof catalogContract;
222
+
223
+ // Export client definition for type-safe forPlugin usage
224
+ // Use: const client = rpcApi.forPlugin(CatalogApi);
225
+ export const CatalogApi = createClientDefinition(
226
+ catalogContract,
227
+ pluginMetadata
228
+ );
package/src/slots.ts ADDED
@@ -0,0 +1,74 @@
1
+ import { createSlot } from "@checkmate-monitor/frontend-api";
2
+ import type { System } from "./types";
3
+
4
+ /**
5
+ * Slot for extending the top of the System Details page.
6
+ * Use for important alerts like active maintenances that should be shown prominently.
7
+ * Extensions receive the full system object.
8
+ *
9
+ * @example
10
+ * extensions: [{
11
+ * id: "my-plugin.system-details-top",
12
+ * slotId: SystemDetailsTopSlot.id,
13
+ * component: ({ system }) => <MaintenanceAlert system={system} />,
14
+ * }]
15
+ */
16
+ export const SystemDetailsTopSlot = createSlot<{ system: System }>(
17
+ "plugin.catalog.system-details-top"
18
+ );
19
+
20
+ /**
21
+ * Slot for extending the System Details page with additional content.
22
+ * Extensions receive the full system object.
23
+ *
24
+ * @example
25
+ * // In your plugin
26
+ * import { SystemDetailsSlot } from "@checkmate-monitor/catalog-common";
27
+ *
28
+ * extensions: [{
29
+ * id: "my-plugin.system-details",
30
+ * slotId: SystemDetailsSlot.id,
31
+ * component: ({ system }) => <MyComponent system={system} />,
32
+ * }]
33
+ */
34
+ export const SystemDetailsSlot = createSlot<{ system: System }>(
35
+ "plugin.catalog.system-details"
36
+ );
37
+
38
+ /**
39
+ * Slot for adding actions to the catalog system configuration page.
40
+ * Extensions receive the system ID and name.
41
+ *
42
+ * @example
43
+ * // In your plugin
44
+ * import { CatalogSystemActionsSlot } from "@checkmate-monitor/catalog-common";
45
+ *
46
+ * extensions: [{
47
+ * id: "my-plugin.system-actions",
48
+ * slotId: CatalogSystemActionsSlot.id,
49
+ * component: ({ systemId, systemName }) => <MyAction systemId={systemId} />,
50
+ * }]
51
+ */
52
+ export const CatalogSystemActionsSlot = createSlot<{
53
+ systemId: string;
54
+ systemName: string;
55
+ }>("plugin.catalog.system-actions");
56
+
57
+ /**
58
+ * Slot for displaying system state badges.
59
+ * Plugins use this to contribute state indicators (e.g., health status, maintenance status).
60
+ * Extensions receive the system and should render badge components.
61
+ *
62
+ * @example
63
+ * // In your plugin
64
+ * import { SystemStateBadgesSlot } from "@checkmate-monitor/catalog-common";
65
+ *
66
+ * extensions: [{
67
+ * id: "my-plugin.system-state-badge",
68
+ * slotId: SystemStateBadgesSlot.id,
69
+ * component: ({ system }) => <MyStatusBadge systemId={system.id} />,
70
+ * }]
71
+ */
72
+ export const SystemStateBadgesSlot = createSlot<{ system: System }>(
73
+ "plugin.catalog.system-state-badges"
74
+ );
package/src/types.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+
3
+ // Domain type schemas for catalog entities
4
+ // These match the database output types exactly
5
+ // JSON serialization will handle Date -> ISO string conversion automatically
6
+
7
+ export const SystemSchema = z.object({
8
+ id: z.string(),
9
+ name: z.string(),
10
+ description: z.string().nullable(),
11
+ owner: z.string().nullable(),
12
+ metadata: z.record(z.string(), z.unknown()).nullable(),
13
+ createdAt: z.date(),
14
+ updatedAt: z.date(),
15
+ });
16
+ export type System = z.infer<typeof SystemSchema>;
17
+
18
+ export const GroupSchema = z.object({
19
+ id: z.string(),
20
+ name: z.string(),
21
+ systemIds: z.array(z.string()), // Required field from the service layer
22
+ metadata: z.record(z.string(), z.unknown()).nullable(),
23
+ createdAt: z.date(),
24
+ updatedAt: z.date(),
25
+ });
26
+ export type Group = z.infer<typeof GroupSchema>;
27
+
28
+ export const ViewSchema = z.object({
29
+ id: z.string(),
30
+ name: z.string(),
31
+ description: z.string().nullable(),
32
+ configuration: z.unknown(),
33
+ createdAt: z.date(),
34
+ updatedAt: z.date(),
35
+ });
36
+ export type View = z.infer<typeof ViewSchema>;
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "@checkmate-monitor/tsconfig/common.json",
3
+ "include": [
4
+ "src"
5
+ ]
6
+ }