Skip to content

Commit

Permalink
Distinguish between a "path" ("/pos/rot") and an "address" (netaddr/p…
Browse files Browse the repository at this point in the history
…os/rot)

    Rename PlaceEntity.address to be PlaceEntity.path
    Define get/set for PlaceEntity fields for "path" and "address" with the
        latter returning a "domain-network-address/pos/rot"
    Restrict PlaceEntity.path validation to "/f,f,f/f,f,f,f"
    Update DB to rename existing database fields address->path
    Include both "path" and "address" in returned Place JSON information
Add new Places functions:
    Places.getCurrentInfoAPIKey()
    Places.getAddressString()
Optionally include APIKey in returned Place JSON for domain owner
Add 'Attendance" and current place info to /explore.json response
Update API.md description of the place operations
  • Loading branch information
Misterblue committed Feb 4, 2021
1 parent c7d6150 commit d6eaf01
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 37 deletions.
1 change: 1 addition & 0 deletions docs/API-Places.md
Expand Up @@ -88,6 +88,7 @@ This request return JSON formatted as:
{
"placeId": string,
"name": string,
"path": string,
"address": string,
"description": string,
"maturity": string, // one of 'unrated', 'everyone', 'teen', 'mature', 'adult'
Expand Down
8 changes: 4 additions & 4 deletions docs/API.md
Expand Up @@ -70,10 +70,10 @@ The API requests to the metaverse-server are:
| GET | /api/v1/user/location | [doc](./API-Users.md#get-apiv1userprofile) get this user's profile |
| GET | /api/v1/user/places/ | [doc](./API-places.md#get-apiv1userplaces) fetch requesting user's places |
| POST | /api/v1/user/places/ | [doc](./API-places.md#post-apiv1userplaces) create a place |
| GET | /api/v1/user/places/:placeId | [doc](./API-places.md#get-apiv1userplacesplaceId) get information on specific place |
| DELETE | /api/v1/user/places/:placeId | [doc](./API-places.md#delete-apiv1userplacesplaceId) delete a place |
| GET | /api/v1/user/places/:placeId/field/:fieldname | [doc](./API-places.md#get-apiv1userplacesplaceidfieldfieldname) fetch specific place parameter |
| POST | /api/v1/user/places/:placeId/field/:fieldname | [doc](./API-places.md#post-apiv1userplacesplaceidfieldfieldname) set specific place parameter |
| GET | /api/v1/places/:placeId | [doc](./API-places.md#get-apiv1placesplaceId) get information on specific place |
| DELETE | /api/v1/places/:placeId | [doc](./API-places.md#delete-apiv1placesplaceId) delete a place |
| GET | /api/v1/places/:placeId/field/:fieldname | [doc](./API-places.md#get-apiv1placesplaceidfieldfieldname) fetch specific place parameter |
| POST | /api/v1/places/:placeId/field/:fieldname | [doc](./API-places.md#post-apiv1placesplaceidfieldfieldname) set specific place parameter |
| PUT | /api/v1/user/public_key | [doc](./API-Users.md#put-apiv1userpublic_key) update this user's public key |
| GET | /api/v1/users | [doc](./API-Users.md#get-apiv1users) fetch list of user information |
| POST | /api/v1/users | [doc](./API-Users.md#post-apiv1users) create account |
Expand Down
2 changes: 1 addition & 1 deletion src/Entities/PlaceEntity.ts
Expand Up @@ -25,7 +25,7 @@ export class PlaceEntity implements Entity {
public maturity: string; // maturity level of the place (see Sets/Maturity.ts)
public tags: string[]; // tags defining the string content
public domainId: string; // domain the place is in
public address: string; // Address within the domain
public path: string; // address within the domain: "optional-domain/x,y,z/x,y,z,x"
public thumbnail: string; // thumbnail for place
public images: string[]; // images for the place

Expand Down
24 changes: 10 additions & 14 deletions src/Entities/PlaceFields.ts
Expand Up @@ -117,17 +117,19 @@ export const placeFields: { [key: string]: FieldDefn } = {
setter: simpleSetter,
getter: simpleGetter
},
'address': {
entity_field: 'address',
'address': { // The network address of a location in the domain
entity_field: 'path',
request_field_name: 'address',
get_permissions: [ Perm.ALL ],
set_permissions: [ Perm.DOMAINACCESS, Perm.ADMIN ],
set_permissions: [ Perm.NONE ],
validate: isPathValidator,
setter: simpleSetter,
getter: simpleGetter
setter: noSetter,
getter: async (pField: FieldDefn, pEntity: Entity): Promise<any> => {
return Places.getAddressString(pEntity as PlaceEntity);
}
},
'path': { // alternate external name for 'address'
entity_field: 'address',
'path': { // the address within the domain
entity_field: 'path',
request_field_name: 'path',
get_permissions: [ Perm.ALL ],
set_permissions: [ Perm.DOMAINACCESS, Perm.ADMIN ],
Expand Down Expand Up @@ -223,13 +225,7 @@ export const placeFields: { [key: string]: FieldDefn } = {
setter: noSetter,
getter: async (pField: FieldDefn, pEntity: Entity): Promise<any> => {
if (pEntity.hasOwnProperty('currentAPIKeyTokenId')) {
const tokenId = (pEntity as PlaceEntity).currentAPIKeyTokenId;
if (IsNotNullOrEmpty(tokenId)) {
const aToken = await Tokens.getTokenWithTokenId(tokenId);
if (aToken) {
return aToken.token;
};
};
return Places.getCurrentInfoAPIKey(pEntity as PlaceEntity);
};
return 'unknown';
}
Expand Down
33 changes: 30 additions & 3 deletions src/Entities/Places.ts
Expand Up @@ -56,7 +56,7 @@ export const Places = {
const newPlace = new PlaceEntity();
newPlace.id = GenUUID();
newPlace.name = 'UNKNOWN-' + genRandomString(5);
newPlace.address = '/0,0,0/0,0,0,0';
newPlace.path = '/0,0,0/0,0,0,1';
newPlace.whenCreated = new Date();
const APItoken = await Tokens.createToken(pAccountId, [ TokenScope.PLACE ], -1);
newPlace.currentAPIKeyTokenId = APItoken.id;
Expand Down Expand Up @@ -124,7 +124,7 @@ export const Places = {
// If the last current update is stale (older than a few minutes), the domain's number is used
let attendance: number = 0;
let useDomain: boolean = true;
let lastGoodUpdateTime = new Date(Date.now()
const lastGoodUpdateTime = new Date(Date.now()
- (Config['metaverse-server']['place-current-timeout-minutes'] * 60 * 1000));
if (IsNotNullOrEmpty(pPlace.currentLastUpdateTime) && pPlace.currentLastUpdateTime > lastGoodUpdateTime) {
attendance = pPlace.currentAttendance;
Expand All @@ -133,13 +133,40 @@ export const Places = {
if (useDomain) {
// There isn't current attendance info. Default to domain's numbers
if (IsNullOrEmpty(pPlace.domainId)) {
let aDomain = await Domains.getDomainWithId(pPlace.domainId);
const aDomain = await Domains.getDomainWithId(pPlace.domainId);
if (IsNotNullOrEmpty(aDomain)) {
attendance = (aDomain.numUsers ?? 0) + (aDomain.anonUsers ?? 0);
};
};
};
return attendance;

},
async getCurrentInfoAPIKey(pPlace: PlaceEntity): Promise<string> {
// Return that APIKey value from the access token
let key: string;
const keyToken = await Tokens.getTokenWithTokenId(pPlace.currentAPIKeyTokenId);
if (IsNotNullOrEmpty(keyToken)) {
key = keyToken.token;
};
return key;
},
async getAddressString(pPlace: PlaceEntity): Promise<string> {
// Compute and return the string for the Places's address.
// The address is of the form "optional-domain/x,y,z/x,y,z,w".
// If the domain is missing, the domain-server's network address is added
let addr = pPlace.path ?? '/0,0,0/0,0,0,1';
const pieces = addr.split('/');
if (pieces[0].length === 0) {
const aDomain = await Domains.getDomainWithId(pPlace.domainId);
if (IsNotNullOrEmpty(aDomain)) {
let domainAddr = aDomain.networkAddr;
if (IsNotNullOrEmpty(aDomain.networkPort)) {
domainAddr = aDomain.networkAddr + ":" + aDomain.networkPort;
};
addr = domainAddr + addr;
};
};
return addr;
}
};
1 change: 1 addition & 0 deletions src/Tools/Db.ts
Expand Up @@ -275,6 +275,7 @@ async function DoDatabaseFormatChanges() {
await RenameDbField(accountCollection, 'whenAccountCreated', 'whenCreated');

await RenameDbField(placeCollection, 'placeId', 'id');
await RenameDbField(placeCollection, 'address', 'path');
await RenameDbField(placeCollection, 'whenPlaceEntryCreated', 'whenCreated');

await RenameDbField(tokenCollection, 'tokenId', 'id');
Expand Down
3 changes: 2 additions & 1 deletion src/route-tools/Util.ts
Expand Up @@ -268,7 +268,8 @@ export async function buildPlaceInfoSmall(pPlace: PlaceEntity): Promise<any> {
'placeId': pPlace.id,
'id': pPlace.id,
'name': pPlace.name,
'address': pPlace.address,
'address': Places.getAddressString(pPlace),
'path': pPlace.path,
'description': pPlace.description,
'maturity': pPlace.maturity ?? Maturity.UNRATED,
'tags': pPlace.tags,
Expand Down
7 changes: 5 additions & 2 deletions src/route-tools/Validators.ts
Expand Up @@ -50,10 +50,13 @@ export async function isBooleanValidator(pField: FieldDefn, pEntity: Entity, pVa
};
export async function isPathValidator(pField: FieldDefn, pEntity: Entity, pValue: any): Promise<ValidateResponse> {
// Regexp to check format of "domainname/float,float,float/float,float,float,float"
if (/^[\w +_-]*\/-?\d+(\.\d*)?,-?\d+(\.\d*)?,-?\d+(\.\d*)?\/-?\d+(\.\d*)?,-?\d+(\.\d*)?,-?\d+(\.\d*)?,-?\d+(\.\d*)?$/.test(pValue)) {
// if (/^[\w\.:+_-]*\/-?\d+(\.\d*)?,-?\d+(\.\d*)?,-?\d+(\.\d*)?\/-?\d+(\.\d*)?,-?\d+(\.\d*)?,-?\d+(\.\d*)?,-?\d+(\.\d*)?$/.test(pValue)) {
// Regexp to check format of "/float,float,float/float,float,float,float"
// This make a "path" just the position and rotation within a domain
if (/^\/-?\d+(\.\d*)?,-?\d+(\.\d*)?,-?\d+(\.\d*)?\/-?\d+(\.\d*)?,-?\d+(\.\d*)?,-?\d+(\.\d*)?,-?\d+(\.\d*)?$/.test(pValue)) {
return { valid: true };
};
return { valid: false, reason: 'path must have the form "domain/f,f,f/f,f,f,f' };
return { valid: false, reason: 'path must have the form "optional-network-addr/f,f,f/f,f,f,f' };
};
export async function isDateValidator(pField: FieldDefn, pEntity: Entity, pValue: any): Promise<ValidateResponse> {
if (pValue instanceof Date) {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/api/v1/places.ts
Expand Up @@ -96,7 +96,7 @@ export const procPostPlaces: RequestHandler = async (req: Request, resp: Respons
const newPlace = await Places.createPlace(aDomain.sponsorAccountId);
newPlace.name = requestedName;
newPlace.description = requestedDesc;
newPlace.address = requestedAddr;
newPlace.path = requestedAddr;
newPlace.domainId = aDomain.id;
newPlace.maturity = aDomain.maturity ?? Maturity.UNRATED;
Places.addPlace(newPlace);
Expand Down
13 changes: 10 additions & 3 deletions src/routes/api/v1/places/placeId.ts
Expand Up @@ -20,6 +20,8 @@ import { Router, RequestHandler, Request, Response, NextFunction } from 'express
import { setupMetaverseAPI, finishMetaverseAPI, param1FromParams, placeFromParams } from '@Route-Tools/middleware';
import { accountFromAuthToken } from '@Route-Tools/middleware';

import { Tokens } from '@Entities/Tokens';

import { Perm } from '@Route-Tools/Perm';
import { checkAccessToEntity } from '@Route-Tools/Permissions';

Expand All @@ -28,14 +30,19 @@ import { buildPlaceInfo } from '@Route-Tools/Util';
import { Places } from '@Entities/Places';
import { Maturity } from '@Entities/Sets/Maturity';

import { IsNullOrEmpty } from '@Tools/Misc';
import { IsNotNullOrEmpty, IsNullOrEmpty } from '@Tools/Misc';
import { VKeyedCollection } from '@Tools/vTypes';
import { Logger } from '@Tools/Logging';

export const procGetPlacesPlaceId: RequestHandler = async (req: Request, resp: Response, next: NextFunction) => {
if (req.vPlace) {
const placeInfo = await buildPlaceInfo(req.vPlace);
// If the requestor is the 'owner' of the place, add APIKey to the response
if (checkAccessToEntity(req.vAuthToken, req.vPlace, [ Perm.DOMAINACCESS, Perm.ADMIN ], req.vAuthAccount)) {
placeInfo.current_api_key = Places.getCurrentInfoAPIKey(req.vPlace);
};
req.vRestResp.Data = {
'place': await buildPlaceInfo(req.vPlace),
'place': placeInfo,
'maturity-categories': Maturity.MaturityCategories
};
}
Expand All @@ -62,7 +69,7 @@ export const procPutPlacesPlaceId: RequestHandler = async (req: Request, resp: R
updates.domainId = req.vPlace.domainId;
};
};
for (const field of [ 'path', 'address', 'description', 'thumbnail' ]) {
for (const field of [ 'path', 'description', 'thumbnail' ]) {
if (req.body.place.hasOwnProperty(field)) {
await Places.setField(req.vAuthToken, req.vPlace, field, req.body.place[field], req.vAuthAccount, updates);
};
Expand Down
15 changes: 7 additions & 8 deletions src/routes/explore.ts
Expand Up @@ -43,14 +43,8 @@ const procGetExplore: RequestHandler = async (req: Request, resp: Response, next
const placeDesc: VKeyedCollection = {
'Domain Name': place.name,
};
// Build redirection URL for getting to the place
let visit = 'hifi://' + aDomain.networkAddr;
if (IsNotNullOrEmpty(aDomain.networkPort)) {
visit += ':' + aDomain.networkPort;
};
// visit += '/' + place.address;
visit += place.address;
placeDesc.Visit = visit;
placeDesc.Address = Places.getAddressString(place);
placeDesc.Visit = 'hifi://' + placeDesc.Address;

placeDesc.DomainId = aDomain.id;
placeDesc['Network Address'] = aDomain.networkAddr;
Expand All @@ -65,8 +59,13 @@ const procGetExplore: RequestHandler = async (req: Request, resp: Response, next
};
};

// 'People' is number of people at place for old Explore script
placeDesc.People = aDomain.numUsers + aDomain.anonUsers;

placeDesc.Attendance = Places.getCurrentAttendance(place);
placeDesc.CurrentImages = place.currentImages;
placeDesc.CurrentInfo = place.currentInfo;

allPlaces.push(placeDesc);
};
};
Expand Down

0 comments on commit d6eaf01

Please sign in to comment.