diff --git a/docs/API-Domains.md b/docs/API-Domains.md index c05d58a3..2a9325c1 100755 --- a/docs/API-Domains.md +++ b/docs/API-Domains.md @@ -23,6 +23,7 @@ A request returns an array of domain descriptions: "domainId": stringDomainId, "id": stringDomainId, "name": stringName, + "visibility": string, // one of 'open', 'friends', 'connections', 'private' "label": stringName, "public_key": stringPublicKey, "sponsor_account_id": stringAccountIdAssociated, @@ -136,6 +137,7 @@ The domain fields that can be fetched: | --------- | -------- | -------- | ---- | | domainId | all | noone |string | | name | all | domain sponsor admin | string | +| visibility | all | domain sponsor admin | string | | public_key | all | domain | string | | sponsor_account_id | all | domain sponsor admin | string | | version | all | domain | string | diff --git a/docs/API-Explore.md b/docs/API-Explore.md index 7acf13f3..038ad34f 100644 --- a/docs/API-Explore.md +++ b/docs/API-Explore.md @@ -6,6 +6,12 @@ This request returns Place information that is compatible with this legacy dialo There are some additional fields providing new information that could be used by future scripts. +Since this mimics the legacy 'Explore' request, the returned Place data gives +the name of the listed Place as "Domain name". +The "Domain name" field will have the same value as "Place.name". +The "DomainId", though, is the ID of the domain that Place is in and additional +Domain information is given under "Place. + ## GET /explore.json This request takes a number of parameters to control the list of places returned. @@ -16,12 +22,16 @@ This request takes a number of parameters to control the list of places returned | tag | get places that have specified tags (comma separated list. Any tag matches) | | per_page | number of entries to return per request | | page_num | which "page" of entries to return | +| order | comma separated list of 'ascending', 'decending', 'num_users', 'name' | +| search | search on the place name. ( "search=fred" will return all Places whos name includes "fred") | +| status | comma separated list of 'online' (domain is heartbeating), 'active' (has attendees) | So, a legal request could be: ``` GET /explore.json?per_page=20&page_num=4 GET /explore.json?maturity=adult + GET /explore.json?order=ascending,num_users&maturity=unrated GET /explore.json?tag=friendly,kids,sandbox The response is a JSON array of Place descriptions. @@ -29,15 +39,32 @@ The response is a JSON array of Place descriptions. ``` [ { - "Domain name": "place name", - "Address": "domain-network-address/float,float,float/float,float,float,float", - "Visit": "hifi://domain-network-address/float,float,float/float,float,float,float", - "DomainId": "domainId", - "Network Address": "domain network address", - "Network Port": "domain network port", - "Owner": "account name of domain owner", - "People": number, - "Place": { place information as defined in [Places](./API-Places.md) } + "Domain name": "place name", + "Address": "domain-network-address/float,float,float/float,float,float,float", + "Visit": "hifi://domain-network-address/float,float,float/float,float,float,float", + "DomainId": "domainId", + "Network Address": "domain network address", + "Network Port": "domain network port", + "Owner": "account name of domain owner", + "People": number, + "Place": { + "placeId': string, + "id': string, + "name': "place name", + "visibility': string, // one of "open", "friends", "connections", "private" + "path": "/float,float,float/float,float,float,float", + "address": "domain-network-address/float,float,float/float,float,float,float", + "description': "place description", + "maturity': string, // one of "everyone", "teen", "mature", "adult", "unrated" + "tags': string[], + "thumbnail': stringURL, + "images': stringURLS[], + // The following fields can be updated in realtime by a script at the Place + "current_attendance': number, // reported attendance at place + "current_images': stringURLS[], + "current_info': string, + "current_last_update_time': "ISODateString" + }, }, ... ] diff --git a/docs/API-Places.md b/docs/API-Places.md index a665c958..37c4570a 100755 --- a/docs/API-Places.md +++ b/docs/API-Places.md @@ -89,6 +89,7 @@ This request return JSON formatted as: { "placeId": string, "name": string, + "visibility": string, // one of 'open', 'friends', 'connections', 'private' "path": string, "address": string, "description": string, @@ -226,6 +227,7 @@ The place fields that can be fetched: | placeId | all | none | string | | name | all | domainOwner, admin | string | | description | all | domainOwner, admin | string | +| visibility | all | domainOwner, admin | string | | domainId | all | none | string | | maturity | all | domainOwner, admin | string | | tags | all | domainOwner, admin | stringArray | diff --git a/src/Entities/DomainEntity.ts b/src/Entities/DomainEntity.ts index 2f8bead3..879b84e7 100755 --- a/src/Entities/DomainEntity.ts +++ b/src/Entities/DomainEntity.ts @@ -21,6 +21,7 @@ import { Entity } from '@Entities/Entity'; export class DomainEntity implements Entity { public id: string; // globally unique domain identifier public name: string; // domain name/label + public visibility: string; // visibility of this entry in general domain lists public publicKey: string; // DomainServers's public key in multi-line PEM format public apiKey: string; // Access key if a temp domain public sponsorAccountId: string; // The account that gave this domain an access key diff --git a/src/Entities/DomainFields.ts b/src/Entities/DomainFields.ts index 21cb6941..4d5458da 100644 --- a/src/Entities/DomainFields.ts +++ b/src/Entities/DomainFields.ts @@ -80,6 +80,20 @@ export const DomainFields: { [key: string]: FieldDefn } = { setter: noOverwriteSetter, getter: simpleGetter }, + 'visiblity': { + entity_field: 'visiblity', + request_field_name: 'visiblity', + get_permissions: [ Perm.ALL ], + set_permissions: [ Perm.DOMAIN, Perm.SPONSOR, Perm.ADMIN ], + validate: async (pField: FieldDefn, pEntity: Entity, pVal: any): Promise => { + if(typeof(pVal) === 'string' && Visibility.KnownVisibility(pVal)) { + return { valid: true }; + } + return { valid: false, reason: 'not accepted visibility value'}; + }, + setter: simpleSetter, + getter: simpleGetter + }, // An alternate way of setting domain name 'world_name': { entity_field: 'name', diff --git a/src/Entities/EntityFilters/AccountScopeFilter.ts b/src/Entities/EntityFilters/AccountScopeFilter.ts index b1b026ef..a7eb9269 100755 --- a/src/Entities/EntityFilters/AccountScopeFilter.ts +++ b/src/Entities/EntityFilters/AccountScopeFilter.ts @@ -18,7 +18,6 @@ import { CriteriaFilter } from '@Entities/EntityFilters/CriteriaFilter'; import { Accounts } from '@Entities/Accounts'; import { AccountEntity } from '@Entities/AccountEntity'; import { Logger } from '@Tools/Logging'; -import { ParseQueryString } from '@Tools/Misc'; // AccountScopeFilter filters a query stream to the accounts the requestor // can look at. That is, a person can normally see only the domains diff --git a/src/Entities/EntityFilters/PlaceFilterInfo.ts b/src/Entities/EntityFilters/PlaceFilterInfo.ts index 403d5670..14538e02 100644 --- a/src/Entities/EntityFilters/PlaceFilterInfo.ts +++ b/src/Entities/EntityFilters/PlaceFilterInfo.ts @@ -19,6 +19,7 @@ import { PlaceEntity } from '@Entities/PlaceEntity'; import { CriteriaFilter } from '@Entities/EntityFilters/CriteriaFilter'; import { Maturity } from '@Entities/Sets/Maturity'; +import { Visibility } from '@Entities/Sets/Visibility'; import { VKeyedCollection } from '@Tools/vTypes'; import { Logger } from '@Tools/Logging'; @@ -179,14 +180,17 @@ export class PlaceFilterInfo extends CriteriaFilter { this._doingQuery = true; const criteria:VKeyedCollection = {}; if (this._maturity) { - criteria.maturity = { '$in': this._maturity } + criteria['maturity'] = { '$in': this._maturity } }; if (this._tags) { - criteria.tags = { '$in': this._tags } + criteria['tags'] = { '$in': this._tags } }; if (this._search) { - criteria.name = { '$regex': this._search, '$options': 'i' } - } + criteria['name'] = { '$regex': this._search, '$options': 'i' } + }; + criteria['visibility'] = { "$or": [ { "visibility": { "$exists": false }}, + { "visibility": Visibility.OPEN }, + ] }; return criteria; }; diff --git a/src/Entities/PlaceEntity.ts b/src/Entities/PlaceEntity.ts index 962ceb85..9c126bae 100755 --- a/src/Entities/PlaceEntity.ts +++ b/src/Entities/PlaceEntity.ts @@ -22,6 +22,7 @@ export class PlaceEntity implements Entity { public id: string; // globally unique place identifier public name: string; // Human friendly name of the place public description: string; // Human friendly description of the place + public visibility: string; // visibility of this Place in general Place lists 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 diff --git a/src/Entities/PlaceFields.ts b/src/Entities/PlaceFields.ts index 8bc5c3d4..f814c9e3 100644 --- a/src/Entities/PlaceFields.ts +++ b/src/Entities/PlaceFields.ts @@ -22,6 +22,7 @@ import { Domains } from '@Entities/Domains'; import { Places } from '@Entities/Places'; import { AuthToken } from '@Entities/AuthToken'; import { Maturity } from '@Entities/Sets/Maturity'; +import { Visibility } from '@Entities/Sets/Visibility'; import { Perm } from '@Route-Tools/Perm'; import { checkAccessToEntity } from '@Route-Tools/Permissions'; @@ -33,7 +34,6 @@ import { simpleGetter, simpleSetter, noSetter, sArraySetter, dateStringGetter } import { IsNullOrEmpty, IsNotNullOrEmpty } from '@Tools/Misc'; import { Logger } from '@Tools/Logging'; -import { Tokens } from './Tokens'; // Naming and access for the fields in a PlaceEntity. // Indexed by request_field_name. @@ -77,16 +77,30 @@ export const placeFields: { [key: string]: FieldDefn } = { entity_field: 'description', request_field_name: 'description', get_permissions: [ Perm.ALL ], - set_permissions: [ Perm.DOMAIN, Perm.OWNER, Perm.ADMIN ], + set_permissions: [ Perm.DOMAINACCESS, Perm.ADMIN ], validate: isStringValidator, setter: simpleSetter, getter: simpleGetter }, + 'visiblity': { + entity_field: 'visiblity', + request_field_name: 'visiblity', + get_permissions: [ Perm.ALL ], + set_permissions: [ Perm.DOMAINACCESS, Perm.ADMIN ], + validate: async (pField: FieldDefn, pEntity: Entity, pVal: any): Promise => { + if(typeof(pVal) === 'string' && Visibility.KnownVisibility(pVal)) { + return { valid: true }; + } + return { valid: false, reason: 'not accepted visibility value'}; + }, + setter: simpleSetter, + getter: simpleGetter + }, 'domainId': { entity_field: 'domainId', request_field_name: 'domainId', get_permissions: [ Perm.ALL ], - set_permissions: [ Perm.OWNER, Perm.ADMIN ], + set_permissions: [ Perm.DOMAINACCESS, Perm.ADMIN ], validate: async (pField: FieldDefn, pEntity: Entity, pVal: any, pAuth: AuthToken): Promise => { // This is setting a place to a new domainId. Make sure the domain exists // and requestor has access to that domain. diff --git a/src/Entities/Sets/Restriction.ts b/src/Entities/Sets/Restriction.ts index 1caf9896..eef6e548 100644 --- a/src/Entities/Sets/Restriction.ts +++ b/src/Entities/Sets/Restriction.ts @@ -34,5 +34,3 @@ export class Restriction { }; }; - - diff --git a/src/Entities/Sets/Visibility.ts b/src/Entities/Sets/Visibility.ts new file mode 100644 index 00000000..1cc532cb --- /dev/null +++ b/src/Entities/Sets/Visibility.ts @@ -0,0 +1,34 @@ +// Copyright 2021 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict' + +export class Visibility { + public static OPEN: string = 'open'; + public static FRIENDS: string = 'friends'; + public static CONNECTIONS: string = 'connections'; + public static PRIVATE: string = 'private'; + + static VisibilityCategories = [ + Visibility.OPEN, + Visibility.FRIENDS, + Visibility.CONNECTIONS, + Visibility.PRIVATE + ]; + + static KnownVisibility(pVisibility: string): boolean { + return this.VisibilityCategories.includes(pVisibility); + }; +}; + diff --git a/src/route-tools/Util.ts b/src/route-tools/Util.ts index 74c0e25d..49fcf4c9 100755 --- a/src/route-tools/Util.ts +++ b/src/route-tools/Util.ts @@ -31,7 +31,9 @@ import { createPublicKey } from 'crypto'; import { VKeyedCollection, VKeyValue } from '@Tools/vTypes'; import { IsNotNullOrEmpty } from '@Tools/Misc'; import { Logger } from '@Tools/Logging'; + import { Maturity } from '@Entities/Sets/Maturity'; +import { Visibility } from '@Entities/Sets/Visibility'; // The public_key is sent as a binary (DER) form of a PKCS1 key. // To keep backward compatibility, we convert the PKCS1 key into a SPKI key in PEM format @@ -126,6 +128,7 @@ export async function buildDomainInfo(pDomain: DomainEntity): Promise { 'id': pDomain.id, 'domainId': pDomain.id, 'name': pDomain.name, + 'visibility': pDomain.visibility ?? Visibility.OPEN, 'sponsorAccountId': pDomain.sponsorAccountId, 'label': pDomain.name, 'network_address': pDomain.networkAddr, @@ -142,6 +145,7 @@ export async function buildDomainInfoV1(pDomain: DomainEntity): Promise { 'domainId': pDomain.id, 'id': pDomain.id, // legacy 'name': pDomain.name, + 'visibility': pDomain.visibility ?? Visibility.OPEN, 'world_name': pDomain.name, // legacy 'label': pDomain.name, // legacy 'public_key': pDomain.publicKey ? createSimplifiedPublicKey(pDomain.publicKey) : undefined, @@ -269,6 +273,7 @@ export async function buildPlaceInfoSmall(pPlace: PlaceEntity, pDomain?: DomainE 'placeId': pPlace.id, 'id': pPlace.id, 'name': pPlace.name, + 'visibility': pPlace.visibility ?? Visibility.OPEN, 'address': await Places.getAddressString(pPlace), 'path': pPlace.path, 'description': pPlace.description,