1
0
Fork 0
freesewing/sites/backend/openapi/lib.mjs

504 lines
17 KiB
JavaScript
Raw Normal View History

export const jwt = [{ jwt: [] }]
export const key = [{ key: [] }]
2022-12-22 18:41:19 +01:00
export const errors = {
accountStatusLacking:
'The account is in a status that does not allow this action (eg: it is not active or disabled).',
confirmationIdMissing: 'The confirmation ID was not specified in the URL',
consentRequired: 'Consent is required to perform this action',
dataNotAnObject: 'The `data` field in the POST body does not contain a value of type `object`',
designMissing: 'The request lacks a `design` value the POST body',
designNotStringy: 'The `design` field in the POST body is not of type `string`',
emailExists: 'There is already an User account with this email address',
emailMissing: 'The `email` field was missing from the request body',
expiresInMissing: 'The `expiresIn` field was missing from the request body',
expiresInNotNumeric: 'The `expiresIn` field in the request body was not a number',
expiresIsHigherThanMaximum:
'The `expiresIn` field in the request body is higher than allowed by the `apikeys.maxExpirySeconds` configuration',
failedToFindConfirmationId: `The confirmation ID could not be found`,
insufficientAccessLevel:
'The credentials used to make this API call are insufficient for this operation.',
invalidLevel: 'The `level` field in the request body was not a valid permission level',
keyLevelExceedsRoleLevel:
'The `level` field in the request body is higher than the `level` of the user creating the key. This is not allowed.',
languageMissing: 'The `langauge` field was missing from the request body',
levelMissing: 'The `level` field was missing from the request body',
levelNotNumeric: 'The `level` field in the request body was a number',
mfaActive: 'MFA is already activated on the account',
mfaTokenMissing: 'The `token` field is missing from the request body',
nameMissing: 'The `name` field was missing from the request body',
passwordMissing: 'The `password` field is missing from the request body',
personMissing: 'The request lacks a `person` value in the POST body',
personNotNumeric: 'The `person` field in the POST body is not of type `integer`',
postBodyMissing: 'The request lacks a POST body',
settingsNotAnObject:
'The `settings` field in the POST body does not contain a value of type `object`',
unsupportedLanguageMissing: 'The `langauge` field holds a language code that we do no support',
usernameMissing: 'The `username` field is missing from the request body',
}
export const errorExamples = (errs) =>
errs.map((err) => `\n - \`${err}\` : ${errors[err]}`).join(' ')
export const fields = {
level: {
description: `
One of the [API permission
levels](https://freesewing.dev/reference/backend/api/rbac#permission-levels) which
is an integer between (and including) \`0\` and \`8\`.`,
type: 'number',
example: 5,
},
result: {
description: 'Textual description of the result of the API call',
type: 'string',
example: 'created',
},
error: {
description: 'Textual description of the error that caused this API call to fail',
type: 'string',
// Note: needs example
},
}
export const parameters = {
auth: {
in: 'path',
name: 'auth',
required: true,
schema: {
type: 'string',
enum: ['jwt', 'key'],
},
description:
'One of `jwt` or `key` depending on whether you ' +
'want to authentication with a JSON Web Token (`jwt`) or an API key (`key`)',
},
}
2022-12-22 18:41:19 +01:00
export const response = {
status: {
201: {
description:
'**Success - Resource created**\n\n' +
'Status code `201` indicates that the resource was created successfully.',
},
204: {
description:
'**Success - Resource removed**\n\n' +
'Status code `204` indicates that the resource was removed successfully.' +
'<br>**Note:** _There is no response body for a `204` status code_.',
},
400: {
description:
'**Client error - Invalid request**\n\n' +
'Status code `400` indicates that the request was invalid.<br>' +
'The return body will have an `error` field which holds a string value that indicates the nature of the problem:\n\n',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
result: { ...fields.result, example: 'error' },
error: { ...fields.error, example: 'postBodyMissing' },
},
},
},
},
},
401: {
description:
'**Unauthorized - Authentication failed**\n\n' +
'Status code `401` indicates that the request could not be authenticated.' +
'This typically means that authentication failed.<br>\n' +
'**Note:** _There is no response body for a `401` status code_.',
},
403: {
description:
'**Forbidden - Permissions problem**\n\n' +
'Status code `403` indicates that the request was forbidden.<br>' +
'The return body will have an `error` field which can hold:\n\n',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
result: fields.result,
error: {
...fields.error,
example: 'accountStatusLacking',
},
},
},
},
},
},
404: {
description:
'**Not found - Resource not found**\n\n' +
'Status code `404` indicates that the resource could not be found.' +
'This typically means that it does not or no longer exist.<br>' +
'**Note:** _There is no response body for a `404` status code_.',
},
500: {
description:
'**Server error - API call failed**\n\n' +
'Status code `500` indicates that the request could not be handled ' +
'due to an unforseen error.',
},
},
body: {
apikey: {
description: 'Object holding the data of the created API key',
type: 'object',
properties: {
createdAt: {
description: 'UTC Timestamp in ISO 8601 format.',
type: 'string',
example: '2022-12-18T17:14:30.460Z',
},
expiresAt: {
description: 'UTC Timestamp in ISO 8601 format.',
type: 'string',
example: '2022-12-18T18:14:30.460Z',
},
key: {
description: 'The _key_ part of the API key serves as the username',
type: 'string',
example: 'c00475bd-3002-4baa-80ad-0145cd6a646c',
},
level: fields.level,
name: fields.name,
secret: {
description: `
The _secret_ part of the API key serves as the password.
It is only revealed in the response of the API key creation.`,
type: 'string',
example: '56b74b5dc2da7a4f37b3c9a6172e840cf4912dc37cbc55c87485f2e0abf59245',
},
userId: {
description: `The unique ID of the user who owns this resource.`,
type: 'number',
example: 4,
},
},
},
pattern: {
description: 'Object holding the data of the pattern',
type: 'object',
properties: {
id: {
description: `The Pattern's unique ID`,
type: 'integer',
example: 666,
},
createdAt: {
description: 'Timestamp of when the Pattern was created, in ISO 8601 format.',
type: 'string',
example: '2022-12-18T18:14:30.460Z',
},
data: {
description: `Additional data for the Pattern`,
type: 'object',
},
design: {
description: `Name of the design this Pattern is an instance of`,
type: 'string',
example: 'aaron',
},
img: {
description: `An image [data-uri](https://en.wikipedia.org/wiki/Data_URI_scheme) to store with this pattern`,
type: 'string',
example: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEU...truncated',
},
name: {
description: `The name of the Pattern exists solely to help you differentiate between your patterns.`,
type: 'string',
example: 'My favourite shirt',
},
notes: {
description: `Any notes to be stored with the pattern`,
type: 'string',
example: 'These are my notes. I can keep them alongside the pattern. Handy!',
},
personId: {
description: `The unique ID of the Person this pattern was drafted for`,
type: 'integer',
example: 33,
},
public: {
description: `Whether or not this pattern can be viewed/used by others`,
type: 'boolean',
example: false,
},
settings: {
description: `The settings used to draft/render this pattern`,
type: 'object',
example: { sa: 30 },
},
userId: {
description: `The unique ID of the User who created this pattern`,
type: 'integer',
example: 66,
},
updatedAt: {
description: 'Timestamp of when the Pattern was last updated, in ISO 8601 format.',
type: 'string',
example: '2022-12-18T18:14:30.460Z',
},
},
},
person: {
description: 'Object holding the data of the person',
type: 'object',
properties: {
id: {
description: `The Person's unique ID`,
type: 'integer',
example: 666,
},
createdAt: {
description: 'Timestamp of when the Person was created, in ISO 8601 format.',
type: 'string',
example: '2022-12-18T18:14:30.460Z',
},
img: {
description: `An image that was stored with this person`,
type: 'string',
example: 'https://freesewing.org/avatar.svg',
},
imperial: {
description: `Whether or not to use imperial units for this person`,
type: 'boolean',
example: false,
},
name: {
description: `The name of the Person exists solely to help you differentiate between your people.`,
type: 'string',
example: 'My bestie Ronda',
},
notes: {
description: `Any notes to be stored with the person`,
type: 'string',
example: 'These are my notes. I can keep them alongside the person. Handy!',
},
public: {
description: `Whether or not this pattern can be viewed/used by others`,
type: 'boolean',
example: false,
},
measies: {
description: `The measurements for this person`,
type: 'object',
example: { neck: 420 },
},
userId: {
description: `The unique ID of the User who created this pattern`,
type: 'integer',
example: 66,
},
updatedAt: {
description: 'Timestamp of when the Pattern was last updated, in ISO 8601 format.',
type: 'string',
example: '2022-12-18T18:14:30.460Z',
},
},
},
userAccount: {
description: 'Object holding the data of the user',
type: 'object',
properties: {
/*
apikeys Apikey[]
confirmations Confirmation[]
ehash String @unique
ihash String
patterns Pattern[]
people Person[]
*/
id: {
description: `The User's unique ID`,
type: 'integer',
example: 666,
},
bio: {
description: `The bio is the information about the user that they want to make available to others`,
type: 'text',
example: `I make clothes and shoes. I design sewing patterns. I write code. I run http://freesewing.org
我也在学中文
Also: Introvert 🙊
(he/him) or (they/them)`,
},
consent: {
description: `This field is about data protection. It indicates the level of consent the user has given to process their data.
- \`0\`: No consent given
- \`1\`: Consent given to process account data
- \`2\`: Consent given to process account data and person data
- \`3\`: Consent given to process account data and person data, and use anonymized data for research
`,
type: 'integer',
enum: [0, 1, 2, 3],
example: 3,
},
control: {
description: `This field is about keeping it simple. It indicates the level of control the user wants over the user experience.
- \`1\`: Hide all but the most crucial features. Make it as simple as possible.
- \`2\`: Hide the majority of features. Make it simple, but not too much.
- \`3\`: Reveal the majority of features, but not all. Balance simplicity with power.
- \`4\`: Reveal everything, but keep handrails and safety checks. Only intervene when Im about to do something dangerous.
- \`5\`: Reveal everything, remove the handrails and safety checks. Get out of my way, I know what Im doing.
`,
type: 'integer',
example: 4,
enum: [1, 2, 3, 4, 5],
},
createdAt: {
description: 'Timestamp of when the User was created, in ISO 8601 format.',
type: 'string',
example: '2022-12-18T18:14:30.460Z',
},
email: {
description: `The email address of the User`,
type: 'string',
example: 'joost@joost.at',
},
github: {
description: `The github username of the User`,
type: 'string',
example: 'joostdecock',
},
img: {
description: `An image that was stored with this person`,
type: 'string',
example: 'https://freesewing.org/avatar.svg',
},
imperial: {
description: `Whether or not to use imperial units for this user`,
type: 'boolean',
example: false,
},
initial: {
description: `The email address this User account was registered with`,
type: 'string',
example: 'joost@decock.org',
},
language: {
description: `Language code of the language preferred by the user`,
type: 'string',
example: 'en',
enum: ['en', 'es', 'de', 'fr', 'nl'],
},
lastLogin: {
description: 'Timestamp of when the User last authenticated, in ISO 8601 format.',
type: 'string',
example: '2022-12-18T18:14:30.460Z',
},
lusername: {
description: `A lowercased version of the user's username`,
type: 'string',
example: 'joostdecock',
},
mfaEnabled: {
description: `Whether or not multi-factor authentication (MFA) is enabled for this user`,
type: 'boolean',
example: 'true',
},
newsletter: {
description: `Whether or not this user is subscribed to the newsletter (via the account subscription feature)`,
type: 'boolean',
example: 'false',
},
patron: {
description: `The patron tier of the user`,
type: 'integer',
example: 8,
enum: [0, 2, 4, 8],
},
role: {
description: `The role of the user`,
type: 'string',
example: 'user',
enum: ['user', 'bughunter', 'support', 'admin'],
},
patron: {
description: `The status of the user's account
- \`0\`: The account is not active (yet)
- \`1\`: The account is active
- \`-1\`: The account was disabled by the user
- \`-2\`: The account was administratively disabled
`,
type: 'integer',
example: 8,
enum: [0, 1, -1, -2],
},
updatedAt: {
description: 'Timestamp of when the User was last updated, in ISO 8601 format.',
type: 'string',
example: '2022-12-18T18:14:30.460Z',
},
username: {
description: `The User's username`,
type: 'string',
example: 'JoostDeCock',
},
},
},
userProfile: {
description: 'Object holding the profile of the user',
type: 'object',
properties: {},
},
},
}
response.body.userProfile.properties = { ...response.body.userAccount.properties }
for (const remove of [
'consent',
'control',
'createdAt',
'email',
'github',
'initial',
'lastLogin',
'lusername',
'mfaEnabled',
'newsletter',
'updatedAd',
])
delete response.body.userProfile.properties[remove]
export const uploadImg = {
description: `An image [data-uri](https://en.wikipedia.org/wiki/Data_URI_scheme) to store with this resource`,
type: 'string',
example: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEU...truncated',
}
export const token = {
description: `A JWT token`,
type: 'string',
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...truncated',
}
// Schemas
export const schemas = {
apikey: response.body.apikey,
pattern: response.body.pattern,
person: response.body.person,
userAccount: response.body.userAccount,
userProfile: response.body.userProfile,
}
export const jsonResponse = (properties) => ({
content: {
'application/json': {
schema: {
type: 'object',
properties,
},
},
},
})