Files
speckle-server/docs/speckle-compatibility-contract.md
T

14 KiB

Speckle Compatibility Contract

1. Mục tiêu

Tài liệu này định nghĩa contract tối thiểu mà nền tảng ASP.NET/Blazor mới phải giữ để Speckle Connector, Speckle SDK và Speckle Viewer hiện tại có thể làm việc với server mới.

Mục tiêu không phải clone toàn bộ Speckle Server. Mục tiêu là giữ đúng các điểm chạm quan trọng:

  • Connector đăng nhập được.
  • Connector đọc danh sách project/model/version được.
  • Connector tạo project/model/version được.
  • Connector upload object graph được.
  • Connector pull object graph được.
  • Viewer load được model/version/object.
  • File upload/import cơ bản hoạt động.

2. Mapping thuật ngữ

Speckle legacy API mới Ý nghĩa
Stream Project Không gian chứa model, version, user role.
Branch Model Nhánh dữ liệu trong project.
Commit Version Snapshot trỏ tới root object.
Object Object JSON object immutable, có id hash.
referencedObject Root Object Object gốc của version.
Blob Blob/File File thô hoặc attachment.

Quy tắc:

  • API mới nên dùng Project/Model/Version.
  • Compatibility API vẫn phải trả Stream/Branch/Commit cho client legacy.
  • ID có thể dùng cùng giá trị giữa Project và Stream để giảm mapping phức tạp.

3. Client cần support

Client Mức support MVP
Speckle Connector Desktop Manager Login và lưu account.
Speckle Connectors v2/v3 Push/Pull cơ bản.
Speckle SDK Auth, stream/project query, object upload/download.
Speckle Viewer Load object stream và viewer resource.
Speckle Web cũ Không phải mục tiêu chính, chỉ support nếu không phát sinh thêm contract lớn.

4. Auth contract

4.1 Default app IDs

Server mới cần pre-register các app ID phổ biến:

App ID Vai trò
spklwebapp Speckle Web Manager.
sdm Speckle Desktop Manager.
sca Speckle Connector legacy.
sdas Speckle Desktop Auth Service.
sdui Desktop connector UI.
connectrV3 Desktop Connectors v3.
spklexcel Excel connector nếu cần.
spklpwerbi PowerBI connector nếu cần.

4.2 OAuth-like flow

GET /auth/accesscode

Mục tiêu: browser flow để user approve connector/app.

Yêu cầu:

  • Nhận query params tương thích Speckle app flow.
  • Nếu user chưa đăng nhập, chuyển đến trang login.
  • Nếu app chưa trusted, hiển thị màn approve.
  • Tạo access code ngắn hạn.
  • Redirect về redirectUrl của app hoặc localhost callback connector.

POST /auth/token

Mục tiêu: đổi access code thành bearer token.

Yêu cầu:

  • Nhận payload tương thích connector.
  • Validate app ID, access code, redirect URL/challenge nếu có.
  • Trả token dạng bearer để dùng với GraphQL và REST.
  • Token phải có scope đúng.

Token storage:

  • Token public trả cho client có thể giữ format Speckle: 10 ký tự id + 32 ký tự secret.
  • DB chỉ lưu token id, token digest, owner, scopes, appId, expiry, last chars.
  • Không lưu plain token secret.

POST /auth/logout

Mục tiêu: revoke session web hoặc token hiện tại nếu client gọi.

5. Scope và permission

Scope tối thiểu:

Scope Ý nghĩa
streams:read Đọc project/model/version/object.
streams:write Tạo/sửa project/model/version, upload object.
profile:read Đọc thông tin user hiện tại.
profile:email Đọc email user.
users:read Đọc collaborator/team.
users:invite Invite user nếu connector/web cần.

Project role tối thiểu:

Role Quyền
Owner Full quyền project.
Contributor Upload, tạo model/version, comment.
Reviewer Xem, comment, issue, approval nếu được giao.
Viewer Chỉ xem/pull.

6. REST object endpoints

6.1 POST /objects/{streamId}

Mục tiêu: upload object batch.

Yêu cầu compatibility:

  • streamId được map sang projectId.
  • Auth cần streams:write và quyền ghi project.
  • Request là multipart/form-data.
  • Mỗi file part chứa JSON array object.
  • MIME hợp lệ: application/gzip, text/plain, application/json, application/octet-stream.
  • Nếu gzip, server phải gunzip rồi parse JSON.
  • Mỗi object phải có id/hash hợp lệ hoặc server tính theo rule Speckle.
  • Server lưu object dạng immutable và deduplicate.
  • Response thành công: HTTP 201.

Lỗi tối thiểu:

Điều kiện Status
Không có quyền 401 hoặc 403.
Project không tồn tại 404.
Payload không parse được 400.
Batch quá lớn 413.

6.2 GET /objects/{streamId}/{objectId}

Mục tiêu: download object và children phục vụ connector/viewer legacy.

Yêu cầu:

  • Auth cần streams:read hoặc project public.
  • Trả dữ liệu object dạng JSON/gzip theo hành vi Speckle hiện tại.
  • Phải support object root và traversal children nếu client yêu cầu qua query hiện có.

6.3 GET /objects/{streamId}/{objectId}/single

Mục tiêu: download một object đơn lẻ.

Yêu cầu:

  • Không expand children.
  • Dùng cho client cần object cụ thể.

6.4 POST /api/diff/{streamId}

Mục tiêu: client gửi danh sách object id, server trả danh sách object còn thiếu.

Yêu cầu:

  • Auth cần streams:write.
  • Body nhận danh sách object ids theo format Speckle legacy.
  • Response là danh sách object ids server chưa có.
  • Endpoint này giúp giảm upload trùng.

6.5 POST /api/getobjects/{streamId}

Mục tiêu: client pull nhiều object theo danh sách id.

Yêu cầu:

  • Auth cần streams:read.
  • Body có field objects là JSON string của array object ids.
  • Response gzip.
  • Nếu Accept: text/plain, response content type là text/plain; charset=UTF-8.
  • Format text stream: mỗi dòng objectId<TAB>json.

6.6 POST /api/v2/projects/{streamId}/object-stream

Mục tiêu: object streaming v2 cho viewer/client mới.

Yêu cầu:

  • Auth cần streams:read hoặc project public.
  • Body:
{
  "objectIds": ["object-id-1", "object-id-2"],
  "attributeMask": {
    "include": ["id", "speckle_type"]
  }
}
  • attributeMask có thể là include hoặc exclude.
  • Response gzip text stream.
  • streamId vẫn dùng tên route legacy nhưng map sang projectId.

7. Blob/file endpoints

7.1 POST /api/stream/{streamId}/blob

Upload blob vào project.

Yêu cầu:

  • Auth cần quyền ghi comment/blob.
  • Request multipart.
  • Response 201 với uploadResults.

7.2 POST /api/stream/{streamId}/blob/diff

Client gửi danh sách blob ids, server trả blob ids chưa có.

7.3 GET /api/stream/{streamId}/blob/{blobId}

Download blob.

Yêu cầu:

  • Support public project nếu policy cho phép.
  • Header Content-Disposition theo filename.

7.4 DELETE /api/stream/{streamId}/blob/{blobId}

Xóa blob nếu user có quyền.

7.5 GET /api/stream/{streamId}/blobs

List blob metadata, có thể filter bằng fileName.

7.6 POST /api/file/{fileType}/{streamId}/{branchName?}

Legacy file upload/import endpoint.

Yêu cầu:

  • Giữ cho connector/client cũ.
  • branchName mặc định main.
  • Sau upload phải tạo file upload record và enqueue import job.
  • Response 201 với uploadResults.
  • Nên trả warning deprecation nhưng không phá client.

8. Viewer derivative endpoints

8.1 GET /api/viewer-derivatives/{projectId}/{versionId}/manifest

Mục tiêu: viewer lấy manifest derivative nếu có.

Response khi chưa sẵn sàng:

{
  "status": "queued-or-processing",
  "progress": 0.5,
  "stage": "generating-tiles"
}

Response khi sẵn sàng:

{
  "status": "ready",
  "manifest": {}
}

8.2 GET /api/viewer-derivatives/{projectId}/{versionId}/artifacts/*

Mục tiêu: viewer tải tile/buffer/metadata artifact.

Yêu cầu:

  • Auth đọc project.
  • Cache immutable cho artifact đã publish.

8.3 Worker publish endpoints

Các endpoint nội bộ cho worker:

  • PUT /api/viewer-derivatives/{projectId}/{versionId}/artifacts/*
  • POST /api/viewer-derivatives/{projectId}/{versionId}/publish
  • POST /api/viewer-derivatives/{projectId}/{versionId}/fail

Chỉ internal worker token có quyền gọi.

9. GraphQL compatibility surface

9.1 Query tối thiểu

Server mới cần support tối thiểu:

type Query {
  activeUser: User
  serverInfo: ServerInfo
  project(id: String!): Project!
  stream(id: String!): Stream
  streams(query: String, limit: Int, cursor: String): UserStreamCollection
}

Project fields tối thiểu:

type Project {
  id: ID!
  name: String!
  description: String
  visibility: ProjectVisibility!
  role: String
  createdAt: DateTime!
  updatedAt: DateTime!
  models(cursor: String, limit: Int, filter: ProjectModelsFilter): ModelCollection!
  model(id: String!): Model!
  modelByName(name: String!): Model!
  versions(limit: Int, cursor: String): VersionCollection!
  version(id: String!): Version!
  object(id: String!): Object
}

Legacy Stream fields tối thiểu:

type Stream {
  id: String!
  name: String!
  description: String
  isPublic: Boolean!
  role: String
  branches(limit: Int, cursor: String): BranchCollection
  branch(name: String): Branch
  commits(limit: Int, cursor: String): CommitCollection
  commit(id: String): Commit
  object(id: String!): Object
}

9.2 Mutation tối thiểu

API mới:

type Mutation {
  projectMutations: ProjectMutations!
  modelMutations: ModelMutations!
  versionMutations: VersionMutations!
  fileUploadMutations: FileUploadMutations!
}

Legacy:

type Mutation {
  streamCreate(stream: StreamCreateInput!): String
  streamUpdate(stream: StreamUpdateInput!): Boolean!
  streamDelete(id: String!): Boolean!
  branchCreate(branch: BranchCreateInput!): String!
  branchUpdate(branch: BranchUpdateInput!): Boolean!
  branchDelete(branch: BranchDeleteInput!): Boolean!
  commitCreate(commit: CommitCreateInput!): String!
  commitUpdate(commit: CommitUpdateInput!): Boolean!
  commitDelete(commit: CommitDeleteInput!): Boolean!
  commitReceive(input: CommitReceivedInput!): Boolean!
  objectCreate(objectInput: ObjectCreateInput!): [String!]!
}

9.3 Model/Version fields tối thiểu

type Model {
  id: ID!
  name: String!
  displayName: String!
  description: String
  createdAt: DateTime!
  updatedAt: DateTime!
  versions(limit: Int, cursor: String, filter: ModelVersionsFilter): VersionCollection!
  version(id: String!): Version!
  projectId: String!
}

type Version {
  id: ID!
  referencedObject: String
  message: String
  sourceApplication: String
  createdAt: DateTime!
  totalChildrenCount: Int
  parents: [String]
  model: Model!
}

Legacy:

type Branch {
  id: String!
  name: String!
  commits(limit: Int, cursor: String): CommitCollection
}

type Commit {
  id: String!
  referencedObject: String!
  message: String
  sourceApplication: String
  totalChildrenCount: Int
  branchName: String
  parents: [String]
  createdAt: DateTime
}

10. Object identity contract

Yêu cầu:

  • Object id phải dài 32 ký tự nếu client gửi theo Speckle hash legacy.
  • Nếu object có id hoặc hash, server phải tôn trọng nếu hợp lệ.
  • Nếu thiếu id, server tính hash theo thuật toán tương thích Speckle.
  • Dedup key tối thiểu: (projectId, objectId).
  • Object data lưu nguyên JSON để pull ra không mất field.
  • Không normalize JSON làm thay đổi semantic object khi trả về client.

Acceptance test:

  • Dùng cùng một object JSON, server mới phải tạo cùng object id như Speckle Server hoặc chấp nhận id client gửi.
  • Upload cùng batch hai lần không tạo bản ghi trùng.
  • Pull object sau upload trả data tương đương payload ban đầu.

11. Error compatibility

Không cần clone toàn bộ error message, nhưng cần giữ:

  • HTTP status đúng.
  • JSON body có error hoặc GraphQL errors.
  • Không trả HTML error page cho API.
  • Validation lỗi payload phải rõ để connector log được.

12. Contract test bắt buộc

MVP chỉ được xem là tương thích khi pass các test:

  • Connector login qua /auth/accesscode/auth/token.
  • Tạo project bằng legacy streamCreate.
  • Tạo model bằng legacy branchCreate.
  • Upload object qua POST /objects/{projectId}.
  • Diff object qua POST /api/diff/{projectId}.
  • Tạo version bằng legacy commitCreate.
  • Pull version object bằng GET /objects/{projectId}/{objectId}.
  • Pull nhiều object bằng POST /api/getobjects/{projectId}.
  • Viewer load version qua object stream.
  • Token read-only không được upload.
  • Private project không đọc được nếu thiếu quyền.

13. Chính sách thay đổi contract

  • Mọi endpoint trong tài liệu này là compatibility contract, không xoá trong MVP.
  • Nếu cần thay đổi request/response, thêm version route mới thay vì sửa route cũ.
  • Ghi contract test trước khi implement.
  • Khi support thêm connector version mới, bổ sung query/mutation/endpoint thật vào tài liệu này.