diff --git a/packages/api/docker/docker-compose-local-ports.yml b/packages/api/docker/docker-compose-local-ports.yml deleted file mode 100644 index 2225249acc..0000000000 --- a/packages/api/docker/docker-compose-local-ports.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: '3.6' -services: - rest: - ports: - - 3000:3000/tcp - db: - ports: - - 5432:5432 - cluster: - ports: - - '127.0.0.1:9094:9094' - minio: - ports: - - '9000:9000' - - '9001:9001' diff --git a/packages/api/docker/docker-compose-volumes.yml b/packages/api/docker/docker-compose-volumes.yml index 6cd61bdf07..fc9cb3824a 100644 --- a/packages/api/docker/docker-compose-volumes.yml +++ b/packages/api/docker/docker-compose-volumes.yml @@ -1,11 +1,11 @@ version: '3.6' -services: - ipfs: - volumes: - - ./compose/ipfs:/data/ipfs - cluster: - volumes: - - ./compose/cluster:/data/ipfs-cluster - minio: - volumes: - - ./compose/minio:/data/minio + +volumes: + nftstorage-db-data: + external: true + nftstorage-ipfs-data: + external: true + nftstorage-cluster-data: + external: true + nftstorage-minio-data: + external: true diff --git a/packages/api/docker/docker-compose.yml b/packages/api/docker/docker-compose.yml index 5ed75eaaf0..7529bd02fa 100644 --- a/packages/api/docker/docker-compose.yml +++ b/packages/api/docker/docker-compose.yml @@ -25,6 +25,8 @@ services: memory: 1G ports: - 5432 + volumes: + - nftstorage-db-data:/var/lib/postgresql/data environment: POSTGRES_DB: postgres POSTGRES_USER: postgres @@ -32,6 +34,8 @@ services: POSTGRES_PORT: 5432 ipfs: image: ipfs/go-ipfs:v0.10.0 # update this when go-ipfs M1 macs https://github.com/ipfs/go-ipfs/issues/8645 + volumes: + - nftstorage-ipfs-data:/data/ipfs cluster: image: ipfs/ipfs-cluster:v1.0.0-rc4 @@ -49,12 +53,22 @@ services: CLUSTER_MONITORPINGINTERVAL: 2s # Speed up peer discovery ports: - 9094 + volumes: + - nftstorage-cluster-data:/data/ipfs-cluster minio: image: quay.io/minio/minio command: server /data/minio --console-address ":9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin + volumes: + - nftstorage-minio-data:/data/minio ports: - 9000 - 9001 + +volumes: + nftstorage-db-data: + nftstorage-ipfs-data: + nftstorage-cluster-data: + nftstorage-minio-data: diff --git a/packages/api/docker/run-with-dependencies.sh b/packages/api/docker/run-with-dependencies.sh deleted file mode 100755 index 514eb0b411..0000000000 --- a/packages/api/docker/run-with-dependencies.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash - -THIS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) - -COMPOSE_FILE_ARGS=( - "--file" "$THIS_DIR/docker-compose.yml" -) - -# if we're persisting volumes, use a deterministic project name -# so the volumes will be attached on next run. -# otherwise, append the current timestamp so we can run multiple envs -# in parallel -if [[ "$PERSIST_VOLUMES" != "" ]]; then - PROJECT_NAME="nft-storage-dev-persistent" - PERSIST_VOLUME_ARGS="" - VOLUME_CLEANUP="" - - # add docker-compose-persist.yml, to tell docker compose to use external - # volumes instead of throw-away vols - COMPOSE_FILE_ARGS=( - ${COMPOSE_FILE_ARGS[@]} - "--file" "$THIS_DIR/docker-compose-persist.yml" - ) - - # create named volumes for persistent data (this is idempotent) - docker volume create nftstorage-pg-data - docker volume create nftstorage-ipfs-data - docker volume create nftstorage-cluster-data - docker volume create nftstorage-minio-data -else - TS=$(date +'%s') - PROJECT_NAME="nft-storage-dev-$TS" - PERSIST_VOLUME_ARGS="--renew-anon-volumes" - VOLUME_CLEANUP="--volumes" -fi - - - - -function cleanup { - echo "stopping compose project $PROJECT_NAME" - docker compose --project-name $PROJECT_NAME \ - down \ - --remove-orphans \ - $VOLUME_CLEANUP \ - ; - echo "compose project $PROJECT_NAME down" -} -# this trap causes cleanup to be run whenever the script exits (including abnormal exit) -trap cleanup EXIT - -# get the bound port for a port exposed by a container -# returns ip:port, where ip seems to always be 0.0.0.0 -function get_host_port { - local service_name=$1 - local port_number=$2 - docker compose --project-name $PROJECT_NAME port $service_name $port_number -} - -echo "starting docker compose project $PROJECT_NAME" - -docker compose \ - "${COMPOSE_FILE_ARGS[@]}" \ - --project-name $PROJECT_NAME \ - up \ - $PERSIST_VOLUME_ARGS \ - --detach \ -; - -DB_HOST_PORT=$(get_host_port "db" 5432) -POSTGREST_HOST_PORT=$(get_host_port "rest" 3000) -CLUSTER_HOST_PORT=$(get_host_port "cluster" 9094) -MINIO_HOST_PORT=$(get_host_port "minio" 9000) - -# this thing below splits the ip:port returned by get_host_port into a bash array of [host, port] -minio_port_components=(${MINIO_HOST_PORT//:/ }) - -# MINIO_API_PORT and DATABASE_CONNECTION are used by cli.js when creating the -# db schema and s3 bucket -export MINIO_API_PORT=${minio_port_components[1]} -export DATABASE_CONNECTION="postgres://postgres:postgres@$DB_HOST_PORT/postgres" - -# The vars below are used to configure the service -export DATABASE_URL="http://$POSTGREST_HOST_PORT" -export CLUSTER_API_URL="http://$CLUSTER_HOST_PORT" -export S3_ENDPOINT="http://$MINIO_HOST_PORT" - -echo "services started." -echo "environment overrides:" -echo "MINIO_API_PORT=${MINIO_API_PORT}" -echo "DATABASE_CONNECTION=${DATABASE_CONNECTION}" -echo "DATABASE_URL=${DATABASE_URL}" -echo "CLUSTER_API_URL=${CLUSTER_API_URL}" -echo "S3_ENDPOINT=${S3_ENDPOINT}" -echo - -# run ARGV as a command -echo "running $@" -$@ \ No newline at end of file diff --git a/packages/api/package.json b/packages/api/package.json index 779fc364fb..b068f31197 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -7,11 +7,10 @@ "main": "dist/worker.js", "scripts": { "deploy": "wrangler publish --env production", - "predev": "./scripts/cli.js services start && ./scripts/cli.js db-sql --cargo --testing --reset && ./scripts/cli.js minio bucket create dotstorage-dev-0", - "dev": "miniflare dist/worker.js --watch --debug --env ../../.env", - "dev:persist": "PERSIST_VOLUMES=true npm run dev", - "build": "scripts/cli.js build", - "test": "./docker/run-with-dependencies.sh ./scripts/run-test.sh", + "dev": "npm run build && ./scripts/cli.js run dev", + "dev:persist": "npm run build && ./scripts/cli.js run dev --persistent", + "build": "./scripts/cli.js build", + "test": "./scripts/cli.js build --env=test && scripts/cli.js run test", "db-types": "./scripts/cli.js db-types" }, "author": "Hugo Dias (hugodias.me)", diff --git a/packages/api/scripts/cli.js b/packages/api/scripts/cli.js index 045fc204e4..d000640862 100755 --- a/packages/api/scripts/cli.js +++ b/packages/api/scripts/cli.js @@ -9,14 +9,12 @@ import Sentry from '@sentry/cli' import { createRequire } from 'module' // @ts-ignore import git from 'git-rev-sync' -import { - servicesStartCmd, - servicesStopCmd, - servicesPullCmd, -} from './cmds/services.js' +import { servicesExecCmd, servicesPullCmd } from './cmds/services.js' import { dbSqlCmd } from './cmds/db-sql.js' import { dbTypesCmd } from './cmds/db-types.js' import { minioBucketCreateCmd, minioBucketRemoveCmd } from './cmds/minio.js' +import { runTestSuiteCmd } from './cmds/run-test.js' +import { runDevServerCmd } from './cmds/run-dev.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const require = createRequire(__dirname) @@ -104,19 +102,12 @@ prog process.exit(1) } }) - .command('services start') + .command('services exec [command]') .describe( - 'Run docker compose to setup Cluster, PostgreSQL, PostgREST and Minio' + 'Run docker compose to setup Cluster, PostgreSQL, PostgREST and Minio. Executes your command while services are running, then tears down the docker env.' ) - .option('--project', 'Project name', 'nft-storage-dev') - .action(servicesStartCmd) - .command('services stop') - .describe( - 'Run docker compose to setup Cluster, PostgreSQL, PostgREST and Minio' - ) - .option('--project', 'Project name', 'nft-storage-dev') - .option('--clean', 'Clean all dockers artifacts', false) - .action(servicesStopCmd) + .option('--persistent', 'Whether to enable persistent data volumes', false) + .action(servicesExecCmd) .command('services pull') .describe('pull and build all docker images used for dev/test') .action(servicesPullCmd) @@ -135,5 +126,28 @@ prog .command('minio bucket remove ') .describe('Remove a bucket, automatically removing all contents') .action(minioBucketRemoveCmd) + .command('run test') + .describe( + 'Run the test suite. Any unrecognized positional args will be passed on to the test runner.' + ) + .option( + '--services', + 'Run service dependencies using docker compose and cleanup after the dev server exits. Set to false if running in a custom environment', + true + ) + .action(runTestSuiteCmd) + .command('run dev') + .describe('Run the development API server using miniflare') + .option( + '--services', + 'Run service dependencies using docker compose and cleanup after the dev server exits. Set to false if running in a custom environment', + true + ) + .option( + '--persistent', + 'Whether to enable persistent data volumes. Only has an effect when --services=true', + false + ) + .action(runDevServerCmd) prog.parse(process.argv) diff --git a/packages/api/scripts/cmds/db-types.js b/packages/api/scripts/cmds/db-types.js index 610a0d2462..d43cf18f5e 100755 --- a/packages/api/scripts/cmds/db-types.js +++ b/packages/api/scripts/cmds/db-types.js @@ -1,18 +1,14 @@ import path from 'path' import { fileURLToPath } from 'node:url' import execa from 'execa' -import { servicesStartCmd, servicesStopCmd } from './services.js' +import { runWithServices } from './services.js' import delay from 'delay' import { dbSqlCmd } from './db-sql.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) export async function dbTypesCmd() { - const project = `nft-storage-db-types-${Date.now()}` - await servicesStartCmd({ project }) - await delay(2000) - - try { + await runWithServices(async () => { await dbSqlCmd({ cargo: true, testing: true }) await delay(2000) const url = `${process.env.DATABASE_URL}/?apikey=${process.env.DATABASE_TOKEN}` @@ -29,7 +25,5 @@ export async function dbTypesCmd() { preferLocal: true, } ) - } finally { - await servicesStopCmd({ clean: true, project }) - } + }) } diff --git a/packages/api/scripts/cmds/minio.js b/packages/api/scripts/cmds/minio.js index 5f20979f00..78b310d6a3 100644 --- a/packages/api/scripts/cmds/minio.js +++ b/packages/api/scripts/cmds/minio.js @@ -2,29 +2,32 @@ import { Client as Minio } from 'minio' import retry from 'p-retry' import { isPortReachable } from '../utils.js' -export const MINIO_API_PORT = process.env.MINIO_API_PORT - ? Number.parseInt(process.env.MINIO_API_PORT) - : 9000 - -const minioConfig = { - useSSL: false, - endPoint: '127.0.0.1', - port: MINIO_API_PORT, - accessKey: 'minioadmin', - secretKey: 'minioadmin', +function minioConfig() { + const port = process.env.MINIO_API_PORT + ? Number.parseInt(process.env.MINIO_API_PORT) + : 9000 + + return { + useSSL: false, + endPoint: '127.0.0.1', + port: port, + accessKey: 'minioadmin', + secretKey: 'minioadmin', + } } /** * @param {string} name Bucket name */ export async function minioBucketCreateCmd(name) { + const config = minioConfig() await retry(async () => { - if (!(await isPortReachable(MINIO_API_PORT))) { - throw new Error(`Minio API not reachable on port: ${MINIO_API_PORT}`) + if (!(await isPortReachable(config.port))) { + throw new Error(`Minio API not reachable on port: ${config.port}`) } }) - const minio = new Minio(minioConfig) + const minio = new Minio(config) if (await minio.bucketExists(name)) { return console.log(`Cannot create bucket "${name}": already exists`) @@ -38,7 +41,7 @@ export async function minioBucketCreateCmd(name) { * @param {string} name Bucket name */ export async function minioBucketRemoveCmd(name) { - const minio = new Minio(minioConfig) + const minio = new Minio(minioConfig()) if (!(await minio.bucketExists(name))) { return console.log(`Cannot remove bucket "${name}": not found`) diff --git a/packages/api/scripts/cmds/run-dev.js b/packages/api/scripts/cmds/run-dev.js new file mode 100644 index 0000000000..cebffac63e --- /dev/null +++ b/packages/api/scripts/cmds/run-dev.js @@ -0,0 +1,44 @@ +import execa from 'execa' +import path from 'path' +import { fileURLToPath } from 'url' + +import { runWithServices } from './services.js' +import { dbSqlCmd } from './db-sql.js' +import { minioBucketCreateCmd } from './minio.js' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const workerPath = path.join(__dirname, '../../dist/worker.js') +const envFilePath = path.join(__dirname, '../../../../.env') + +/** + * + * @param {object} opts + * @param {boolean} opts.services + * @param {boolean} opts.persistent + */ +export async function runDevServerCmd({ persistent, services }) { + const action = async () => { + console.log('initializing DB schema') + await dbSqlCmd({ cargo: true, testing: true, reset: true }) + + console.log('creating minio bucket') + await minioBucketCreateCmd('dotstorage-dev-0') + + console.log('running dev server') + await runMiniflare(workerPath, '--watch', '--debug', '--env', envFilePath) + } + + if (services) { + await runWithServices(action, { persistent }) + } else { + await action() + } +} + +/** + * + * @param {string[]} miniflareArgs + */ +async function runMiniflare(...miniflareArgs) { + await execa('npx', ['miniflare', ...miniflareArgs], { stdio: 'inherit' }) +} diff --git a/packages/api/scripts/cmds/run-test.js b/packages/api/scripts/cmds/run-test.js new file mode 100644 index 0000000000..cf30de1367 --- /dev/null +++ b/packages/api/scripts/cmds/run-test.js @@ -0,0 +1,37 @@ +import execa from 'execa' +import { runWithServices } from './services.js' +import { dbSqlCmd } from './db-sql.js' +import { minioBucketCreateCmd } from './minio.js' + +/** + * + * @param {object} opts + * @param {boolean} opts.services + * @param {string[]} opts._ + */ +export async function runTestSuiteCmd(opts) { + const action = async () => { + console.log('initializing DB schema') + await dbSqlCmd({ cargo: true, testing: true }) + + console.log('creating minio bucket') + await minioBucketCreateCmd('dotstorage-dev-0') + + console.log('running tests') + await runAva(opts._) + } + + if (opts.services) { + await runWithServices(action) + } else { + await action() + } +} + +/** + * + * @param {string[]} avaArgs + */ +async function runAva(avaArgs) { + await execa('npx', ['ava', ...avaArgs], { stdio: 'inherit' }) +} diff --git a/packages/api/scripts/cmds/services.js b/packages/api/scripts/cmds/services.js index 4e25e74312..dfc2dea47d 100644 --- a/packages/api/scripts/cmds/services.js +++ b/packages/api/scripts/cmds/services.js @@ -1,76 +1,253 @@ import path from 'path' import { fileURLToPath } from 'url' import execa from 'execa' -import { MINIO_API_PORT } from './minio.js' -import { isPortReachable } from '../utils.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const composeDir = path.join(__dirname, '../../docker') -const composeFiles = [ - 'docker-compose.yml', - 'docker-compose-local-ports.yml', - 'docker-compose-volumes.yml', -] - -const composeFileArgs = composeFiles.flatMap((filename) => [ - '--file', - path.join(composeDir, filename), -]) -const PG_PORT = 3000 -const PGRST_PORT = 5432 -const CLUSTER_PORT = 9094 +const persistentVolumeNames = [ + 'nftstorage-db-data', + 'nftstorage-ipfs-data', + 'nftstorage-cluster-data', + 'nftstorage-minio-data', +] export async function servicesPullCmd() { - await execa('docker-compose', [...composeFileArgs, 'build'], { - stdio: 'inherit', - }) - await execa('docker-compose', [...composeFileArgs, 'pull'], { - stdio: 'inherit', - }) + await runDockerCompose(['build']) + await runDockerCompose(['pull']) } /** - * @param {{ project: string }} opts + * Runs a given command after starting docker-based services (database, etc). + * + * Overrides the following environment variables for the child process: + * - DATABASE_CONNECTION + * - MINIO_API_PORT + * - DATABASE_URL + * - CLUSTER_API_URL + * - S3_ENDPOINT + * + * Automatically stops docker compose environment when command exits (including abnormal exit). + * Restores the original values + * + * @param {string} command a command to run + * @param {{ persistent: boolean }} opts */ -export async function servicesStartCmd({ project }) { - const ports = [PG_PORT, PGRST_PORT, MINIO_API_PORT, CLUSTER_PORT] - const reachablePorts = await Promise.all(ports.map((p) => isPortReachable(p))) - // if any port is reachable a service is running on it - if (reachablePorts.some((r) => r)) { - console.error('⚠️ Services are already running.') +export async function servicesExecCmd(command, { persistent }) { + const action = async () => { + await execa.command(command, { stdio: 'inherit' }) } - await execa( - 'docker-compose', - [...composeFileArgs, '--project-name', project, 'up', '--detach'], - { stdio: 'inherit' } + await runWithServices(action, { persistent }) +} + +/** + * + * @param {() => Promise} action an async action to perform while services are running + * @param {object} args + * @param {boolean} [args.persistent] + */ +export async function runWithServices(action, { persistent } = {}) { + const project = generateProjectName(persistent) + + const composeFiles = ['docker-compose.yml'] + if (persistent) { + composeFiles.push('docker-compose-volumes.yml') + await createNamedVolumes() + } + + const originalEnv = getCurrentEnvVariableValues( + 'DATABASE_CONNECTION', + 'MINIO_API_PORT', + 'DATABASE_URL', + 'CLUSTER_API_URL', + 'S3_ENDPOINT' ) + + const cleanup = async () => { + const composeArgs = ['--project-name', project, 'down', '--remove-orphans'] + if (!persistent) { + composeArgs.push('--volumes') + } + await runDockerCompose(composeArgs, composeFiles) + + // restore original environment variables + setEnvVariables(originalEnv) + } + + registerCleanupHook(cleanup) + + const composeArgs = ['--project-name', project, 'up', '--detach'] + if (!persistent) { + composeArgs.push('--renew-anon-volumes') + } + await runDockerCompose(composeArgs, composeFiles) + + const ports = await getServicePorts({ project }) + const overrides = getConfigOverrides(ports) + console.log('env overrides:', overrides) + + // override environment with vars containing dynamic port numbers. + // note that the original env will be restored in the cleanup hook + setEnvVariables(overrides) + + // run passed in action + await action() } /** - * @param {{ project: string, clean?: boolean }} opts + * + * @param {string[]} args + * @param {string[]} composeFiles */ -export async function servicesStopCmd({ project, clean }) { +async function runDockerCompose(args, composeFiles = ['docker-compose.yml']) { await execa( - 'docker-compose', - [...composeFileArgs, '--project-name', project, 'stop'], + 'docker', + ['compose', ...composeFileArgs(composeFiles), ...args], { stdio: 'inherit' } ) +} - if (clean) { - await execa( - 'docker-compose', - [ - ...composeFileArgs, - '--project-name', - project, - 'down', - '--rmi', - 'local', - '-v', - '--remove-orphans', - ], - { stdio: 'inherit' } - ) +async function createNamedVolumes() { + for (const vol of persistentVolumeNames) { + await execa('docker', ['volume', 'create', vol], { stdio: 'inherit' }) } } + +/** + * + * @param {{ project: string }} args + */ +async function getServicePorts({ project }) { + const [db, postgrest, cluster, minio] = await Promise.all([ + getMappedPort({ project, service: 'db', port: 5432 }), + getMappedPort({ project, service: 'rest', port: 3000 }), + getMappedPort({ project, service: 'cluster', port: 9094 }), + getMappedPort({ project, service: 'minio', port: 9000 }), + ]) + + return { db, postgrest, cluster, minio } +} + +/** + * + * @param {object} ports + * @param {number} ports.db + * @param {number} ports.postgrest + * @param {number} ports.cluster + * @param {number} ports.minio + */ +function getConfigOverrides(ports) { + return { + DATABASE_CONNECTION: `postgres://postgres:postgres@localhost:${ports.db}/postgres`, + MINIO_API_PORT: ports.minio.toString(), + DATABASE_URL: `http://localhost:${ports.postgrest}`, + CLUSTER_API_URL: `http://localhost:${ports.cluster}`, + S3_ENDPOINT: `http://localhost:${ports.minio}`, + } +} + +/** + * + * @param {Record} env + */ +function setEnvVariables(env) { + for (const [k, v] of Object.entries(env)) { + process.env[k] = v + } +} + +/** + * @param {string[]} names + */ +function getCurrentEnvVariableValues(...names) { + /** @type Record */ + const env = {} + for (const name of names) { + const val = process.env[name] + if (typeof val === 'string') { + env[name] = val + } + } + return env +} + +/** + * + * @param {object} opts + * @param {string} opts.project + * @param {string} opts.service + * @param {number} opts.port + */ +async function getMappedPort({ project, service, port }) { + const { stdout } = await execa('docker', [ + 'compose', + ...composeFileArgs(['docker-compose.yml']), + '--project-name', + project, + 'port', + service, + port.toString(), + ]) + const components = stdout.split(':') + const portStr = components[components.length - 1] + return Number.parseInt(portStr) +} + +/** + * @param {string[]} composeFiles + */ +function composeFileArgs(composeFiles) { + return composeFiles.flatMap((filename) => [ + '--file', + path.join(composeDir, filename), + ]) +} + +/** + * + * @param {boolean} persistent + */ +function generateProjectName(persistent = false) { + if (persistent) { + return 'nft-storage-dev-persistent' + } + const timestamp = new Date().getTime() + return `nft-storage-dev-${timestamp}` +} + +/** + * Registers a callback to be invoked in case of any exit condition. + * Will call process.exit after the cleanup function resolves. If an + * error is thrown during cleanup, exits with status code 1, otherwise, + * exits with status code 0. + * + * @param {() => Promise} cleanup an async cleanup function + */ +function registerCleanupHook(cleanup) { + let started = false + + const events = [ + `exit`, + `SIGINT`, + `SIGUSR1`, + `SIGUSR2`, + `uncaughtException`, + `SIGTERM`, + ] + events.forEach((eventType) => { + process.on(eventType, () => { + if (started) { + return + } + started = true + cleanup() + .then(() => { + process.exit(0) + }) + .catch((err) => { + console.error('Error occurred in cleanup hook: ', err) + process.exit(1) + }) + }) + }) +} diff --git a/packages/api/src/utils/db-types.d.ts b/packages/api/src/utils/db-types.d.ts index 4cad90fadc..50b5b94a96 100644 --- a/packages/api/src/utils/db-types.d.ts +++ b/packages/api/src/utils/db-types.d.ts @@ -18,6 +18,7 @@ export interface paths { query: { user_id?: parameters['rowFilter.admin_search.user_id'] email?: parameters['rowFilter.admin_search.email'] + github_id?: parameters['rowFilter.admin_search.github_id'] token?: parameters['rowFilter.admin_search.token'] token_id?: parameters['rowFilter.admin_search.token_id'] deleted_at?: parameters['rowFilter.admin_search.deleted_at'] @@ -904,6 +905,117 @@ export interface paths { } } } + '/user_tag_proposal': { + get: { + parameters: { + query: { + id?: parameters['rowFilter.user_tag_proposal.id'] + user_id?: parameters['rowFilter.user_tag_proposal.user_id'] + tag?: parameters['rowFilter.user_tag_proposal.tag'] + proposed_tag_value?: parameters['rowFilter.user_tag_proposal.proposed_tag_value'] + user_proposal_form?: parameters['rowFilter.user_tag_proposal.user_proposal_form'] + admin_decision_message?: parameters['rowFilter.user_tag_proposal.admin_decision_message'] + admin_decision_type?: parameters['rowFilter.user_tag_proposal.admin_decision_type'] + inserted_at?: parameters['rowFilter.user_tag_proposal.inserted_at'] + deleted_at?: parameters['rowFilter.user_tag_proposal.deleted_at'] + /** Filtering Columns */ + select?: parameters['select'] + /** Ordering */ + order?: parameters['order'] + /** Limiting and Pagination */ + offset?: parameters['offset'] + /** Limiting and Pagination */ + limit?: parameters['limit'] + } + header: { + /** Limiting and Pagination */ + Range?: parameters['range'] + /** Limiting and Pagination */ + 'Range-Unit'?: parameters['rangeUnit'] + /** Preference */ + Prefer?: parameters['preferCount'] + } + } + responses: { + /** OK */ + 200: { + schema: definitions['user_tag_proposal'][] + } + /** Partial Content */ + 206: unknown + } + } + post: { + parameters: { + body: { + /** user_tag_proposal */ + user_tag_proposal?: definitions['user_tag_proposal'] + } + query: { + /** Filtering Columns */ + select?: parameters['select'] + } + header: { + /** Preference */ + Prefer?: parameters['preferReturn'] + } + } + responses: { + /** Created */ + 201: unknown + } + } + delete: { + parameters: { + query: { + id?: parameters['rowFilter.user_tag_proposal.id'] + user_id?: parameters['rowFilter.user_tag_proposal.user_id'] + tag?: parameters['rowFilter.user_tag_proposal.tag'] + proposed_tag_value?: parameters['rowFilter.user_tag_proposal.proposed_tag_value'] + user_proposal_form?: parameters['rowFilter.user_tag_proposal.user_proposal_form'] + admin_decision_message?: parameters['rowFilter.user_tag_proposal.admin_decision_message'] + admin_decision_type?: parameters['rowFilter.user_tag_proposal.admin_decision_type'] + inserted_at?: parameters['rowFilter.user_tag_proposal.inserted_at'] + deleted_at?: parameters['rowFilter.user_tag_proposal.deleted_at'] + } + header: { + /** Preference */ + Prefer?: parameters['preferReturn'] + } + } + responses: { + /** No Content */ + 204: never + } + } + patch: { + parameters: { + query: { + id?: parameters['rowFilter.user_tag_proposal.id'] + user_id?: parameters['rowFilter.user_tag_proposal.user_id'] + tag?: parameters['rowFilter.user_tag_proposal.tag'] + proposed_tag_value?: parameters['rowFilter.user_tag_proposal.proposed_tag_value'] + user_proposal_form?: parameters['rowFilter.user_tag_proposal.user_proposal_form'] + admin_decision_message?: parameters['rowFilter.user_tag_proposal.admin_decision_message'] + admin_decision_type?: parameters['rowFilter.user_tag_proposal.admin_decision_type'] + inserted_at?: parameters['rowFilter.user_tag_proposal.inserted_at'] + deleted_at?: parameters['rowFilter.user_tag_proposal.deleted_at'] + } + body: { + /** user_tag_proposal */ + user_tag_proposal?: definitions['user_tag_proposal'] + } + header: { + /** Preference */ + Prefer?: parameters['preferReturn'] + } + } + responses: { + /** No Content */ + 204: never + } + } + } '/rpc/pgrst_watch': { post: { parameters: { @@ -961,6 +1073,30 @@ export interface paths { } } } + '/rpc/copy_upload_history': { + post: { + parameters: { + body: { + args: { + /** Format: bigint */ + new_user_id: number + /** Format: bigint */ + new_auth_key_id: number + /** Format: bigint */ + old_user_id: number + } + } + header: { + /** Preference */ + Prefer?: parameters['preferParams'] + } + } + responses: { + /** OK */ + 200: unknown + } + } + } '/rpc/create_upload': { post: { parameters: { @@ -990,6 +1126,8 @@ export interface definitions { /** Format: text */ email?: string /** Format: text */ + github_id?: string + /** Format: text */ token?: string /** Format: text */ token_id?: string @@ -1229,6 +1367,7 @@ export interface definitions { /** Format: public.user_tag_type */ tag: | 'HasAccountRestriction' + | 'HasDeleteRestriction' | 'HasPsaAccess' | 'HasSuperHotAccess' | 'StorageLimitBytes' @@ -1244,6 +1383,42 @@ export interface definitions { /** Format: timestamp with time zone */ deleted_at?: string } + user_tag_proposal: { + /** + * Format: bigint + * @description Note: + * This is a Primary Key. + */ + id: number + /** + * Format: bigint + * @description Note: + * This is a Foreign Key to `user.id`. + */ + user_id: number + /** Format: public.user_tag_type */ + tag: + | 'HasAccountRestriction' + | 'HasDeleteRestriction' + | 'HasPsaAccess' + | 'HasSuperHotAccess' + | 'StorageLimitBytes' + /** Format: text */ + proposed_tag_value: string + /** Format: jsonb */ + user_proposal_form: string + /** Format: text */ + admin_decision_message?: string + /** Format: public.user_tag_proposal_decision_type */ + admin_decision_type?: 'Approved' | 'Declined' + /** + * Format: timestamp with time zone + * @default timezone('utc'::text, now()) + */ + inserted_at: string + /** Format: timestamp with time zone */ + deleted_at?: string + } } export interface parameters { @@ -1277,6 +1452,8 @@ export interface parameters { /** Format: text */ 'rowFilter.admin_search.email': string /** Format: text */ + 'rowFilter.admin_search.github_id': string + /** Format: text */ 'rowFilter.admin_search.token': string /** Format: text */ 'rowFilter.admin_search.token_id': string @@ -1424,6 +1601,26 @@ export interface parameters { 'rowFilter.user_tag.inserted_at': string /** Format: timestamp with time zone */ 'rowFilter.user_tag.deleted_at': string + /** @description user_tag_proposal */ + 'body.user_tag_proposal': definitions['user_tag_proposal'] + /** Format: bigint */ + 'rowFilter.user_tag_proposal.id': string + /** Format: bigint */ + 'rowFilter.user_tag_proposal.user_id': string + /** Format: public.user_tag_type */ + 'rowFilter.user_tag_proposal.tag': string + /** Format: text */ + 'rowFilter.user_tag_proposal.proposed_tag_value': string + /** Format: jsonb */ + 'rowFilter.user_tag_proposal.user_proposal_form': string + /** Format: text */ + 'rowFilter.user_tag_proposal.admin_decision_message': string + /** Format: public.user_tag_proposal_decision_type */ + 'rowFilter.user_tag_proposal.admin_decision_type': string + /** Format: timestamp with time zone */ + 'rowFilter.user_tag_proposal.inserted_at': string + /** Format: timestamp with time zone */ + 'rowFilter.user_tag_proposal.deleted_at': string } export interface operations {}