Browse Source

basic API progress

develop
Rob Colbert 2 years ago
parent
commit
9010a4d4d0
  1. 72
      app/controllers/core.js
  2. 76
      app/controllers/core/info.js
  3. 109
      app/controllers/core/user.js
  4. 17
      app/services/user.js
  5. 11
      app/views/admin/settings/editor.pug
  6. 1
      config/site.js
  7. 47
      docs/core.md
  8. 2
      dtp-webapp.js

72
app/controllers/core.js

@ -0,0 +1,72 @@
// core.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const DTP_COMPONENT_NAME = 'core';
const path = require('path');
const express = require('express');
const { SiteController } = require('../../lib/site-lib');
class CoreController extends SiteController {
constructor (dtp) {
super(dtp, DTP_COMPONENT_NAME);
this.services = [ ];
}
async start ( ) {
const router = express.Router();
this.dtp.app.use('/core', router);
router.use(
async (req, res, next) => {
res.locals.currentView = 'core';
/*
* TODO: H1V3 authentication before processing request (HTTP Bearer token)
*/
return next();
},
);
router.use('/info', await this.loadChild(path.join(__dirname, 'core', 'info')));
this.services.push({ name: 'info', url: '/core/info' });
router.use('/user', await this.loadChild(path.join(__dirname, 'core', 'user')));
this.services.push({ name: 'user', url: '/core/user' });
router.get('/', this.getCoreRoot.bind(this));
return router;
}
async getCoreRoot (req, res) {
res.status(200).json({
component: DTP_COMPONENT_NAME,
site: res.locals.site/*{
name: this.dtp.config.site.name,
description: this.dtp.config.site.description,
domain: this.dtp.config.site.domain,
domainKey: this.dtp.config.site.domainKey,
settings: ,
}*/,
description: this.dtp.pkg.description,
version: this.dtp.pkg.version,
services: this.services,
});
}
}
module.exports = {
slug: 'core',
name: 'core',
create: async (dtp) => {
let controller = new CoreController(dtp);
return controller;
},
};

76
app/controllers/core/info.js

@ -0,0 +1,76 @@
// core/info.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const DTP_COMPONENT_NAME = 'core:info';
const express = require('express');
const { SiteController } = require('../../../lib/site-lib');
class CoreInfoController extends SiteController {
constructor (dtp) {
super(dtp, DTP_COMPONENT_NAME);
this.methods = [
{
name: 'getPackageInfo',
url: '/package',
method: 'GET',
},
{
name: 'getSiteInfo',
url: '/site',
method: 'GET',
},
];
}
async start ( ) {
const router = express.Router();
router.use(async (req, res, next) => {
res.locals.currentView = 'core';
res.locals.hiveView = 'info';
return next();
});
router.get('/package', this.getPackageInfo.bind(this));
router.get('/site', this.getSiteInfo.bind(this));
router.get('/', this.getCoreRoot.bind(this));
return router;
}
async getPackageInfo (req, res) {
res.status(200).json({
name: this.dtp.pkg.name,
version: this.dtp.pkg.version,
description: this.dtp.pkg.description,
author: this.dtp.pkg.author,
license: this.dtp.pkg.license,
private: this.dtp.pkg.private,
});
}
async getSiteInfo (req, res) {
res.status(200).json(res.locals.site);
}
async getCoreRoot (req, res) {
res.status(200).json({
component: DTP_COMPONENT_NAME,
version: this.dtp.pkg.version,
services: this.services,
methods: this.methods,
});
}
}
module.exports = async (dtp) => {
let controller = new CoreInfoController(dtp);
return controller;
};

109
app/controllers/core/user.js

@ -0,0 +1,109 @@
// core/user.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const DTP_COMPONENT_NAME = 'core:user';
const express = require('express');
const { SiteController, SiteError } = require('../../../lib/site-lib');
class CoreUserController extends SiteController {
constructor (dtp) {
super(dtp, DTP_COMPONENT_NAME);
this.methods = [
{
name: 'getUserProfile',
url: '/:userId',
method: 'GET',
},
];
}
async start ( ) {
const router = express.Router();
router.use(async (req, res, next) => {
res.locals.currentView = 'core';
res.locals.hiveView = 'info';
return next();
});
router.param('userId', this.populateUserId.bind(this));
router.get('/resolve', this.getResolveUser.bind(this));
router.get('/:userId', this.getUserProfile.bind(this));
router.get('/', this.getCoreUserRoot.bind(this));
return router;
}
async populateUserId (req, res, next, userId) {
const { user: userService } = this.dtp.services;
try {
res.locals.user = await userService.getUserProfile(userId);
if (!res.locals.user) {
throw new SiteError(404, 'User profile not found');
}
res.locals.user = userService.filterUserObject(res.locals.user);
res.locals.user.picture.large = `/image/${res.locals.user.picture.large._id}`;
res.locals.user.picture.small = `/image/${res.locals.user.picture.small._id}`;
return next();
} catch (error) {
this.log.error('failed to provide User profile', { userId, error });
return res.status(error.statusCode || 500).json({ success: false, message: error.message });
}
}
async getResolveUser (req, res) {
const { user: userService } = this.dtp.services;
try {
if (!req.query.search || !req.query.search.length) {
throw new SiteError(406, 'Must include search term');
}
res.locals.search = await userService.filterUsername(req.query.search);
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.users = await userService.getUserAccounts(res.locals.pagination, res.locals.search);
res.locals.users = res.locals.users.map((user) => {
const apiUser = userService.filterUserObject(user);
apiUser.picture.large = `/image/${user.picture.large}`;
apiUser.picture.small = `/image/${user.picture.small}`;
return apiUser;
});
res.status(200).json({
success: true,
pagination: res.locals.pagination,
count: res.locals.users.length,
search: res.locals.search,
users: res.locals.users,
});
} catch (error) {
this.log.error("failed to resolve user accounts", { error });
res.status(error.statusCode || 500).json({ success: false, message: error.message });
}
}
async getUserProfile (req, res) {
res.status(200).json(res.locals.user);
}
async getCoreUserRoot (req, res) {
res.status(200).json({
component: DTP_COMPONENT_NAME,
version: this.dtp.pkg.version,
services: this.services,
methods: this.methods,
});
}
}
module.exports = async (dtp) => {
let controller = new CoreUserController(dtp);
return controller;
};

17
app/services/user.js

@ -385,13 +385,24 @@ class UserService {
}
filterUserObject (user) {
return {
const filteredUser = {
_id: user._id,
email: user.email,
created: user.created,
displayName: user.displayName,
username: user.username,
username_lc: user.username_lc,
bio: user.bio,
flags: user.flags,
permissions: user.permissions,
picture: user.picture,
};
if (filteredUser.flags && filteredUser.flags._id) {
delete filteredUser.flags._id;
}
if (filteredUser.permissions && filteredUser.permissions._id) {
delete filteredUser.permissions._id;
}
return filteredUser;
}
async getUserAccount (userId) {
@ -431,7 +442,7 @@ class UserService {
user = User.findOne({ username: userId });
}
user = await user
.select('+email +flags +settings')
.select('+email +flags +permissions +settings')
.populate(this.populateUser)
.lean();
return user;

11
app/views/admin/settings/editor.pug

@ -3,7 +3,7 @@ block content
form(method="POST", action="/admin/settings").uk-form
fieldset
legend Site Information
legend Site Settings
.uk-margin
label(for="name").uk-form-label Site name
input(id="name", name="name", type="text", maxlength="200", placeholder="Enter site name", value= site.name).uk-input
@ -14,4 +14,13 @@ block content
label(for="company").uk-form-label Company name
input(id="company", name="company", type="text", maxlength="200", placeholder="Enter company name", value= site.company).uk-input
fieldset
legend Network Settings
.uk-margin
label(for="network-policy").uk-form-label Constellation Policy
select(id="network-policy", name="networkPolicy").uk-select
option(value="closed", selected= (site.networkPolicy === 'closed')) Closed
option(value="approval-required", selected= (site.networkPolicy === 'approval-required')) Approval Required
option(value="open", selected= (site.networkPolicy === 'open')) Open
button(type="submit").uk-button.dtp-button-primary Save Settings

1
config/site.js

@ -10,4 +10,5 @@ module.exports = {
domain: process.env.DTP_SITE_DOMAIN,
domainKey: process.env.DTP_SITE_DOMAIN_KEY,
company: process.env.DTP_SITE_COMPANY || 'Digital Telepresence, LLC',
networkPolicy: 'closed',
};

47
docs/core.md

@ -0,0 +1,47 @@
# DTP Core
The Digital Telepresence Platform is distributed. There are nodes running everywhere generating events on behalf of community members. Cores define those communities. People create and manage member accounts on one or more Core nodes. Application nodes then request to join a Core's community. If accepted, the application node can then offer it's services to that Core's members.
## Application Node Connections (Core Connect)
Your Core node manages member accounts, and also serves as an identity provider to Application nodes. DTP Sites, Social, Venue, Links, etc., are all examples of Application nodes. They each implement a specific application, and they connect to the DTP Global Core to provide their services to that community.
## Core-to-Core Connections (Constellation Connect)
Your Core node defines a community of people and the services they use. There can and will be many Core nodes with unique collections of members and services, and Core nodes can connect with each other to share information. When a Core connects to one or more other Core(s), they have formed a Constellation.
### Open Core
An Open Core automatically accepts Core and Constellation Connect requests to join the community. The administrator is not presented with a connection request, and their approval is not required. The connection is automatic, OAuth2 credentials are immediately sent, and the connection is immediately established.
### Selective Core
A Selective Core receives and stores Core and Constellation Connect requests, and delivers them to the Site Administrator for review and consideration. The administrator can approve or reject each request.
If a Core Connect request is approved, OAuth2 credentials are delivered to the Application node, and it can begin authenticating and authorizing your community members to use the service it provides, and also (optionally) start contributing events to the Community Timeline.
If a Constellation Connect request is approved, OAuth2 credentials are delivered to the Core node, and it can begin sending and receiving Constellation Timeline events.
### Closed Core
A Closed Core automatically rejects and forgets all Core and Constellation Connect requests. Closed Core systems must manually add Application nodes to their Community, and other Core nodes to their Constellation.
## Core Connect Procedure
Application node administrators will configure their node, then run `dtp-webapp-cli.js` to request a connection to a DTP Core node as follows:
```
node dtp-webapp-cli.js --action=core-connect host:port
```
The process will either automatically connect the application node (Open Core), deliver the connection request to the Core Admin (approval required), or deny the request (Closed Core). If approval is required, your node will update automatically if approved, and let you know if you've been denied.
## Constellation Connect Procedure
Core node administrators will configure their Core, then run `dtp-webapp-cli.js` to send a Constellation Connect request to another Core node as follows:
```
node dtp-webapp-cli.js --action=constellation-connect host:port
```
The process will either automatically add your Core to the Constellation, deliver the connection request to the Core Admin (approval required), or deny the request (Closed Core). If approval is required, your Core will update automatically and join the Constellation if approved.

2
dtp-webapp.js

@ -4,7 +4,7 @@
'use strict';
const DTP_COMPONENT_NAME = 'webapp';
const DTP_COMPONENT_NAME = 'core';
require('dotenv').config();

Loading…
Cancel
Save