openapi: '3.1.0' info: title: eCorpus version: '1.0.0' summary: HTTP API for eCorpus description: | This HTTP API provides all necessary routes to access and edit scenes stored on an eCorpus instance under the `/scenes` path. Additionally it provides a number of namespaced utilities for **users** management (`/users`), **authentication** and ACL edition (`/auth`), changes **history** management (`/history`) or gathering scenes under **collections** (`/tags`). It provides some webDAV utility routes for the `/scenes` resources but is far from [Class 1](http://www.webdav.org/specs/rfc4918.html#rfc.section.18.1) Compliance: Only routes that are necessary for proper [Voyager](https://smithsonian.github.io/dpo-voyager/) support are implemented. WebDAV-specific methods are defined as an extension with a `x-` prefix to prevent breaking openAPI tooling Other namespaces tends to adhere to a stricter REST philosophy where possible. contact: name: eCorpus Support url: https://github.com/Holusion/eCorpus email: contact@holusion.com license: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0.html servers: - url: https://ecorpus.holusion.com tags: - name: admin description: Administrative tasks routes - name: auth description: Authentication, access control querying and edition routes. - name: history description: | history management utilities for the `/scenes` namespace. Scene names in `/history` directly and uniquely maps to scenes in `/scenes`. - name: scenes description: | Where all the actual data is stored API design for the `/scenes/*` makes use of the liberal definition of [GET for collections](https://datatracker.ietf.org/doc/html/rfc2518#section-8.4) in the webDAV specification to return well-defined JSON documents for those queries, allowing most use cases to bypass cumbersome PROPFIND queries - name: tags description: | collections (tags) management routes. - name: users description: Users management paths: /scenes: get: tags: [scenes] operationId: getScenes description: | get a list of scenes with optional search parameters. Similar to PROPFIND but will return JSON. Provides advanced search and pagination semantics parameters: - name: id in: query style: form explode: true schema: {type: array, items: {$ref: "#/components/schemas/Uid"}} description: | Scenes IDs to get. If provided, will return only those scenes. Mainly useful for zip download - name: name in: query style: form explode: true schema: {type: array, items: {type: string}} description: | Scene names to get. If provided, will return only those scenes, ignoring the `match` parameter - name: match in: query description: | Matches a string against a scene's name, author, authorized users and document meta. See examples for the accepted syntax schema: type: string examples: simple: value: foo description: | Match scenes that contains the string "foo" words: value: foo bar description: | Match scenes that contains both words in unrelated places. quoted: value: '"foo bar"' description: | Match scenes that contains those words next to each other - name: access in: query style: form explode: true description: | Match only scenes for which the current user have the requested access rights schema: type: array items: { $ref: "#/components/schemas/AccessType"} - name: limit in: query description: | Limit the number of returned results. A hard maximum of 100 is enforced Ordering is guaranteed to stay stable when `orderBy:ctime`is used` schema: {type: number, format: int32, minimum: 1, maximum: 100} - name: offset in: query description: Skip the first *n* results. Use with `limit` to paginate results. schema: {type: number, format: int32, minimum: 0} - name: orderBy in: query description: Field to use for results sorting schema: type: string enum: ["name", "ctime", "mtime"] - name: orderDirection in: query description: Switch between ascending and descending order schema: type: string enum: ["asc", "desc"] responses: '200': description: | A list of scenes matching this query. By default, returns a JSON object containing basic metadata for all scenes. Can be used to return a ZIP archive of the matched scenes content. headers: Etag: {$ref: "#/components/schemas/Etag"} Last-Modified: {$ref: "#/components/schemas/LastModified"} content: application/json: schema: type: object required: ["scenes"] properties: scenes: type: array items: $ref: "#/components/schemas/Scene" text/plain: schema: type: string example: "foo\nbar\nbaz" application/zip: schema: type: string format: binary post: tags: [scenes] operationId: postScenes description: import an archive of scenes to be extracted into the `scenes/` folder requestBody: required: true description: | A zip file that contains an archived scene hierarchy. eCorpus uses a simplified internal zip decoder that is guaranteed to handle only data exported from another eCorpus instance. If you wish to create an archive yourself, you have to ensure your archiver stores only uncompressed data using only supported ZIP features content: application/zip: schema: {type: string, format: binary} x-propfind: tags: [scenes] operationId: propfindScenes description: | list all readable content in `scenes/`. See [rfc4918](http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND) to learn more about PROPFIND This is a minimal implementation that just lists contained elements, respecting the [Depth](http://www.webdav.org/specs/rfc4918.html#HEADER_Depth) header. While this is not paginated, expect future changes to implement limits on the results' size. responses: '200': description: a PROPFIND multistatus response content: application/xml: schema: { $ref: "#/components/schemas/WebDAVMultistatus"} /scenes/{scene}: parameters: - $ref: '#/components/parameters/scene' get: tags: [scenes] operationId: getScene description: get a scene's metadata. responses: '200': description: The requested scene's data headers: Etag: {$ref: "#/components/schemas/Etag"} Last-Modified: {$ref: "#/components/schemas/LastModified"} content: application/json: { $ref: "#/components/schemas/Scene" } '404': description: scene not found. It doesn't exist or isn't readable with the current authentication x-mkcol: tags: [scenes] operationId: mkScene description: creates a new empty scene. This scene will essentially be invisible until populated x-propfind: tags: [scenes] operationId: propfindScene description: fetch the scene's content delete: tags: [scenes] operationId: deleteScene description: Archives a scene parameters: - name: archive in: query schema: {type: "boolean"} description: Set to false to force-delete scenes (instance administrators only). Otherwise archive it in a way that could be undone by administrators. Defaults to true responses: '204': description: "success" '401': description: | Unauthorized, either due to improper authentication or insufficient access rights. Archiving a scene requires "admin" rights over it, while force-delete requires instance-level administrative rights. post: tags: [scenes] operationId: postScene description: | creates a new scene using attached data. When a GLB model is provided as request body, creates a default scene, initialized with this model positionned at (0,0,0). requestBody: description: "scene initialization data" required: true content: model/gltf-binary: schema: type: string format: binary description: A `.glb` model file responses: '201': description: a new scene was created and initialized with a default `scene.svx.json` file '409': description: a scene with this name already exists patch: tags: [scenes] operationId: patchScene description: Edit scene's metadata requestBody: description: | Scene patch data using a partial object. Check schema for a list of editable fields. Permissions are additive, to reset a user to default permissions, explicitely set his permission to `null`. Tags are copied: if a `tags` field is provided, any tag not included will be removed. required: true content: application/json: schema: type: object required: [] properties: name: type: string permissions: type: object additionalProperties: $ref: "#/components/schemas/AccessType" tags: type: array items: type: string responses: '200': description: "The updated scene data" content: application/json: schema: { $ref: "#/components/schemas/Scene" } /scenes/{scene}/{file:.*}: parameters: - $ref: '#/components/parameters/scene' - $ref: '#/components/parameters/file' get: tags: [scenes] operationId: getFile description: get a file in scene content: default: description: | The file. It's `Content-Type` header should match the expected [MIME](https://developer.mozilla.org/fr/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) type. schema: { type: string, format: binary } put: tags: [scenes] operationId: putFile description: overwrite the file with new content requestBody: description: The file's data content: '*/*': schema: {type: string, format: binary } x-copy: tags: [scenes] operationId: copyFile description: copy a file to another location in the same scene x-move: tags: [scenes] operationId: moveFile description: move a file to another location in the same scene delete: tags: [scenes] operationId: deleteFile description: archives a file. It is still accessible through the history API x-mkcol: tags: [scenes] operationId: mkFolder description: creates a folder in a scene x-propfind: tags: [scenes] operationId: propfindFile description: get a file's properties /history/{scene}: parameters: - $ref: '#/components/parameters/scene' get: tags: [history] operationId: getHistory description: get a full history of a scene's modifications post: tags: [history] operationId: postHistory description: edit a scene's history /history/{scene}/files: parameters: - $ref: '#/components/parameters/scene' get: tags: [history] operationId: getFileHistory description: list all files in the scenes in their current state /tags: get: tags: [tags] operationId: getTags description: get a list of tags on this server /tags/{tag}: parameters: - name: tag in: path required: true schema: {type: "string"} description: name of a tag get: tags: [tags] operationId: getTag description: get all scenes associated with this tag /users: get: tags: [users] operationId: getUsers description: get a list of registered users responses: '200': description: An array of all users registered on this server content: application/json: schema: type: array items: { $ref: "#/components/schemas/User"} '401': $ref: "#/components/responses/HTTPError" post: tags: [users] operationId: postUser description: create a new user requestBody: description: the user to create content: application/json: schema: type: object required: ["username", "email", "password"] properties: username: {type: "string", example: "jdupont" } email: {type: "string", example: "jdupont@example.com" } password: {type: "string", example: "p@ssw0rd" } isAdministrator: {type: "boolean"} responses: '201': description: user created successfully '400': $ref: "#/components/responses/HTTPError" /users/{uid}: parameters: - name: uid in: path required: true schema: {type: "string", pattern: '^\d+$'} description: unique ID of an user (stays stable through user renames) delete: tags: [users] operationId: deleteUser description: delete a user patch: tags: [users] operationId: patchUser description: change a user's data /auth/login: get: tags: [auth] operationId: getAuth description: get login data post: tags: [auth] operationId: postAuth description: log-in to the server /auth/login/{username}/link: parameters: - name: username in: path required: true schema: {type: "string"} description: human-readable unique name of an user get: tags: [auth] operationId: getAuthLink description: get a login link for this user post: tags: [auth] operationId: postAuthLink description: generate and send a login link for this user /auth/logout: post: tags: [auth] operationId: postLogout description: delete this request's credentials /auth/access/{scene}: parameters: - $ref: '#/components/parameters/scene' get: tags: [auth] operationId: getAccess description: get a scene's access rights responses: '200': description: Access map defined for this scene content: application/json: schema: type: array items: type: object required: ["uid", "username", "access"] properties: uid: { $ref: "#/components/schemas/Uid"} username: {type: string} access: { $ref: "#/components/schemas/AccessType" } '401': $ref: "#/components/responses/HTTPError" patch: tags: [auth] operationId: patchAccess description: edit a scene's access rights # Administrative data. Might contain server configuration routes in the future /admin/stats: get: tags: [admin] operationId: getAdminStats description: get server stats /admin/mailtest: post: tags: [admin] operationId: postAdminMailtest description: sends a test email components: parameters: scene: name: scene in: path required: true schema: {type: string } description: unique name of a scene example: foo file: name: file in: path required: true schema: {type: "string"} description: | relative path to a scene's file. Might contain slashs, though openAPI spec won't allow them in test queries examples: folder: summary: a file in a nested folder value: models/foo.glb thumbnail: summary: a thumbnail for this scene value: "scene-image-thumb.jpg" document: summary: a voyager scene document file value: scene.svx.json responses: 'HTTPError': description: Generic HTTP error response whose content depends on the request's "Accept" header content: application/json: schema: type: object required: ["code", "message"] properties: code: description: | [HTTP Status](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) code type: number format: int32 minimum: 100 maximum: 599 message: type: string text/plain: schema: type: "string" example: "Bad Request" text/html: schema: type: "string" schemas: Scene: type: object required: ["ctime", "mtime", "author_id", "author", "id", "access"] properties: ctime: {type: "string", format: "date-time"} mtime: {type: "string", format: "date-time"} author_id: { $ref: "#/components/schemas/Uid" } author: {type: "string"} id: { $ref: "#/components/schemas/Uid"} name: {type: "string"} thumb: {type: "string", desciption: "URI to the scene's thumbnail representation if it exists"} access: { type: object, required: ["any", "default"], properties: { default: { $ref: "#/components/schemas/AccessType" }, any: { $ref: "#/components/schemas/AccessType" }, user: { $ref: "#/components/schemas/AccessType" }, } } User: type: object required: ["uid", "username", "isAdministrator"] properties: uid: {$ref: "#/components/schemas/Uid"} username: {type: "string", example: "alice" } isAdministrator: {type: "boolean"} Uid: type: "string" pattern: '^\d+$' description: string representation of unique IDs. Applicable for users or scenes, but uids are not expected to be unique across namespaces UIDs are often stringified to prevent rounding errors on large intergers in the javascript engine AccessType: type: string enum: ["none", "read", "write", "admin"] Etag: description: | unique string describing the resource state, generally used for caching. A base64url sha256 hash sum is generally used but the exact format's stability shouldn't be relied-upon. type: string examples: weak: value: "W/oFpbfs6awWPgScobk7dDvUnjEWYnDvqCekDfVnMKAWI" summary: "Weak Etag, calculated upon the resource's identifying data, which could lead to an Etag change while the content hasn't changed" strong: value: "hohW3ydF34xbzWteINtEmJ69ftqMXcyX3HO5S4hp4lo" summary: Calculated on a resource's content, which is expected to stay stable. LastModified: description: a date's UTC representation type: string format: date-time WebDAVMultistatus: description: | XML document returned for a PROPFIND request as defined in [rfc4918](http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND) See [rfc4918#xml.element.definitions](http://www.webdav.org/specs/rfc4918.html#xml.element.definitions) for reference XML schema. type: object xml: name: "multistatus" namespace: "DAV:" prefix: "D" required: ["response"] properties: responseDescription: type: string xml: {prefix: "D"} response: type: array xml: {prefix: "D"} items: { $ref: "#/components/schemas/WebDAVResponse" } WebDAVResponse: description: | Part of a webDAV multistatus XML response object type: object required: ["href"] properties: href: type: array items: type: string example: https://example.com/foo xml: { prefix: "D"} propstat: { $ref: "#/components/schemas/WebDAVPropstat"} WebDAVPropstat: description: Propstat Object type: object required: ["status"] xml: { prefix: "D"} properties: status: type: string xml: { prefix: "D"} examples: ok: value: HTTP/1.1 200 OK description: successful response forbidden: value: HTTP/1.1 403 Forbidden description: 403 HTTP error prop: type: object xml: { prefix: "D" } properties: getlastmodified: { $ref: "#/components/schemas/WebDAVDate" } creationdate: { $ref: "#/components/schemas/WebDAVDate" } resourcetype: xml: { prefix: "D" } type: object WebDAVDate: description: a date, formated as GMT iso string type: string format: date-time xml: { prefix: "D" } example: Tue, 28 May 2024 11:59:56 GMT