Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): add create aspect to maintainence #2708

Merged
merged 2 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ PICKUP_BASIC_AUTH_TOKEN = dGVzdDp0ZXN0
PICKUP_API_URL = http://127.0.0.1:9094

# Maintenance Mode
MAINTENANCE_MODE = rw
MAINTENANCE_MODE = rwc

# S3
S3_ENDPOINT = http://127.0.0.1:9000
Expand Down
14 changes: 14 additions & 0 deletions packages/api/src/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {
modes as MaintenanceModes,
DEFAULT_MODE,
NO_READ_OR_WRITE,
READ_ONLY,
READ_WRITE_ONLY,
} from './middleware/maintenance.js'

/**
Expand Down Expand Up @@ -193,6 +196,17 @@ function maintenanceModeFromString(s) {
return m
}
}
/** @type {Record<string, import('./middleware/maintenance.js').Mode>} */
const legacyModeMappings = {
'--': NO_READ_OR_WRITE,
'r-': READ_ONLY,
rw: READ_WRITE_ONLY,
}
for (const [key, value] of Object.entries(legacyModeMappings)) {
if (s === key) {
return value
}
}
throw new Error(
`invalid maintenance mode value "${s}". valid choices: ${MaintenanceModes}`
)
Expand Down
29 changes: 15 additions & 14 deletions packages/api/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import { getServiceConfig } from './config.js'
import {
withMode,
READ_ONLY as RO,
READ_WRITE as RW,
READ_WRITE_ONLY as RW,
READ_WRITE_CREATE as RWC,
} from './middleware/maintenance.js'
import { getContext } from './utils/context.js'
import { withAuth } from './middleware/auth.js'
Expand Down Expand Up @@ -96,7 +97,7 @@ r.add(
r.add(
'post',
'/pins',
withAuth(withMode(pinsAdd, RW), {
withAuth(withMode(pinsAdd, RWC), {
checkHasPsaAccess,
checkHasAccountRestriction,
}),
Expand All @@ -105,7 +106,7 @@ r.add(
r.add(
'post',
'/pins/:requestid',
withAuth(withMode(pinsReplace, RW), {
withAuth(withMode(pinsReplace, RWC), {
checkHasPsaAccess,
checkHasAccountRestriction,
}),
Expand All @@ -114,7 +115,7 @@ r.add(
r.add(
'delete',
'/pins/:requestid',
withAuth(withMode(pinsDelete, RW), {
withAuth(withMode(pinsDelete, RWC), {
checkHasDeleteRestriction,
checkHasPsaAccess,
}),
Expand All @@ -128,7 +129,7 @@ r.add('get', '/:cid', withAuth(withMode(nftGet, RO)), [postCors])
r.add(
'post',
'/upload',
withAuth(withMode(nftUpload, RW), {
withAuth(withMode(nftUpload, RWC), {
checkHasAccountRestriction,
checkUcan,
}),
Expand All @@ -137,24 +138,24 @@ r.add(
r.add(
'patch',
'/upload/:cid',
withAuth(withMode(nftUpdateUpload, RW), { checkHasAccountRestriction }),
withAuth(withMode(nftUpdateUpload, RWC), { checkHasAccountRestriction }),
[postCors]
)
r.add(
'post',
'/store',
withAuth(withMode(nftStore, RW), { checkHasAccountRestriction }),
withAuth(withMode(nftStore, RWC), { checkHasAccountRestriction }),
[postCors]
)
r.add(
'delete',
'/:cid',
withAuth(withMode(nftDelete, RW), { checkHasDeleteRestriction }),
withAuth(withMode(nftDelete, RWC), { checkHasDeleteRestriction }),
[postCors]
)

// Temporary Metaplex upload route, mapped to metaplex user account.
r.add('post', '/metaplex/upload', withMode(metaplexUpload, RW), [postCors])
r.add('post', '/metaplex/upload', withMode(metaplexUpload, RWC), [postCors])

// User
r.add(
Expand Down Expand Up @@ -206,7 +207,7 @@ r.add(
r.add(
'post',
'/api/pins',
withAuth(withMode(pinsAdd, RW), {
withAuth(withMode(pinsAdd, RWC), {
checkHasPsaAccess,
checkHasAccountRestriction,
}),
Expand All @@ -215,7 +216,7 @@ r.add(
r.add(
'post',
'/api/pins/:requestid',
withAuth(withMode(pinsReplace, RW), {
withAuth(withMode(pinsReplace, RWC), {
checkHasPsaAccess,
checkHasAccountRestriction,
}),
Expand All @@ -224,7 +225,7 @@ r.add(
r.add(
'delete',
'/api/pins/:requestid',
withAuth(withMode(pinsDelete, RW), {
withAuth(withMode(pinsDelete, RWC), {
checkHasDeleteRestriction,
checkHasPsaAccess,
}),
Expand All @@ -238,13 +239,13 @@ r.add('get', '/api/:cid', withAuth(withMode(nftGet, RO)), [postCors])
r.add(
'post',
'/api/upload',
withAuth(withMode(nftUpload, RW), { checkUcan, checkHasAccountRestriction }),
withAuth(withMode(nftUpload, RWC), { checkUcan, checkHasAccountRestriction }),
[postCors]
)
r.add(
'delete',
'/api/:cid',
withAuth(withMode(nftDelete, RW), { checkHasDeleteRestriction }),
withAuth(withMode(nftDelete, RWC), { checkHasDeleteRestriction }),
[postCors]
)

Expand Down
22 changes: 16 additions & 6 deletions packages/api/src/middleware/maintenance.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,42 @@ import { ErrorMaintenance, HTTPError } from '../errors.js'
import { getServiceConfig } from '../config.js'

/**
* @typedef {'rw' | 'r-' | '--'} Mode
* @typedef {'rwc' | 'rw-' | 'r--' | '---'} Mode
* @typedef {import('../bindings').Handler} Handler
*/

/**
* Read and write and create
*/
export const READ_WRITE_CREATE = 'rwc'

/**
* Read and write.
*/
export const READ_WRITE = 'rw'
export const READ_WRITE_ONLY = 'rw-'

/**
* Read only mode.
*/
export const READ_ONLY = 'r-'
export const READ_ONLY = 'r--'

/**
* No reading or writing.
*/
export const NO_READ_OR_WRITE = '--'
export const NO_READ_OR_WRITE = '---'

/** @type {readonly Mode[]} */
export const modes = Object.freeze([NO_READ_OR_WRITE, READ_ONLY, READ_WRITE])
export const modes = Object.freeze([
NO_READ_OR_WRITE,
READ_ONLY,
READ_WRITE_ONLY,
READ_WRITE_CREATE,
])

/**
* The default maintenance mode (normal operation).
*/
export const DEFAULT_MODE = READ_WRITE
export const DEFAULT_MODE = READ_WRITE_CREATE

/** @type {() => Mode} */
let getMaintenanceMode = () => getServiceConfig().MAINTENANCE_MODE
Expand Down
21 changes: 19 additions & 2 deletions packages/api/test/config.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const BASE_CONFIG = {
DATABASE_TOKEN:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoyNTUwNjUzNjM0LCJyb2xlIjoic2VydmljZV9yb2xlIn0.necIJaiP7X2T2QjGeV-FhpkizcNTX8HjDDBAxpgQTEI',
DATABASE_CONNECTION: 'postgresql://postgres:postgres@localhost:5432/postgres',
MAINTENANCE_MODE: 'rw',
MAINTENANCE_MODE: 'rwc',
S3_REGION: 'us-east-1',
S3_ACCESS_KEY_ID: 'minioadmin',
S3_SECRET_ACCESS_KEY: 'minioadmin',
Expand Down Expand Up @@ -145,7 +145,7 @@ test.serial(
test.serial(
'serviceConfigFromVariables sets MAINTENANCE_MODE if it contains a valid mode string',
(t) => {
const modes = ['--', 'r-', 'rw']
const modes = ['---', 'r--', 'rw-', 'rwc']
for (const m of modes) {
t.is(
serviceConfigFromVariables(
Expand All @@ -159,6 +159,23 @@ test.serial(
}
)

test.serial(
'serviceConfigFromVariables sets MAINTENANCE_MODE if it contains a valid legacy mode string',
(t) => {
const modes = ['--', 'r-', 'rw']
for (const m of modes) {
t.is(
serviceConfigFromVariables(
override({
MAINTENANCE_MODE: m,
})
).MAINTENANCE_MODE.toString(),
m + '-'
)
}
}
)

test.serial(
'serviceConfigFromVariables uses unaltered values for string config variables',
(t) => {
Expand Down
35 changes: 32 additions & 3 deletions packages/api/test/maintenance.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import test from 'ava'
import {
READ_ONLY,
READ_WRITE,
READ_WRITE_ONLY,
NO_READ_OR_WRITE,
READ_WRITE_CREATE,
} from '../src/middleware/maintenance.js'
import { createClientWithUser } from './scripts/helpers.js'

Expand Down Expand Up @@ -46,25 +47,33 @@ test('maintenance middleware should throw error when in maintenance mode', async

const expectedError = { message: /API undergoing maintenance/ }

await setMode(t, READ_WRITE)
await setMode(t, READ_WRITE_CREATE)
await t.notThrowsAsync(tryRead(t, token))
await t.notThrowsAsync(tryWrite(t, token))
await t.notThrowsAsync(tryCreate(t, token))

await setMode(t, READ_WRITE_ONLY)
await t.notThrowsAsync(tryRead(t, token))
await t.notThrowsAsync(tryWrite(t, token))
await t.throwsAsync(tryCreate(t, token), expectedError)

await setMode(t, READ_ONLY)
await t.notThrowsAsync(tryRead(t, token))
await t.throwsAsync(tryWrite(t, token), expectedError)
await t.throwsAsync(tryCreate(t, token), expectedError)

await setMode(t, NO_READ_OR_WRITE)
await t.throwsAsync(tryRead(t, token), expectedError)
await t.throwsAsync(tryWrite(t, token), expectedError)
await t.throwsAsync(tryCreate(t, token), expectedError)
})

/**
*
* @param {import('ava').ExecutionContext} t
* @param {string} token
*/
async function tryWrite(t, token) {
async function tryCreate(t, token) {
const mf = getMiniflareContext(t)
const res = await mf.dispatchFetch('http://miniflare.test/upload', {
headers: { authorization: `Bearer ${token}` },
Expand All @@ -77,6 +86,26 @@ async function tryWrite(t, token) {
}
}

/**
*
* @param {import('ava').ExecutionContext} t
* @param {string} token
*/
async function tryWrite(t, token) {
const mf = getMiniflareContext(t)
const res = await mf.dispatchFetch('http://miniflare.test/internal/tokens', {
headers: {
authorization: `Bearer ${token}`,
},
method: 'POST',
body: JSON.stringify({ name: `new key ${new Date().toISOString()}` }),
})
if (!res.ok) {
const { error } = await res.json()
throw new Error(error.message)
}
}

/**
*
* @param {import('ava').ExecutionContext} t
Expand Down
2 changes: 1 addition & 1 deletion packages/api/test/scripts/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ globalThis.PICKUP_API_URL = 'http://127.0.0.1:9094'
// will be used with we can active auth in cluster base64 of test:test
globalThis.PICKUP_BASIC_AUTH_TOKEN = 'dGVzdDp0ZXN0'

globalThis.MAINTENANCE_MODE = 'rw'
globalThis.MAINTENANCE_MODE = 'rwc'

globalThis.S3_ENDPOINT = 'http://127.0.0.1:9000'
globalThis.S3_REGION = 'us-east-1'
Expand Down
Loading