Skip to content

Commit

Permalink
Merge branch 'master' into purge-test-extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
alxndrsn committed Dec 4, 2024
2 parents 1619109 + 911739b commit 9fe2123
Show file tree
Hide file tree
Showing 26 changed files with 1,202 additions and 229 deletions.
223 changes: 216 additions & 7 deletions docs/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,6 @@ tags:
* The **Metadata Document** defines the data schema using [an XML-based standard](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Introduction). It is linked in every OData response.

* The **data documents**, linked from the Service Document, are a simple JSON representation of the Submission or Entity data, conforming to the schema we describe in our Metadata Document.

- name: System Endpoints
description: There are some resources available for getting or setting system information
and configuration. You can set the [Usage Reporting configuration](/central-api-system-endpoints/#usage-reporting-configuration)
Expand Down Expand Up @@ -646,6 +645,12 @@ tags:
x-parent-tag: Accounts and Users
description: |-
Presently, it is possible to create and list `User`s in the system, as well as to perform password reset operations. In future versions of this API it will be possible to manage existing user information and delete accounts as well.
- name: User Preferences
x-parent-tag: Accounts and Users
description: |-
The Central frontend uses this API to save various user preferences, such as the sorting order of certain listings. When a user starts a new session, these preferences are loaded in as part of the [User object](/central-api-accounts-and-users/#getting-authenticated-user-details).

Preferences can be set site-wide or per-project.
- name: App Users
x-parent-tag: Accounts and Users
description: |-
Expand Down Expand Up @@ -1473,17 +1478,204 @@ paths:
schema:
$ref: '#/components/schemas/Error403'
x-codegen-request-body-name: body
/v1/user-preferences/project/{projectId}/{propertyName}:
put:
tags:
- User Preferences
summary: Setting a project preference
parameters:
- name: projectId
in: path
description: The integer ID of the `Project`.
required: true
schema:
type: integer
example: 42
- name: propertyName
in: path
description: The name of the preference.
required: true
schema:
type: string
example: "frobwazzleEnabled"
requestBody:
content:
'application/json':
schema:
required:
- propertyValue
type: any
properties:
propertyValue:
type: any
description: The preference to be stored. It may be of any json-serializable type.
example:
propertyValue: true
required: true
responses:
200:
description: The preference was stored
content:
application/json:
schema:
$ref: '#/components/schemas/Success'
403:
description: Forbidden - supplied bearer token was not valid
content:
application/json:
schema:
$ref: '#/components/schemas/Error403'
404:
description: The specified project does not exist
content:
application/json:
schema:
$ref: '#/components/schemas/Error404'
delete:
tags:
- User Preferences
summary: Deleting a project preference
parameters:
- name: projectId
in: path
description: The integer ID of the `Project`.
required: true
schema:
type: integer
example: 42
- name: propertyName
in: path
description: The name of the preference.
required: true
schema:
type: string
example: "frobwazzleEnabled"
- name: Authorization
in: header
description: Bearer encoding of a token of the user whose preferences to operate on
required: true
schema:
type: string
example: Bearer lSpAIeksRu1CNZs7!qjAot2T17dPzkrw9B4iTtpj7OoIJBmXvnHM8z8Ka4QPEjR7
responses:
200:
description: The preference was stored
content:
application/json:
schema:
$ref: '#/components/schemas/Success'
403:
description: Forbidden - supplied bearer token was not valid
content:
application/json:
schema:
$ref: '#/components/schemas/Error403'
404:
description: The preference does not exist
content:
application/json:
schema:
$ref: '#/components/schemas/Error404'
/v1/user-preferences/site/{propertyName}:
put:
tags:
- User Preferences
summary: Setting a sitewide preference
parameters:
- name: propertyName
in: path
description: The name of the preference.
required: true
schema:
type: string
example: "frobwazzleEnabled"
- name: Authorization
in: header
description: Bearer encoding of a token of the user whose preferences to operate on
required: true
schema:
type: string
example: Bearer lSpAIeksRu1CNZs7!qjAot2T17dPzkrw9B4iTtpj7OoIJBmXvnHM8z8Ka4QPEjR7
requestBody:
content:
'application/json':
schema:
required:
- propertyValue
type: any
properties:
propertyValue:
type: any
description: The preference to be stored. It may be of any json-serializable type.
example:
propertyValue: true
required: true

responses:
200:
description: The preference was stored
content:
application/json:
schema:
$ref: '#/components/schemas/Success'
403:
description: Forbidden - supplied bearer token was not valid
content:
application/json:
schema:
$ref: '#/components/schemas/Error403'
delete:
tags:
- User Preferences
summary: Deleting a sitewide preference
parameters:
- name: propertyName
in: path
description: The name of the preference.
required: true
schema:
type: string
example: "frobwazzleEnabled"
- name: Authorization
in: header
description: Bearer encoding of a token of the user whose preferences to operate on
required: true
schema:
type: string
example: Bearer lSpAIeksRu1CNZs7!qjAot2T17dPzkrw9B4iTtpj7OoIJBmXvnHM8z8Ka4QPEjR7
responses:
200:
description: The preference was deleted
content:
application/json:
schema:
$ref: '#/components/schemas/Success'
403:
description: Forbidden - supplied bearer token was not valid
content:
application/json:
schema:
$ref: '#/components/schemas/Error403'
404:
description: The specified preference does not exist
content:
application/json:
schema:
$ref: '#/components/schemas/Error404'
/v1/users/current:
get:
tags:
- Users
- User Preferences
summary: Getting authenticated User details
description: |-
Typically, you would get User details by the User's numeric Actor ID.

However, if you only have a Bearer token, for example, you don't have any information about the user attached to that session, including even the ID with which to get more information. So you can instead supply the text `current` to get the user information associated with the authenticated session.

If you _do_ use `current`, you may request extended metadata. Supply an `X-Extended-Metadata` header value of `true` to additionally retrieve an array of strings of the `verbs` the authenticated User/Actor is allowed to perform server-wide.
If you _do_ use `current`, you may request extended metadata. Supply an `X-Extended-Metadata` header value of `true` to additionally receive:
- an array of strings of the `verbs` the authenticated User/Actor is allowed to perform server-wide
- an object containing the preferences of the authenticated User/Actor
operationId: Getting authenticated User details
responses:
200:
Expand Down Expand Up @@ -1573,6 +1765,14 @@ paths:
server-wide.
items:
type: object
preferences:
description: The preferences of this user
type: object
properties:
site:
type: object
projects:
type: object
example:
createdAt: 2018-04-18T23:19:14.802Z
displayName: My Display Name
Expand All @@ -1584,6 +1784,12 @@ paths:
verbs:
- project.create
- project.update
preferences:
site:
projectSortMode: latest
projects:
"1":
formTrashCollapsed: false
403:
description: Forbidden
content:
Expand Down Expand Up @@ -3670,7 +3876,7 @@ paths:
items:
$ref: '#/components/schemas/FormAttachment'
example:
- name: myfile.mp3
- name: myfile.png
type: image
exists: true
blobExists: true
Expand Down Expand Up @@ -4198,7 +4404,7 @@ paths:
items:
$ref: '#/components/schemas/FormAttachment'
example:
- name: myfile.mp3
- name: myfile.png
type: image
exists: true
blobExists: true
Expand Down Expand Up @@ -4899,7 +5105,7 @@ paths:
items:
$ref: '#/components/schemas/FormAttachment'
example:
- name: myfile.mp3
- name: myfile.png
type: image
exists: true
blobExists: true
Expand Down Expand Up @@ -8816,6 +9022,9 @@ paths:
label: John (88)
firstName: John
age: '88'
application/json; bulk:
schema:
$ref: '#/components/schemas/Success'
403:
description: Forbidden
content:
Expand Down Expand Up @@ -12263,7 +12472,7 @@ components:
properties:
name:
type: string
example: myfile.mp3
example: myfile.png
description: The name of the file as specified in the XForm.
type:
$ref: '#/components/schemas/FormAttachmentType'
Expand Down Expand Up @@ -12658,7 +12867,7 @@ components:
properties:
name:
type: string
example: myfile.mp3
example: myfile.png
description: The name of the file as specified in the Submission XML.
exists:
type: boolean
Expand Down
10 changes: 9 additions & 1 deletion lib/data/dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ const getDataset = (xml) =>
else if (!semverSatisfies(version.get(), '2022.1.0 - 2024.1.x'))
throw Problem.user.invalidEntityForm({ reason: `Entities specification version [${version.get()}] is not supported.` });

const warnings = semverSatisfies(version.get(), '>=2024.1.x')
? null
: [{
type: 'oldEntityVersion',
details: { version: version.get() },
reason: `Entities specification version [${version.get()}] is not compatible with Offline Entities. Please use version 2024.1.0 or later.`
}];

const strippedAttrs = Object.create(null);
for (const [name, value] of Object.entries(entityAttrs.get()))
strippedAttrs[stripNamespacesFromPath(name)] = value;
Expand All @@ -101,7 +109,7 @@ const getDataset = (xml) =>
if (actions.length === 0)
throw Problem.user.invalidEntityForm({ reason: 'The form must specify at least one entity action, for example, create or update.' });

return Option.of({ name: datasetName, actions });
return Option.of({ name: datasetName, actions, warnings });
});

module.exports = { getDataset, validateDatasetName, validatePropertyName };
2 changes: 2 additions & 0 deletions lib/formats/odata.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,8 @@ const rowStreamToOData = (fields, table, domain, originalUrl, query, inStream, t
flush(done) {
// flush is called just before the transform stream is done and closed; we write
// our footer information, close the object, and tell the stream we are done.
if (!cursorPredicate) return this.destroy(Problem.user.odataRepeatIdNotFound());

this.push((added === 0) ? '{"value":[],' : '],'); // open object or close row array.

// if we were given an explicit count, use it from here out, to create
Expand Down
24 changes: 16 additions & 8 deletions lib/http/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ const Problem = require('../util/problem');
// ENDPOINT COMMON UTILS
// we put these first to appease eslint.

const writeProblemJson = (response, error) => {
// we already have a publicly-consumable error object.
response.status(error.httpCode).type('application/json').send({
message: error.message,
code: error.problemCode,
details: error.problemDetails
});
};

// check if a string matches an expected mime type
const isJsonType = (x) => /(^|,)(application\/json|json)($|;|,)/i.test(x);
const isXmlType = (x) => /(^|,)(application\/(atom(svc)?\+)?xml|atom|xml)($|;|,)/i.test(x);
Expand Down Expand Up @@ -191,8 +200,12 @@ const endpointBase = ({ preprocessor = noop, before = noop, resultWriter, errorW
////////////////////////////////////////////////////////////////////////////////
// ENDPOINT FORMAT SPECIALIZATIONS

const streamErrorHandler = (response) => () => {
response.addTrailers({ Status: 'Error' }); // TODO: improve response content.
const streamErrorHandler = (response) => err => {
if (err.isProblem && !response.headersSent && isJsonType(response.get('Content-Type'))) {
writeProblemJson(response, err);
} else {
response.addTrailers({ Status: 'Error' }); // TODO: improve response content.
}
};
const pipelineResult = (result, response, next) => {
if (result instanceof PartialPipe) {
Expand Down Expand Up @@ -230,12 +243,7 @@ const defaultErrorWriter = (error, request, response) => {
}

if (error?.isProblem === true) {
// we already have a publicly-consumable error object.
response.status(error.httpCode).type('application/json').send({
message: error.message,
code: error.problemCode,
details: error.problemDetails
});
writeProblemJson(response, error);
} else {
debugger; // trip debugger if attached.
process.stderr.write(inspect(error));
Expand Down
Loading

0 comments on commit 9fe2123

Please sign in to comment.