@graph-knowledge/api 0.1.4 → 0.1.6
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/README.md +31 -0
- package/package.json +1 -1
- package/src/index.d.ts +2 -1
- package/src/lib/clients/firebase-auth-client.d.ts +2 -0
- package/src/lib/clients/firebase-auth-client.js +20 -0
- package/src/lib/clients/firebase-firestore-client.d.ts +1 -1
- package/src/lib/constants/element-defaults.js +7 -1
- package/src/lib/graph-knowledge-api.d.ts +8 -0
- package/src/lib/graph-knowledge-api.js +11 -0
- package/src/lib/interfaces/auth-client.interface.d.ts +11 -0
- package/src/lib/interfaces/firestore-client.interface.d.ts +2 -1
- package/src/lib/interfaces/template-operations.interface.d.ts +55 -0
- package/src/lib/interfaces/template-operations.interface.js +2 -0
- package/src/lib/models/document.model.d.ts +15 -0
- package/src/lib/operations/template-operations.d.ts +30 -0
- package/src/lib/operations/template-operations.js +221 -0
- package/src/lib/testing/mock-auth-client.d.ts +8 -0
- package/src/lib/testing/mock-auth-client.js +17 -0
- package/src/lib/testing/mock-firestore-client.d.ts +1 -1
- package/src/lib/types/element-input-types.d.ts +79 -1
- package/src/lib/validators/element-type-validators/basic-shape-validators.d.ts +36 -0
- package/src/lib/validators/element-type-validators/basic-shape-validators.js +51 -0
- package/src/lib/validators/element-type-validators/block-arrow-validator.d.ts +11 -0
- package/src/lib/validators/element-type-validators/block-arrow-validator.js +36 -0
- package/src/lib/validators/element-type-validators/line-validator.d.ts +11 -0
- package/src/lib/validators/element-type-validators/line-validator.js +35 -0
- package/src/lib/validators/element-validator-registry.js +9 -0
package/README.md
CHANGED
|
@@ -101,6 +101,7 @@ new GraphKnowledgeAPI(config: ApiConfig)
|
|
|
101
101
|
| `nodes` | `INodeOperations` | Node CRUD operations |
|
|
102
102
|
| `elements` | `IElementOperations` | Element CRUD operations |
|
|
103
103
|
| `batch` | `IBatchOperations` | Batch element operations |
|
|
104
|
+
| `templates` | `ITemplateOperations` | Template operations (list, get, clone) |
|
|
104
105
|
| `authClient` | `IAuthClient` | Authentication client |
|
|
105
106
|
|
|
106
107
|
### Document Operations
|
|
@@ -243,6 +244,35 @@ await api.elements.update(documentId, nodeId, elementId, {
|
|
|
243
244
|
await api.elements.delete(documentId, nodeId, elementId);
|
|
244
245
|
```
|
|
245
246
|
|
|
247
|
+
### Template Operations
|
|
248
|
+
|
|
249
|
+
Templates are pre-made documents that can be cloned by premium users:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// List all available templates
|
|
253
|
+
const templates = await api.templates.list();
|
|
254
|
+
console.log(templates);
|
|
255
|
+
// [{ id: "template-1", title: "UML Class Diagram", isTemplate: true, ... }]
|
|
256
|
+
|
|
257
|
+
// Get a specific template with all its nodes and elements
|
|
258
|
+
const template = await api.templates.get("template-1");
|
|
259
|
+
|
|
260
|
+
// Clone a template (creates a new document) - requires premium
|
|
261
|
+
const myDoc = await api.templates.clone("template-1");
|
|
262
|
+
// Creates "Copy of UML Class Diagram" document
|
|
263
|
+
|
|
264
|
+
// Clone with custom title
|
|
265
|
+
const myDoc2 = await api.templates.clone("template-1", "My Project Diagram");
|
|
266
|
+
// Creates "My Project Diagram" document
|
|
267
|
+
|
|
268
|
+
// The cloned document:
|
|
269
|
+
// - Has a new unique ID
|
|
270
|
+
// - Is owned by the current user
|
|
271
|
+
// - Has isTemplate: false
|
|
272
|
+
// - Has clonedFromTemplateId pointing to the source template
|
|
273
|
+
// - Contains copies of all nodes and elements with new IDs
|
|
274
|
+
```
|
|
275
|
+
|
|
246
276
|
### Batch Operations
|
|
247
277
|
|
|
248
278
|
For efficient bulk operations (ideal for AI agents and automation):
|
|
@@ -379,6 +409,7 @@ GraphKnowledgeAPI (Composition Root)
|
|
|
379
409
|
├── NodeOperations : INodeOperations
|
|
380
410
|
├── ElementOperations : IElementOperations
|
|
381
411
|
├── BatchOperations : IBatchOperations
|
|
412
|
+
├── TemplateOperations : ITemplateOperations
|
|
382
413
|
└── ElementValidatorRegistry : IElementValidatorRegistry
|
|
383
414
|
├── RectangleValidator
|
|
384
415
|
├── TextValidator
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -6,9 +6,10 @@ export type { IDocumentOperations } from "./lib/interfaces/document-operations.i
|
|
|
6
6
|
export type { INodeOperations } from "./lib/interfaces/node-operations.interface";
|
|
7
7
|
export type { IElementOperations } from "./lib/interfaces/element-operations.interface";
|
|
8
8
|
export type { IBatchOperations, BatchUpdateElementInput } from "./lib/interfaces/batch-operations.interface";
|
|
9
|
+
export type { ITemplateOperations } from "./lib/interfaces/template-operations.interface";
|
|
9
10
|
export type { IElementTypeValidator, IElementValidatorRegistry } from "./lib/interfaces/element-validator.interface";
|
|
10
11
|
export type { CreateDocumentInput, UpdateDocumentInput, DocumentResult, CreateNodeInput, UpdateNodeInput, NodeResult } from "./lib/types/api-types";
|
|
11
|
-
export type { BaseElementInput, RectangleInput, TextInput, ConnectorInput, ConnectorAnchor, LineStyle, MarkerType, UmlClassInput, UmlInterfaceInput, UmlComponentInput, UmlPackageInput, UmlArtifactInput, UmlNoteInput, CustomShapeInput, AnyElementInput, UpdateElementInput } from "./lib/types/element-input-types";
|
|
12
|
+
export type { BaseElementInput, RectangleInput, TextInput, ConnectorInput, ConnectorAnchor, LineStyle, MarkerType, UmlClassInput, UmlInterfaceInput, UmlComponentInput, UmlPackageInput, UmlArtifactInput, UmlNoteInput, TriangleInput, DiamondInput, HexagonInput, EllipseInput, LineInput, BlockArrowInput, BlockArrowDirection, CustomShapeInput, AnyElementInput, UpdateElementInput } from "./lib/types/element-input-types";
|
|
12
13
|
export { GraphKnowledgeAPIError, AuthenticationError, NotFoundError, ValidationError, PermissionError } from "./lib/errors/api-errors";
|
|
13
14
|
export type { Document, GraphNode, GraphElement } from "./lib/models";
|
|
14
15
|
export { CURRENT_DOCUMENT_SCHEMA_VERSION } from "./lib/models";
|
|
@@ -76,5 +76,25 @@ class FirebaseAuthClient {
|
|
|
76
76
|
throw new api_errors_1.PermissionError("Premium subscription required");
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
async isAdmin() {
|
|
80
|
+
if (!this._currentUser) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
// Read admin status from user profile in Firestore
|
|
84
|
+
const userDoc = (0, firestore_1.doc)(this.firestore, `users/${this._currentUser.uid}`);
|
|
85
|
+
const snapshot = await (0, firestore_1.getDoc)(userDoc);
|
|
86
|
+
if (!snapshot.exists()) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const profile = snapshot.data();
|
|
90
|
+
return profile?.["isAdmin"] === true;
|
|
91
|
+
}
|
|
92
|
+
async requireAdmin() {
|
|
93
|
+
this.requireAuth();
|
|
94
|
+
const admin = await this.isAdmin();
|
|
95
|
+
if (!admin) {
|
|
96
|
+
throw new api_errors_1.PermissionError("Admin access required");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
79
99
|
}
|
|
80
100
|
exports.FirebaseAuthClient = FirebaseAuthClient;
|
|
@@ -15,7 +15,7 @@ export declare class FirebaseFirestoreClient implements IFirestoreClient {
|
|
|
15
15
|
getCollection<T>(path: string): Promise<(T & {
|
|
16
16
|
id: string;
|
|
17
17
|
})[]>;
|
|
18
|
-
queryCollection<T>(path: string, field: string, value: string): Promise<(T & {
|
|
18
|
+
queryCollection<T>(path: string, field: string, value: string | number | boolean | null): Promise<(T & {
|
|
19
19
|
id: string;
|
|
20
20
|
})[]>;
|
|
21
21
|
batch(): IBatchWriter;
|
|
@@ -14,7 +14,13 @@ exports.DEFAULT_ELEMENT_DIMENSIONS = {
|
|
|
14
14
|
"uml-component": { width: 150, height: 100 },
|
|
15
15
|
"uml-package": { width: 200, height: 150 },
|
|
16
16
|
"uml-artifact": { width: 100, height: 80 },
|
|
17
|
-
"uml-note": { width: 150, height: 100 }
|
|
17
|
+
"uml-note": { width: 150, height: 100 },
|
|
18
|
+
triangle: { width: 120, height: 100 },
|
|
19
|
+
diamond: { width: 100, height: 100 },
|
|
20
|
+
hexagon: { width: 120, height: 104 },
|
|
21
|
+
ellipse: { width: 150, height: 100 },
|
|
22
|
+
line: { width: 150, height: 0 },
|
|
23
|
+
"block-arrow": { width: 150, height: 80 }
|
|
18
24
|
};
|
|
19
25
|
/**
|
|
20
26
|
* Default dimensions for custom shapes.
|
|
@@ -4,6 +4,7 @@ import { IDocumentOperations } from "./interfaces/document-operations.interface"
|
|
|
4
4
|
import { INodeOperations } from "./interfaces/node-operations.interface";
|
|
5
5
|
import { IElementOperations } from "./interfaces/element-operations.interface";
|
|
6
6
|
import { IBatchOperations } from "./interfaces/batch-operations.interface";
|
|
7
|
+
import { ITemplateOperations } from "./interfaces/template-operations.interface";
|
|
7
8
|
/**
|
|
8
9
|
* Main entry point for the Graph Knowledge Headless API.
|
|
9
10
|
*
|
|
@@ -48,6 +49,7 @@ export declare class GraphKnowledgeAPI {
|
|
|
48
49
|
private readonly _nodes;
|
|
49
50
|
private readonly _elements;
|
|
50
51
|
private readonly _batch;
|
|
52
|
+
private readonly _templates;
|
|
51
53
|
/**
|
|
52
54
|
* Creates a new GraphKnowledgeAPI instance.
|
|
53
55
|
* @param config Configuration including Firebase credentials
|
|
@@ -73,6 +75,12 @@ export declare class GraphKnowledgeAPI {
|
|
|
73
75
|
* Batch element operations (createElements, updateElements, deleteElements).
|
|
74
76
|
*/
|
|
75
77
|
get batch(): IBatchOperations;
|
|
78
|
+
/**
|
|
79
|
+
* Template operations (list, get, clone, publish, unpublish).
|
|
80
|
+
* Templates are documents that can be cloned by premium users.
|
|
81
|
+
* Admins can publish/unpublish templates to control visibility.
|
|
82
|
+
*/
|
|
83
|
+
get templates(): ITemplateOperations;
|
|
76
84
|
/**
|
|
77
85
|
* Signs in with email and password.
|
|
78
86
|
* @param email User email
|
|
@@ -10,6 +10,7 @@ const document_operations_1 = require("./operations/document-operations");
|
|
|
10
10
|
const node_operations_1 = require("./operations/node-operations");
|
|
11
11
|
const element_operations_1 = require("./operations/element-operations");
|
|
12
12
|
const batch_operations_1 = require("./operations/batch-operations");
|
|
13
|
+
const template_operations_1 = require("./operations/template-operations");
|
|
13
14
|
const document_validator_1 = require("./validators/document-validator");
|
|
14
15
|
const node_validator_1 = require("./validators/node-validator");
|
|
15
16
|
const element_validator_registry_1 = require("./validators/element-validator-registry");
|
|
@@ -57,6 +58,7 @@ class GraphKnowledgeAPI {
|
|
|
57
58
|
_nodes;
|
|
58
59
|
_elements;
|
|
59
60
|
_batch;
|
|
61
|
+
_templates;
|
|
60
62
|
/**
|
|
61
63
|
* Creates a new GraphKnowledgeAPI instance.
|
|
62
64
|
* @param config Configuration including Firebase credentials
|
|
@@ -78,6 +80,7 @@ class GraphKnowledgeAPI {
|
|
|
78
80
|
this._nodes = new node_operations_1.NodeOperations(firestoreClient, this._authClient, nodeValidator);
|
|
79
81
|
this._elements = new element_operations_1.ElementOperations(firestoreClient, this._authClient, elementValidatorRegistry);
|
|
80
82
|
this._batch = new batch_operations_1.BatchOperations(firestoreClient, this._authClient, elementValidatorRegistry);
|
|
83
|
+
this._templates = new template_operations_1.TemplateOperations(firestoreClient, this._authClient);
|
|
81
84
|
}
|
|
82
85
|
/**
|
|
83
86
|
* Authentication client for sign-in/sign-out operations.
|
|
@@ -109,6 +112,14 @@ class GraphKnowledgeAPI {
|
|
|
109
112
|
get batch() {
|
|
110
113
|
return this._batch;
|
|
111
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Template operations (list, get, clone, publish, unpublish).
|
|
117
|
+
* Templates are documents that can be cloned by premium users.
|
|
118
|
+
* Admins can publish/unpublish templates to control visibility.
|
|
119
|
+
*/
|
|
120
|
+
get templates() {
|
|
121
|
+
return this._templates;
|
|
122
|
+
}
|
|
112
123
|
// =========================================================================
|
|
113
124
|
// Convenience methods delegating to authClient
|
|
114
125
|
// =========================================================================
|
|
@@ -32,4 +32,15 @@ export interface IAuthClient {
|
|
|
32
32
|
* @throws PermissionError if not premium
|
|
33
33
|
*/
|
|
34
34
|
requirePremium(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Checks if the current user has admin status.
|
|
37
|
+
* @returns Promise resolving to true if user is admin
|
|
38
|
+
*/
|
|
39
|
+
isAdmin(): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Requires the current user to have admin status.
|
|
42
|
+
* @throws AuthenticationError if not authenticated
|
|
43
|
+
* @throws PermissionError if not admin
|
|
44
|
+
*/
|
|
45
|
+
requireAdmin(): Promise<void>;
|
|
35
46
|
}
|
|
@@ -51,8 +51,9 @@ export interface IFirestoreClient {
|
|
|
51
51
|
})[]>;
|
|
52
52
|
/**
|
|
53
53
|
* Queries documents in a collection where a field equals a value.
|
|
54
|
+
* Supports string, number, boolean, and null values.
|
|
54
55
|
*/
|
|
55
|
-
queryCollection<T>(path: string, field: string, value: string): Promise<(T & {
|
|
56
|
+
queryCollection<T>(path: string, field: string, value: string | number | boolean | null): Promise<(T & {
|
|
56
57
|
id: string;
|
|
57
58
|
})[]>;
|
|
58
59
|
/**
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DocumentResult } from "../types/api-types";
|
|
2
|
+
/**
|
|
3
|
+
* Interface for template operations.
|
|
4
|
+
*
|
|
5
|
+
* Templates are documents with `isTemplate: true` that can be cloned
|
|
6
|
+
* by premium users to create new documents.
|
|
7
|
+
*/
|
|
8
|
+
export interface ITemplateOperations {
|
|
9
|
+
/**
|
|
10
|
+
* Lists all published templates available to users.
|
|
11
|
+
* @returns Array of published template documents (shallow, without nodes)
|
|
12
|
+
* @throws AuthenticationError if not authenticated
|
|
13
|
+
*/
|
|
14
|
+
list(): Promise<DocumentResult[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Gets a single template by ID, including all its nodes.
|
|
17
|
+
* @param templateId The template document ID
|
|
18
|
+
* @returns The template with all nodes
|
|
19
|
+
* @throws AuthenticationError if not authenticated
|
|
20
|
+
* @throws NotFoundError if template doesn't exist
|
|
21
|
+
*/
|
|
22
|
+
get(templateId: string): Promise<DocumentResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Clones a template into a new document owned by the current user.
|
|
25
|
+
* Requires premium access.
|
|
26
|
+
*
|
|
27
|
+
* @param templateId The template to clone
|
|
28
|
+
* @param title Optional custom title (defaults to "Copy of {template.title}")
|
|
29
|
+
* @returns The newly created document
|
|
30
|
+
* @throws AuthenticationError if not authenticated
|
|
31
|
+
* @throws PermissionError if not premium user
|
|
32
|
+
* @throws NotFoundError if template doesn't exist
|
|
33
|
+
*/
|
|
34
|
+
clone(templateId: string, title?: string): Promise<DocumentResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Publishes a template, making it visible to all users.
|
|
37
|
+
* Requires admin access.
|
|
38
|
+
*
|
|
39
|
+
* @param templateId The template ID to publish
|
|
40
|
+
* @throws AuthenticationError if not authenticated
|
|
41
|
+
* @throws PermissionError if not admin
|
|
42
|
+
* @throws NotFoundError if template doesn't exist
|
|
43
|
+
*/
|
|
44
|
+
publish(templateId: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Unpublishes a template, hiding it from regular users.
|
|
47
|
+
* Requires admin access.
|
|
48
|
+
*
|
|
49
|
+
* @param templateId The template ID to unpublish
|
|
50
|
+
* @throws AuthenticationError if not authenticated
|
|
51
|
+
* @throws PermissionError if not admin
|
|
52
|
+
* @throws NotFoundError if template doesn't exist
|
|
53
|
+
*/
|
|
54
|
+
unpublish(templateId: string): Promise<void>;
|
|
55
|
+
}
|
|
@@ -42,6 +42,21 @@ export interface Document {
|
|
|
42
42
|
nodes?: GraphNode[];
|
|
43
43
|
rootNodeId?: string;
|
|
44
44
|
isPremiumContent?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Template flag - only admins can set this to true.
|
|
47
|
+
* Templates are visible to all authenticated users.
|
|
48
|
+
*/
|
|
49
|
+
isTemplate?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the template is published and visible to users (templates only).
|
|
52
|
+
* Unpublished templates are only visible to admins.
|
|
53
|
+
*/
|
|
54
|
+
isPublished?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Source template ID if this document was cloned from a template.
|
|
57
|
+
* Provides audit trail for template usage.
|
|
58
|
+
*/
|
|
59
|
+
clonedFromTemplateId?: string;
|
|
45
60
|
}
|
|
46
61
|
export interface GraphNode {
|
|
47
62
|
id: string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ITemplateOperations } from "../interfaces/template-operations.interface";
|
|
2
|
+
import { IFirestoreClient } from "../interfaces/firestore-client.interface";
|
|
3
|
+
import { IAuthClient } from "../interfaces/auth-client.interface";
|
|
4
|
+
import { DocumentResult } from "../types/api-types";
|
|
5
|
+
/**
|
|
6
|
+
* Implementation of template operations.
|
|
7
|
+
*
|
|
8
|
+
* Templates are documents with `isTemplate: true` that can be cloned
|
|
9
|
+
* by premium users to create new documents.
|
|
10
|
+
*/
|
|
11
|
+
export declare class TemplateOperations implements ITemplateOperations {
|
|
12
|
+
private readonly firestore;
|
|
13
|
+
private readonly auth;
|
|
14
|
+
constructor(firestore: IFirestoreClient, auth: IAuthClient);
|
|
15
|
+
list(): Promise<DocumentResult[]>;
|
|
16
|
+
get(templateId: string): Promise<DocumentResult>;
|
|
17
|
+
clone(templateId: string, title?: string): Promise<DocumentResult>;
|
|
18
|
+
publish(templateId: string): Promise<void>;
|
|
19
|
+
unpublish(templateId: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Clones a node with updated IDs and references.
|
|
22
|
+
* Note: Firestore doesn't allow undefined values, so we conditionally include optional fields.
|
|
23
|
+
*/
|
|
24
|
+
private _cloneNode;
|
|
25
|
+
/**
|
|
26
|
+
* Clones an element with updated IDs and references.
|
|
27
|
+
* Note: Firestore doesn't allow undefined values, so we conditionally include optional fields.
|
|
28
|
+
*/
|
|
29
|
+
private _cloneElement;
|
|
30
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TemplateOperations = void 0;
|
|
4
|
+
const models_1 = require("../models");
|
|
5
|
+
const uuid_1 = require("../utils/uuid");
|
|
6
|
+
const api_errors_1 = require("../errors/api-errors");
|
|
7
|
+
// Default canvas dimensions
|
|
8
|
+
const DEFAULT_CANVAS_WIDTH = 1920;
|
|
9
|
+
const DEFAULT_CANVAS_HEIGHT = 1080;
|
|
10
|
+
/**
|
|
11
|
+
* Implementation of template operations.
|
|
12
|
+
*
|
|
13
|
+
* Templates are documents with `isTemplate: true` that can be cloned
|
|
14
|
+
* by premium users to create new documents.
|
|
15
|
+
*/
|
|
16
|
+
class TemplateOperations {
|
|
17
|
+
firestore;
|
|
18
|
+
auth;
|
|
19
|
+
constructor(firestore, auth) {
|
|
20
|
+
this.firestore = firestore;
|
|
21
|
+
this.auth = auth;
|
|
22
|
+
}
|
|
23
|
+
async list() {
|
|
24
|
+
this.auth.requireAuth();
|
|
25
|
+
// Check if user is admin (admins can see all templates including unpublished)
|
|
26
|
+
const isAdmin = await this.auth.isAdmin();
|
|
27
|
+
// Query templates where isTemplate == true
|
|
28
|
+
const documents = await this.firestore.queryCollection("documents", "isTemplate", true);
|
|
29
|
+
// Filter based on user role:
|
|
30
|
+
// - Admins can see all templates (published and unpublished)
|
|
31
|
+
// - Regular users can only see published templates
|
|
32
|
+
const templates = isAdmin
|
|
33
|
+
? documents
|
|
34
|
+
: documents.filter((doc) => {
|
|
35
|
+
const d = doc;
|
|
36
|
+
return d.isPublished === true;
|
|
37
|
+
});
|
|
38
|
+
// Return shallow documents without nodes
|
|
39
|
+
return templates.map((doc) => ({
|
|
40
|
+
...doc,
|
|
41
|
+
nodes: []
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
async get(templateId) {
|
|
45
|
+
this.auth.requireAuth();
|
|
46
|
+
const docData = await this.firestore.getDocument(`documents/${templateId}`);
|
|
47
|
+
if (!docData) {
|
|
48
|
+
throw new api_errors_1.NotFoundError(`Template ${templateId} not found`);
|
|
49
|
+
}
|
|
50
|
+
const docWithFlags = docData;
|
|
51
|
+
// Verify it's actually a template
|
|
52
|
+
if (!docWithFlags.isTemplate) {
|
|
53
|
+
throw new api_errors_1.NotFoundError(`Document ${templateId} is not a template`);
|
|
54
|
+
}
|
|
55
|
+
// Check visibility: unpublished templates are only visible to admins
|
|
56
|
+
if (!docWithFlags.isPublished) {
|
|
57
|
+
const isAdmin = await this.auth.isAdmin();
|
|
58
|
+
if (!isAdmin) {
|
|
59
|
+
throw new api_errors_1.NotFoundError(`Template ${templateId} not found`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const nodes = await this.firestore.getCollection(`documents/${templateId}/nodes`);
|
|
63
|
+
return {
|
|
64
|
+
...docData,
|
|
65
|
+
nodes
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async clone(templateId, title) {
|
|
69
|
+
const userId = this.auth.requireAuth();
|
|
70
|
+
// Require premium access for cloning
|
|
71
|
+
await this.auth.requirePremium();
|
|
72
|
+
// Get the template with all its nodes
|
|
73
|
+
const template = await this.get(templateId);
|
|
74
|
+
// Build ID mapping for nodes and elements
|
|
75
|
+
const idMapping = new Map();
|
|
76
|
+
const nodes = template.nodes || [];
|
|
77
|
+
nodes.forEach((node) => {
|
|
78
|
+
idMapping.set(node.id, (0, uuid_1.generateUuid)());
|
|
79
|
+
node.elements.forEach((element) => {
|
|
80
|
+
idMapping.set(element.id, (0, uuid_1.generateUuid)());
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
// Determine new root node ID - fail fast if template's root node is missing
|
|
84
|
+
if (template.rootNodeId && !idMapping.has(template.rootNodeId)) {
|
|
85
|
+
throw new api_errors_1.ValidationError(`Template's root node (${template.rootNodeId}) is missing from template nodes`);
|
|
86
|
+
}
|
|
87
|
+
const newRootNodeId = template.rootNodeId
|
|
88
|
+
? idMapping.get(template.rootNodeId)
|
|
89
|
+
: (0, uuid_1.generateUuid)();
|
|
90
|
+
// Generate new document ID
|
|
91
|
+
const newDocId = (0, uuid_1.generateUuid)();
|
|
92
|
+
// Create the cloned document
|
|
93
|
+
const clonedDocument = {
|
|
94
|
+
schemaVersion: models_1.CURRENT_DOCUMENT_SCHEMA_VERSION,
|
|
95
|
+
title: title || `Copy of ${template.title}`,
|
|
96
|
+
content: template.content || "",
|
|
97
|
+
owner: userId,
|
|
98
|
+
sharedWith: [],
|
|
99
|
+
createdAt: new Date(),
|
|
100
|
+
rootNodeId: newRootNodeId,
|
|
101
|
+
isPremiumContent: template.isPremiumContent || false,
|
|
102
|
+
isTemplate: false,
|
|
103
|
+
clonedFromTemplateId: templateId,
|
|
104
|
+
nodes: [] // Nodes stored in sub-collection
|
|
105
|
+
};
|
|
106
|
+
// Clone all nodes with new IDs
|
|
107
|
+
const clonedNodes = nodes.map((node) => {
|
|
108
|
+
const newNodeId = idMapping.get(node.id);
|
|
109
|
+
return this._cloneNode(node, newNodeId, userId, idMapping);
|
|
110
|
+
});
|
|
111
|
+
// Write everything in a batch
|
|
112
|
+
const batch = this.firestore.batch();
|
|
113
|
+
batch.set(`documents/${newDocId}`, clonedDocument);
|
|
114
|
+
for (const node of clonedNodes) {
|
|
115
|
+
batch.set(`documents/${newDocId}/nodes/${node.id}`, node);
|
|
116
|
+
}
|
|
117
|
+
await batch.commit();
|
|
118
|
+
return {
|
|
119
|
+
id: newDocId,
|
|
120
|
+
...clonedDocument,
|
|
121
|
+
nodes: clonedNodes
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async publish(templateId) {
|
|
125
|
+
this.auth.requireAuth();
|
|
126
|
+
await this.auth.requireAdmin();
|
|
127
|
+
// Verify template exists
|
|
128
|
+
const docData = await this.firestore.getDocument(`documents/${templateId}`);
|
|
129
|
+
if (!docData) {
|
|
130
|
+
throw new api_errors_1.NotFoundError(`Template ${templateId} not found`);
|
|
131
|
+
}
|
|
132
|
+
if (!docData.isTemplate) {
|
|
133
|
+
throw new api_errors_1.NotFoundError(`Document ${templateId} is not a template`);
|
|
134
|
+
}
|
|
135
|
+
await this.firestore.updateDocument(`documents/${templateId}`, {
|
|
136
|
+
isPublished: true
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async unpublish(templateId) {
|
|
140
|
+
this.auth.requireAuth();
|
|
141
|
+
await this.auth.requireAdmin();
|
|
142
|
+
// Verify template exists
|
|
143
|
+
const docData = await this.firestore.getDocument(`documents/${templateId}`);
|
|
144
|
+
if (!docData) {
|
|
145
|
+
throw new api_errors_1.NotFoundError(`Template ${templateId} not found`);
|
|
146
|
+
}
|
|
147
|
+
if (!docData.isTemplate) {
|
|
148
|
+
throw new api_errors_1.NotFoundError(`Document ${templateId} is not a template`);
|
|
149
|
+
}
|
|
150
|
+
await this.firestore.updateDocument(`documents/${templateId}`, {
|
|
151
|
+
isPublished: false
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Clones a node with updated IDs and references.
|
|
156
|
+
* Note: Firestore doesn't allow undefined values, so we conditionally include optional fields.
|
|
157
|
+
*/
|
|
158
|
+
_cloneNode(node, newNodeId, userId, idMapping) {
|
|
159
|
+
const clonedElements = node.elements.map((element) => this._cloneElement(element, idMapping));
|
|
160
|
+
const clonedNode = {
|
|
161
|
+
id: newNodeId,
|
|
162
|
+
title: node.title,
|
|
163
|
+
content: node.content,
|
|
164
|
+
elements: clonedElements,
|
|
165
|
+
canvasWidth: node.canvasWidth || DEFAULT_CANVAS_WIDTH,
|
|
166
|
+
canvasHeight: node.canvasHeight || DEFAULT_CANVAS_HEIGHT,
|
|
167
|
+
level: node.level,
|
|
168
|
+
owner: userId
|
|
169
|
+
};
|
|
170
|
+
// Only include parentNodeId if it exists (Firestore doesn't allow undefined)
|
|
171
|
+
if (node.parentNodeId) {
|
|
172
|
+
clonedNode.parentNodeId = idMapping.get(node.parentNodeId) || node.parentNodeId;
|
|
173
|
+
}
|
|
174
|
+
return clonedNode;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Clones an element with updated IDs and references.
|
|
178
|
+
* Note: Firestore doesn't allow undefined values, so we conditionally include optional fields.
|
|
179
|
+
*/
|
|
180
|
+
_cloneElement(element, idMapping) {
|
|
181
|
+
const newElementId = idMapping.get(element.id) || (0, uuid_1.generateUuid)();
|
|
182
|
+
// Clone properties and update any ID references
|
|
183
|
+
const clonedProperties = { ...element.properties };
|
|
184
|
+
// Update connector endpoint references if this is a connector
|
|
185
|
+
if (element.elementType === "connector" && clonedProperties) {
|
|
186
|
+
if (clonedProperties["startAnchor"]?.elementId &&
|
|
187
|
+
idMapping.has(clonedProperties["startAnchor"].elementId)) {
|
|
188
|
+
clonedProperties["startAnchor"] = {
|
|
189
|
+
...clonedProperties["startAnchor"],
|
|
190
|
+
elementId: idMapping.get(clonedProperties["startAnchor"].elementId)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (clonedProperties["endAnchor"]?.elementId &&
|
|
194
|
+
idMapping.has(clonedProperties["endAnchor"].elementId)) {
|
|
195
|
+
clonedProperties["endAnchor"] = {
|
|
196
|
+
...clonedProperties["endAnchor"],
|
|
197
|
+
elementId: idMapping.get(clonedProperties["endAnchor"].elementId)
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const clonedElement = {
|
|
202
|
+
id: newElementId,
|
|
203
|
+
elementType: element.elementType,
|
|
204
|
+
x: element.x,
|
|
205
|
+
y: element.y,
|
|
206
|
+
width: element.width,
|
|
207
|
+
height: element.height,
|
|
208
|
+
rotation: element.rotation,
|
|
209
|
+
properties: clonedProperties
|
|
210
|
+
};
|
|
211
|
+
// Only include optional fields if they have values (Firestore doesn't allow undefined)
|
|
212
|
+
if (element.isLink !== undefined) {
|
|
213
|
+
clonedElement.isLink = element.isLink;
|
|
214
|
+
}
|
|
215
|
+
if (element.linkTarget) {
|
|
216
|
+
clonedElement.linkTarget = idMapping.get(element.linkTarget) || element.linkTarget;
|
|
217
|
+
}
|
|
218
|
+
return clonedElement;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
exports.TemplateOperations = TemplateOperations;
|
|
@@ -5,9 +5,11 @@ import { IAuthClient } from "../interfaces/auth-client.interface";
|
|
|
5
5
|
export declare class MockAuthClient implements IAuthClient {
|
|
6
6
|
private _currentUserId;
|
|
7
7
|
private _isPremium;
|
|
8
|
+
private _isAdmin;
|
|
8
9
|
constructor(options?: {
|
|
9
10
|
userId?: string;
|
|
10
11
|
isPremium?: boolean;
|
|
12
|
+
isAdmin?: boolean;
|
|
11
13
|
});
|
|
12
14
|
signIn(email: string, _password: string): Promise<void>;
|
|
13
15
|
signOut(): Promise<void>;
|
|
@@ -23,4 +25,10 @@ export declare class MockAuthClient implements IAuthClient {
|
|
|
23
25
|
* Sets the premium status for testing.
|
|
24
26
|
*/
|
|
25
27
|
setPremium(isPremium: boolean): void;
|
|
28
|
+
isAdmin(): Promise<boolean>;
|
|
29
|
+
requireAdmin(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Sets the admin status for testing.
|
|
32
|
+
*/
|
|
33
|
+
setAdmin(isAdmin: boolean): void;
|
|
26
34
|
}
|
|
@@ -8,9 +8,11 @@ const api_errors_1 = require("../errors/api-errors");
|
|
|
8
8
|
class MockAuthClient {
|
|
9
9
|
_currentUserId = null;
|
|
10
10
|
_isPremium = false;
|
|
11
|
+
_isAdmin = false;
|
|
11
12
|
constructor(options) {
|
|
12
13
|
this._currentUserId = options?.userId ?? null;
|
|
13
14
|
this._isPremium = options?.isPremium ?? false;
|
|
15
|
+
this._isAdmin = options?.isAdmin ?? false;
|
|
14
16
|
}
|
|
15
17
|
async signIn(email, _password) {
|
|
16
18
|
// Simulate successful sign-in
|
|
@@ -49,5 +51,20 @@ class MockAuthClient {
|
|
|
49
51
|
setPremium(isPremium) {
|
|
50
52
|
this._isPremium = isPremium;
|
|
51
53
|
}
|
|
54
|
+
async isAdmin() {
|
|
55
|
+
return this._isAdmin;
|
|
56
|
+
}
|
|
57
|
+
async requireAdmin() {
|
|
58
|
+
this.requireAuth();
|
|
59
|
+
if (!this._isAdmin) {
|
|
60
|
+
throw new api_errors_1.PermissionError("Admin access required");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Sets the admin status for testing.
|
|
65
|
+
*/
|
|
66
|
+
setAdmin(isAdmin) {
|
|
67
|
+
this._isAdmin = isAdmin;
|
|
68
|
+
}
|
|
52
69
|
}
|
|
53
70
|
exports.MockAuthClient = MockAuthClient;
|
|
@@ -13,7 +13,7 @@ export declare class MockFirestoreClient implements IFirestoreClient {
|
|
|
13
13
|
getCollection<T>(path: string): Promise<(T & {
|
|
14
14
|
id: string;
|
|
15
15
|
})[]>;
|
|
16
|
-
queryCollection<T>(path: string, field: string, value: string): Promise<(T & {
|
|
16
|
+
queryCollection<T>(path: string, field: string, value: string | number | boolean | null): Promise<(T & {
|
|
17
17
|
id: string;
|
|
18
18
|
})[]>;
|
|
19
19
|
batch(): IBatchWriter;
|
|
@@ -201,6 +201,84 @@ export interface UmlNoteInput extends BaseElementInput {
|
|
|
201
201
|
/** Stroke width in pixels */
|
|
202
202
|
strokeWidth?: number;
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Input for creating a triangle element.
|
|
206
|
+
*/
|
|
207
|
+
export interface TriangleInput extends BaseElementInput {
|
|
208
|
+
type: "triangle";
|
|
209
|
+
/** Fill color (hex, e.g., "#FF5733") */
|
|
210
|
+
fillColor?: string;
|
|
211
|
+
/** Stroke color (hex) */
|
|
212
|
+
strokeColor?: string;
|
|
213
|
+
/** Stroke width in pixels */
|
|
214
|
+
strokeWidth?: number;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Input for creating a diamond element.
|
|
218
|
+
*/
|
|
219
|
+
export interface DiamondInput extends BaseElementInput {
|
|
220
|
+
type: "diamond";
|
|
221
|
+
/** Fill color (hex, e.g., "#FF5733") */
|
|
222
|
+
fillColor?: string;
|
|
223
|
+
/** Stroke color (hex) */
|
|
224
|
+
strokeColor?: string;
|
|
225
|
+
/** Stroke width in pixels */
|
|
226
|
+
strokeWidth?: number;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Input for creating a hexagon element.
|
|
230
|
+
*/
|
|
231
|
+
export interface HexagonInput extends BaseElementInput {
|
|
232
|
+
type: "hexagon";
|
|
233
|
+
/** Fill color (hex, e.g., "#FF5733") */
|
|
234
|
+
fillColor?: string;
|
|
235
|
+
/** Stroke color (hex) */
|
|
236
|
+
strokeColor?: string;
|
|
237
|
+
/** Stroke width in pixels */
|
|
238
|
+
strokeWidth?: number;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Input for creating an ellipse element.
|
|
242
|
+
*/
|
|
243
|
+
export interface EllipseInput extends BaseElementInput {
|
|
244
|
+
type: "ellipse";
|
|
245
|
+
/** Fill color (hex, e.g., "#FF5733") */
|
|
246
|
+
fillColor?: string;
|
|
247
|
+
/** Stroke color (hex) */
|
|
248
|
+
strokeColor?: string;
|
|
249
|
+
/** Stroke width in pixels */
|
|
250
|
+
strokeWidth?: number;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Input for creating a line element.
|
|
254
|
+
*/
|
|
255
|
+
export interface LineInput extends BaseElementInput {
|
|
256
|
+
type: "line";
|
|
257
|
+
/** Stroke color (hex) */
|
|
258
|
+
strokeColor?: string;
|
|
259
|
+
/** Stroke width in pixels */
|
|
260
|
+
strokeWidth?: number;
|
|
261
|
+
/** Line style (reuses LineStyle from connectors) */
|
|
262
|
+
lineStyle?: LineStyle;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Direction for block arrow elements.
|
|
266
|
+
*/
|
|
267
|
+
export type BlockArrowDirection = "right" | "left" | "up" | "down";
|
|
268
|
+
/**
|
|
269
|
+
* Input for creating a block arrow element.
|
|
270
|
+
*/
|
|
271
|
+
export interface BlockArrowInput extends BaseElementInput {
|
|
272
|
+
type: "block-arrow";
|
|
273
|
+
/** Fill color (hex, e.g., "#FF5733") */
|
|
274
|
+
fillColor?: string;
|
|
275
|
+
/** Stroke color (hex) */
|
|
276
|
+
strokeColor?: string;
|
|
277
|
+
/** Stroke width in pixels */
|
|
278
|
+
strokeWidth?: number;
|
|
279
|
+
/** Arrow direction */
|
|
280
|
+
direction?: BlockArrowDirection;
|
|
281
|
+
}
|
|
204
282
|
/**
|
|
205
283
|
* Input for creating a custom shape element.
|
|
206
284
|
*/
|
|
@@ -220,7 +298,7 @@ export interface CustomShapeInput extends BaseElementInput {
|
|
|
220
298
|
/**
|
|
221
299
|
* Union of all element input types.
|
|
222
300
|
*/
|
|
223
|
-
export type AnyElementInput = RectangleInput | TextInput | ConnectorInput | UmlClassInput | UmlInterfaceInput | UmlComponentInput | UmlPackageInput | UmlArtifactInput | UmlNoteInput | CustomShapeInput;
|
|
301
|
+
export type AnyElementInput = RectangleInput | TextInput | ConnectorInput | UmlClassInput | UmlInterfaceInput | UmlComponentInput | UmlPackageInput | UmlArtifactInput | UmlNoteInput | TriangleInput | DiamondInput | HexagonInput | EllipseInput | LineInput | BlockArrowInput | CustomShapeInput;
|
|
224
302
|
/**
|
|
225
303
|
* Input for updating an existing element.
|
|
226
304
|
*/
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { IElementTypeValidator } from "../../interfaces/element-validator.interface";
|
|
2
|
+
import { AnyElementInput } from "../../types/element-input-types";
|
|
3
|
+
import { BaseElementValidator } from "./base-element-validator";
|
|
4
|
+
/**
|
|
5
|
+
* Base validator for basic shapes (triangle, diamond, hexagon, ellipse).
|
|
6
|
+
* All these shapes share the same properties: fillColor, strokeColor, strokeWidth.
|
|
7
|
+
*/
|
|
8
|
+
declare abstract class BasicShapeValidator extends BaseElementValidator implements IElementTypeValidator {
|
|
9
|
+
abstract readonly elementType: string;
|
|
10
|
+
validate(input: AnyElementInput): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Validator for triangle elements.
|
|
14
|
+
*/
|
|
15
|
+
export declare class TriangleValidator extends BasicShapeValidator {
|
|
16
|
+
readonly elementType = "triangle";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Validator for diamond elements.
|
|
20
|
+
*/
|
|
21
|
+
export declare class DiamondValidator extends BasicShapeValidator {
|
|
22
|
+
readonly elementType = "diamond";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validator for hexagon elements.
|
|
26
|
+
*/
|
|
27
|
+
export declare class HexagonValidator extends BasicShapeValidator {
|
|
28
|
+
readonly elementType = "hexagon";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validator for ellipse elements.
|
|
32
|
+
*/
|
|
33
|
+
export declare class EllipseValidator extends BasicShapeValidator {
|
|
34
|
+
readonly elementType = "ellipse";
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EllipseValidator = exports.HexagonValidator = exports.DiamondValidator = exports.TriangleValidator = void 0;
|
|
4
|
+
const api_errors_1 = require("../../errors/api-errors");
|
|
5
|
+
const base_element_validator_1 = require("./base-element-validator");
|
|
6
|
+
/**
|
|
7
|
+
* Base validator for basic shapes (triangle, diamond, hexagon, ellipse).
|
|
8
|
+
* All these shapes share the same properties: fillColor, strokeColor, strokeWidth.
|
|
9
|
+
*/
|
|
10
|
+
class BasicShapeValidator extends base_element_validator_1.BaseElementValidator {
|
|
11
|
+
validate(input) {
|
|
12
|
+
if (input.type !== this.elementType) {
|
|
13
|
+
throw new api_errors_1.ValidationError(`Expected type "${this.elementType}", got "${input.type}"`, "type");
|
|
14
|
+
}
|
|
15
|
+
const shapeInput = input;
|
|
16
|
+
// Validate common properties
|
|
17
|
+
this.validateCommon(shapeInput);
|
|
18
|
+
// Validate shape-specific properties
|
|
19
|
+
this.validateColor(shapeInput.fillColor, "fillColor");
|
|
20
|
+
this.validateColor(shapeInput.strokeColor, "strokeColor");
|
|
21
|
+
this.validateStrokeWidth(shapeInput.strokeWidth, "strokeWidth");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validator for triangle elements.
|
|
26
|
+
*/
|
|
27
|
+
class TriangleValidator extends BasicShapeValidator {
|
|
28
|
+
elementType = "triangle";
|
|
29
|
+
}
|
|
30
|
+
exports.TriangleValidator = TriangleValidator;
|
|
31
|
+
/**
|
|
32
|
+
* Validator for diamond elements.
|
|
33
|
+
*/
|
|
34
|
+
class DiamondValidator extends BasicShapeValidator {
|
|
35
|
+
elementType = "diamond";
|
|
36
|
+
}
|
|
37
|
+
exports.DiamondValidator = DiamondValidator;
|
|
38
|
+
/**
|
|
39
|
+
* Validator for hexagon elements.
|
|
40
|
+
*/
|
|
41
|
+
class HexagonValidator extends BasicShapeValidator {
|
|
42
|
+
elementType = "hexagon";
|
|
43
|
+
}
|
|
44
|
+
exports.HexagonValidator = HexagonValidator;
|
|
45
|
+
/**
|
|
46
|
+
* Validator for ellipse elements.
|
|
47
|
+
*/
|
|
48
|
+
class EllipseValidator extends BasicShapeValidator {
|
|
49
|
+
elementType = "ellipse";
|
|
50
|
+
}
|
|
51
|
+
exports.EllipseValidator = EllipseValidator;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IElementTypeValidator } from "../../interfaces/element-validator.interface";
|
|
2
|
+
import { AnyElementInput } from "../../types/element-input-types";
|
|
3
|
+
import { BaseElementValidator } from "./base-element-validator";
|
|
4
|
+
/**
|
|
5
|
+
* Validator for block arrow elements.
|
|
6
|
+
*/
|
|
7
|
+
export declare class BlockArrowValidator extends BaseElementValidator implements IElementTypeValidator {
|
|
8
|
+
readonly elementType = "block-arrow";
|
|
9
|
+
validate(input: AnyElementInput): void;
|
|
10
|
+
private validateDirection;
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BlockArrowValidator = void 0;
|
|
4
|
+
const api_errors_1 = require("../../errors/api-errors");
|
|
5
|
+
const base_element_validator_1 = require("./base-element-validator");
|
|
6
|
+
/**
|
|
7
|
+
* Validator for block arrow elements.
|
|
8
|
+
*/
|
|
9
|
+
class BlockArrowValidator extends base_element_validator_1.BaseElementValidator {
|
|
10
|
+
elementType = "block-arrow";
|
|
11
|
+
validate(input) {
|
|
12
|
+
if (input.type !== "block-arrow") {
|
|
13
|
+
throw new api_errors_1.ValidationError(`Expected type "block-arrow", got "${input.type}"`, "type");
|
|
14
|
+
}
|
|
15
|
+
const blockArrowInput = input;
|
|
16
|
+
// Validate common properties
|
|
17
|
+
this.validateCommon(blockArrowInput);
|
|
18
|
+
// Validate block-arrow-specific properties
|
|
19
|
+
this.validateColor(blockArrowInput.fillColor, "fillColor");
|
|
20
|
+
this.validateColor(blockArrowInput.strokeColor, "strokeColor");
|
|
21
|
+
this.validateStrokeWidth(blockArrowInput.strokeWidth, "strokeWidth");
|
|
22
|
+
this.validateDirection(blockArrowInput.direction);
|
|
23
|
+
}
|
|
24
|
+
validateDirection(value) {
|
|
25
|
+
if (value === undefined)
|
|
26
|
+
return;
|
|
27
|
+
if (typeof value !== "string") {
|
|
28
|
+
throw new api_errors_1.ValidationError("direction must be a string", "direction");
|
|
29
|
+
}
|
|
30
|
+
const validDirections = ["right", "left", "up", "down"];
|
|
31
|
+
if (!validDirections.includes(value)) {
|
|
32
|
+
throw new api_errors_1.ValidationError(`direction must be one of: ${validDirections.join(", ")}`, "direction");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.BlockArrowValidator = BlockArrowValidator;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IElementTypeValidator } from "../../interfaces/element-validator.interface";
|
|
2
|
+
import { AnyElementInput } from "../../types/element-input-types";
|
|
3
|
+
import { BaseElementValidator } from "./base-element-validator";
|
|
4
|
+
/**
|
|
5
|
+
* Validator for line elements.
|
|
6
|
+
*/
|
|
7
|
+
export declare class LineValidator extends BaseElementValidator implements IElementTypeValidator {
|
|
8
|
+
readonly elementType = "line";
|
|
9
|
+
validate(input: AnyElementInput): void;
|
|
10
|
+
private validateLineStyle;
|
|
11
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LineValidator = void 0;
|
|
4
|
+
const api_errors_1 = require("../../errors/api-errors");
|
|
5
|
+
const base_element_validator_1 = require("./base-element-validator");
|
|
6
|
+
/**
|
|
7
|
+
* Validator for line elements.
|
|
8
|
+
*/
|
|
9
|
+
class LineValidator extends base_element_validator_1.BaseElementValidator {
|
|
10
|
+
elementType = "line";
|
|
11
|
+
validate(input) {
|
|
12
|
+
if (input.type !== "line") {
|
|
13
|
+
throw new api_errors_1.ValidationError(`Expected type "line", got "${input.type}"`, "type");
|
|
14
|
+
}
|
|
15
|
+
const lineInput = input;
|
|
16
|
+
// Validate common properties
|
|
17
|
+
this.validateCommon(lineInput);
|
|
18
|
+
// Validate line-specific properties
|
|
19
|
+
this.validateColor(lineInput.strokeColor, "strokeColor");
|
|
20
|
+
this.validateStrokeWidth(lineInput.strokeWidth, "strokeWidth");
|
|
21
|
+
this.validateLineStyle(lineInput.lineStyle);
|
|
22
|
+
}
|
|
23
|
+
validateLineStyle(value) {
|
|
24
|
+
if (value === undefined)
|
|
25
|
+
return;
|
|
26
|
+
if (typeof value !== "string") {
|
|
27
|
+
throw new api_errors_1.ValidationError("lineStyle must be a string", "lineStyle");
|
|
28
|
+
}
|
|
29
|
+
const validStyles = ["solid", "dashed", "dotted"];
|
|
30
|
+
if (!validStyles.includes(value)) {
|
|
31
|
+
throw new api_errors_1.ValidationError(`lineStyle must be one of: ${validStyles.join(", ")}`, "lineStyle");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.LineValidator = LineValidator;
|
|
@@ -6,6 +6,9 @@ const rectangle_validator_1 = require("./element-type-validators/rectangle-valid
|
|
|
6
6
|
const text_validator_1 = require("./element-type-validators/text-validator");
|
|
7
7
|
const connector_validator_1 = require("./element-type-validators/connector-validator");
|
|
8
8
|
const uml_validators_1 = require("./element-type-validators/uml-validators");
|
|
9
|
+
const basic_shape_validators_1 = require("./element-type-validators/basic-shape-validators");
|
|
10
|
+
const line_validator_1 = require("./element-type-validators/line-validator");
|
|
11
|
+
const block_arrow_validator_1 = require("./element-type-validators/block-arrow-validator");
|
|
9
12
|
const custom_shape_validator_1 = require("./element-type-validators/custom-shape-validator");
|
|
10
13
|
/**
|
|
11
14
|
* Registry for element type validators.
|
|
@@ -31,6 +34,12 @@ class ElementValidatorRegistry {
|
|
|
31
34
|
this.register(new uml_validators_1.UmlPackageValidator());
|
|
32
35
|
this.register(new uml_validators_1.UmlArtifactValidator());
|
|
33
36
|
this.register(new uml_validators_1.UmlNoteValidator());
|
|
37
|
+
this.register(new basic_shape_validators_1.TriangleValidator());
|
|
38
|
+
this.register(new basic_shape_validators_1.DiamondValidator());
|
|
39
|
+
this.register(new basic_shape_validators_1.HexagonValidator());
|
|
40
|
+
this.register(new basic_shape_validators_1.EllipseValidator());
|
|
41
|
+
this.register(new line_validator_1.LineValidator());
|
|
42
|
+
this.register(new block_arrow_validator_1.BlockArrowValidator());
|
|
34
43
|
}
|
|
35
44
|
register(validator) {
|
|
36
45
|
this.validators.set(validator.elementType, validator);
|