Skip to content

Commit

Permalink
feat: WIP slicing www out of registry
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdickinson committed Jun 6, 2019
1 parent bf18c03 commit f239e24
Show file tree
Hide file tree
Showing 24 changed files with 6,225 additions and 199 deletions.
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ services:
env_file:
- ./services/registry/.env

web:
image: mhart/alpine-node:12
volumes:
- ./services/:/services
working_dir: /services/web
command: npm start
networks:
- entropic
ports:
- "3001:3001"
env_file:
- ./services/web/.env

volumes:
postgres_data:
beanstalk_wal:
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
"lint-fix": "prettier --write '**/*.js'",
"lint-md": "markdownlint \"**/*.md\" -i \"**/node_modules/**\"",
"lint-registry": "cd services/registry; npm run lint",
"postinstall": "for d in cli services/registry services/workers services/common/boltzmann; do cd $d; npm i; cd -; done",
"postinstall": "for d in cli services/registry services/workers services/web services/common/boltzmann; do cd $d; npm i; cd -; done",
"start": "docker-compose up",
"test": "for d in cli services/registry; do cd $d; npm t; cd -; done"
"test": "for d in cli services/registry services/web; do cd $d; npm t; cd -; done"
}
}
4 changes: 3 additions & 1 deletion services/common/boltzmann/middleware/requestid.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

const bole = require('bole');
const uuid = require('uuid');
const os = require('os');

module.exports = createRequestId;

function createRequestId(
requestIdHeader = process.env.REQUEST_ID_HEADER || 'request-id'
) {
const host = os.hostname()
return function mw(next) {
return async function inner(context) {
const request = context.request;
context.id = request.headers[requestIdHeader] || uuid.v1();
context.id = request.headers[requestIdHeader] || `${host}_${uuid.v1()}`;
context.logger = bole(context.id);
const response = await next(context);

Expand Down
45 changes: 45 additions & 0 deletions services/common/storage-api/NOTES
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
storageApi
getProviders()
[Provider]
Provider.redirect
getProvider(name)
getAuthentication({remoteId, provider})
Authentication
user: User
signup({username, email, remoteAuth: {token, id, provider}})
* NOTE: bring email uniqueness check into signup
* NOTE: err.code
- "signup.email_taken"
- "signup.username_taken"
User
getTokens({for: user, page: N})
[objects]
createToken({for: user, description})
deleteToken({for: user, valueHashes: [String]}) -> { count }
resolveCLISession({session, value})
fetchCLISession({session}) -> { description, value? }
createCLISession({description}) -> id String
getActiveMaintainers({namespace, host, name, page})

inviteMaintainer({namespace, host, name, from, to})
* NOTE: err.code
- invite.invitee_dne
- invite.package_dne
- invite.already_accepted
- invite.already_declined
- I think the invite logic might be incorrect around
maintainers.js L75, need to look into this.
removeMaintainer({namespace, host, name, from, to})
* NOTE: err.code
- invite.invitee_dne
- invite.package_dne
- invite.already_accepted
- invite.already_declined
- invite.invitee_not_maintainer (distinct from invitee_dne: namespace exists but is not a maintainer)
acceptMaintainerInvite({namespace, host, name, member, bearer})
* NOTE: err.code
- invite.invitee_dne
- invite.package_dne
- invite.already_accepted
- invite.already_declined
- invite.invitee_not_maintainer (distinct from invitee_dne: namespace exists but is not a maintainer)
10 changes: 10 additions & 0 deletions services/common/storage-api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict'

module.exports = class Client {
constructor ({ host = process.env.STORAGE_HOST, requestId }) {
this.host = host
this.requestId = requestId
}


}
13 changes: 13 additions & 0 deletions services/common/storage-api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions services/common/storage-api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "storage-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Chris Dickinson <chris@neversaw.us> (http://neversaw.us/)",
"license": "Apache-2.0",
"dependencies": {
"node-fetch": "^2.6.0"
}
}
12 changes: 3 additions & 9 deletions services/registry/handlers/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,9 @@ module.exports = {
async function login(context) {
// this is all that's required to work with npm-profile.
const body = await json(context.request);
const id = uuid.v4();
await context.redis.setexAsync(
`cli_${id}`,
5000,
JSON.stringify({
const id = await context.storageApi.createCLISession({
description: body.hostname
})
);

})
return response.json({
doneUrl: `${process.env.EXTERNAL_HOST}/-/v1/login/poll/${id}`,
loginUrl: `${process.env.EXTERNAL_HOST}/www/login?cli=${id}`
Expand All @@ -41,7 +35,7 @@ async function poll(context, { session }) {
return response.error('invalid request', 400);
}
const result = JSON.parse(
(await context.redis.getAsync(`cli_${session}`)) || '{}'
(await context.storageApi.fetchCLISession({session})) || '{}'
);
if (result.value) {
return response.json({
Expand Down
1 change: 0 additions & 1 deletion services/registry/handlers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ function makeRouter() {
...require('./packages'),
...require('./maintainers'),
...require('./namespaces'),
...require('./www'),

fork.get('/-/v1/login/poll/:session', auth.poll),
fork.post('/-/v1/login', auth.login),
Expand Down
167 changes: 68 additions & 99 deletions services/registry/handlers/maintainers.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,71 +33,55 @@ module.exports = [
];

async function maintainers(context, { namespace, host, name }) {
const pkg = await Package.objects
.get({
active: true,
name,
'namespace.active': true,
'namespace.name': namespace
})
.catch(Package.objects.NotFound, () => null);
const [err, maintainers] = await context.storageApi.getActiveMaintainers({namespace, host, name, page: 0}).then(
xs => [null, xs],
xs => [xs, null]
)

if (!pkg) {
if (err) {
if (err.status === 404) {
return response.error(
`"${namespace}@${host}/${name}" does not exist.`,
404
);
}

context.logger.error(err.message || err)
return response.error(
`"${namespace}@${host}/${name}" does not exist.`,
404
`Caught error fetching maintainers for "${namespace}@${host}/${name}".`,
500
);
}

const namespaces = await Namespace.objects
.filter({
'maintainers.package_id': pkg.id,
'maintainers.accepted': true,
'maintainers.active': true
})
.then();

const objects = namespaces.map(ns => ns.name).sort();
return response.json({ objects });
}

// Invite a maintainer. Maintainers are namespaces, which might be a single user or a group of users.
// More correctly: maintainership is a relationship between a namespace and a package.
async function invite(context, { namespace, host, name, invitee }) {
if (!context.pkg) {
return response.error(`"${namespace}@${host}/${name}" not found.`, 404);
}

if (!context.invitee) {
return response.error(`${invitee} not found.`, 404);
}
const [err, invite] = await context.storageApi.inviteMaintainer({
namespace,
host,
name,
from: context.user.name,
to: invitee
}).then(
xs => [null, xs],
xs => [xs, null]
)

const existing = await Maintainer.objects
.get({
namespace: context.invitee,
package: context.pkg
})
.catch(Maintainer.objects.NotFound, () => null);
if (existing) {
if (existing.active === false) {
return response.message(
`${invitee} has already declined to maintain ${namespace}@${host}/${name}.`
);
}
if (existing.accepted === false) {
return response.message(
`${invitee} has already been invited to maintain ${namespace}@${host}/${name}.`
);
}
if (err) {
const msg = {
'invite.invitee_dne': `Unknown namespace: "${invitee}".`,
'invite.package_dne': `Unknown package: "${invitee}".`,
'invite.already_accepted': `Namespace "${invitee}" is already a member.`,
'invite.already_declined': `Namespace "${invitee}" has declined this invite.`
}[err.code]
return response.error(
msg || `Caught error inviting "${invitee}" to ${namespace}@${host}/${name}`,
err.code
);
}

await Maintainer.objects.create({
namespace: context.invitee,
package: context.pkg,
accepted: false,
active: true
});

context.logger.info(
`${invitee} invited to join the maintainers of ${namespace}@${host}/${name} by ${
context.user.name
Expand All @@ -109,35 +93,32 @@ async function invite(context, { namespace, host, name, invitee }) {
}

async function remove(context, { namespace, host, name, invitee }) {
if (!context.pkg) {
return response.error(
`"${namespace}@${host}/${name}" does not exist.`,
404
);
}

if (!context.invitee) {
return response.error(`${invitee} does not exist.`, 404);
}
const [err, invite] = await context.storageApi.removeMaintainer({
namespace,
host,
name,
from: context.user.name,
to: invitee
}).then(
xs => [null, xs],
xs => [xs, null]
)

const maintainership = await Maintainer.objects
.filter({
package_id: context.pkg.id,
namespace_id: context.invitee.id,
active: true
})
.slice(0, 1)
.update({
modified: new Date(),
active: false
})
.then();
if (err) {
const msg = {
'invite.invitee_dne': `Unknown namespace: "${invitee}".`,
'invite.invitee_not_maintainer': `${invitee} was not a maintainer of ${namespace}@${host}/${name}.`
'invite.package_dne': `Unknown package: "${invitee}".`,
'invite.already_accepted': `Namespace "${invitee}" is already a member.`,
'invite.already_declined': `Namespace "${invitee}" has declined this invite.`
}[err.code]

if (maintainership.length === 0) {
return response.message(
`${invitee} was not a maintainer of ${namespace}@${host}/${name}.`
return response.error(
msg || `Caught error inviting "${invitee}" to ${namespace}@${host}/${name}`,
err.code
);
}

context.logger.info(
`${invitee} removed as maintainer of ${namespace}@${host}/${name} by ${
context.user.name
Expand All @@ -150,28 +131,16 @@ async function remove(context, { namespace, host, name, invitee }) {
}

async function accept(context, { namespace, host, name, member }) {
const invitation = await Maintainer.objects
.get({
namespace_id: context.member.id,
package_id: context.pkg.id,
active: true,
accepted: false
})
.catch(Maintainer.objects.NotFound, () => null);

if (!invitation) {
return response.error('invitation not found', 404);
}

await Maintainer.objects
.filter({
id: invitation.id
})
.update({
modified: new Date(),
accepted: true
});

const [err] = await context.storageApi.acceptMaintainerInvite({
namespace,
host,
name,
member,
bearer: context.user.name
}).then(
xs => [null, xs],
xs => [xs, null]
)
context.logger.info(
`${
context.user.name
Expand Down
1 change: 0 additions & 1 deletion services/registry/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const myMiddles = [
require('./middleware/postgres'),
require('./middleware/transaction'),
require('boltzmann/middleware/redis'),
require('./middleware/session'),
require('./middleware/bearer-auth'),
require('./middleware/object-store')
];
Expand Down
Loading

0 comments on commit f239e24

Please sign in to comment.