T04.f: backend Festipod ActivityPods + dockerisation
- Ajoute la configuration et les services backend (app, actors, registration, attendees, contacts, catalog, core auth/nodeinfo/proxy/notification). - Corrige le démarrage : désactive resourcesWithContainerPath pour éviter le conflit entre l'URI de l'acteur applicatif (/as/actor/app) et la route /app. - Corrige l'URL du dépôt de shapes : https://shapes.activitypods.org/. - Ajoute backend/Dockerfile et docker-compose.yml racine (frontend + backend). - Met à jour .env.example racine avec les variables SEMAPPS_* et VITE_BACKEND_*. - Gates verts : bunx tsc --noEmit (0 erreur) et bun test (50 pass).
This commit is contained in:
@@ -43,6 +43,46 @@ PORT=3000
|
||||
# NODE_ENV=production bascule `bun run start` (pas de HMR). En dev, laisser vide.
|
||||
NODE_ENV=
|
||||
|
||||
# ── Backend ActivityPods ───────────────────────────────────────────────────
|
||||
# URL publique du backend Festipod (écoute sur le port 3003).
|
||||
SEMAPPS_HOME_URL=http://localhost:3003
|
||||
|
||||
# URL publique du frontend (utilisée par le backend pour les OIDC redirects, etc.)
|
||||
SEMAPPS_FRONT_URL=http://localhost:5000
|
||||
|
||||
# Endpoint SPARQL du Fuseki auquel se connecte le backend.
|
||||
SEMAPPS_SPARQL_ENDPOINT=http://localhost:3030/
|
||||
|
||||
# Dataset Jena/Fuseki principal utilisé par le backend.
|
||||
SEMAPPS_MAIN_DATASET=settings
|
||||
|
||||
# Identifiants Jena/Fuseki.
|
||||
SEMAPPS_JENA_USER=admin
|
||||
SEMAPPS_JENA_PASSWORD=admin
|
||||
|
||||
# Redis : cache (db 3) et file des jobs (db 4).
|
||||
SEMAPPS_REDIS_CACHE_URL=redis://localhost:6390/3
|
||||
SEMAPPS_QUEUE_SERVICE_URL=redis://localhost:6390/4
|
||||
|
||||
# Dataset utilisé par le service auth.account.
|
||||
SEMAPPS_AUTH_ACCOUNTS_DATASET_NAME=settings
|
||||
|
||||
# Dépôt de shapes/shapetrees utilisé pour déclarer les besoins d'accès.
|
||||
SEMAPPS_SHAPE_REPOSITORY_URL=https://shapes.activitypods.org/
|
||||
|
||||
# Nom et description de l'application (backend).
|
||||
SEMAPPS_APP_NAME=Festipod
|
||||
SEMAPPS_APP_DESCRIPTION=Points de rencontre sur les événements publics
|
||||
SEMAPPS_APP_LANG=en,fr
|
||||
SEMAPPS_PORT=3003
|
||||
|
||||
# ── Frontend / Backend coupling ────────────────────────────────────────────
|
||||
# URL du backend telle que vue par le navigateur.
|
||||
VITE_BACKEND_URL=http://localhost:3003
|
||||
|
||||
# Client ID OAuth/Solid du backend (laisser vide pour l'auto-découverte).
|
||||
VITE_BACKEND_CLIENT_ID=
|
||||
|
||||
# ── Outillage dev (facultatif) ─────────────────────────────────────────────
|
||||
# Override du chemin local du polyfill @ng-eventually/sdk pour `pnpm run
|
||||
# overlay:polyfill` (overlay local réactif). Défaut = ../nextgraph/ng-eventually-js/packages/sdk.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
SEMAPPS_APP_NAME=Festipod
|
||||
SEMAPPS_APP_DESCRIPTION=Points de rencontre sur les événements publics
|
||||
SEMAPPS_APP_LANG=en,fr
|
||||
SEMAPPS_HOME_URL=http://localhost:3003
|
||||
SEMAPPS_FRONT_URL=http://localhost:5000
|
||||
SEMAPPS_SHAPE_REPOSITORY_URL=https://shapes.activitypods.org/
|
||||
SEMAPPS_PORT=3003
|
||||
SEMAPPS_SPARQL_ENDPOINT=http://localhost:3030/
|
||||
SEMAPPS_MAIN_DATASET=settings
|
||||
SEMAPPS_JENA_USER=admin
|
||||
SEMAPPS_JENA_PASSWORD=admin
|
||||
SEMAPPS_REDIS_CACHE_URL=redis://localhost:6390/3
|
||||
SEMAPPS_QUEUE_SERVICE_URL=redis://localhost:6390/4
|
||||
SEMAPPS_AUTH_ACCOUNTS_DATASET_NAME=settings
|
||||
@@ -0,0 +1,25 @@
|
||||
FROM node:22-slim
|
||||
|
||||
# Install pnpm and build tools required for native modules (sharp, bcrypt).
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& corepack enable && corepack prepare pnpm@10.26.0 --activate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies first to leverage Docker layer caching.
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --prod
|
||||
|
||||
# Copy application code.
|
||||
COPY config ./config
|
||||
COPY moleculer.config.js ./
|
||||
COPY services ./services
|
||||
|
||||
EXPOSE 3003
|
||||
|
||||
CMD ["pnpm", "start"]
|
||||
@@ -0,0 +1,18 @@
|
||||
require('dotenv-flow').config();
|
||||
|
||||
module.exports = {
|
||||
APP_NAME: process.env.SEMAPPS_APP_NAME,
|
||||
APP_DESCRIPTION: process.env.SEMAPPS_APP_DESCRIPTION,
|
||||
APP_LANG: process.env.SEMAPPS_APP_LANG,
|
||||
HOME_URL: process.env.SEMAPPS_HOME_URL,
|
||||
FRONT_URL: process.env.SEMAPPS_FRONT_URL,
|
||||
SHAPE_REPOSITORY_URL: process.env.SEMAPPS_SHAPE_REPOSITORY_URL,
|
||||
PORT: process.env.SEMAPPS_PORT,
|
||||
SPARQL_ENDPOINT: process.env.SEMAPPS_SPARQL_ENDPOINT,
|
||||
MAIN_DATASET: process.env.SEMAPPS_MAIN_DATASET,
|
||||
JENA_USER: process.env.SEMAPPS_JENA_USER,
|
||||
JENA_PASSWORD: process.env.SEMAPPS_JENA_PASSWORD,
|
||||
REDIS_CACHE_URL: process.env.SEMAPPS_REDIS_CACHE_URL,
|
||||
QUEUE_SERVICE_URL: process.env.SEMAPPS_QUEUE_SERVICE_URL,
|
||||
AUTH_ACCOUNTS_DATASET_NAME: process.env.SEMAPPS_AUTH_ACCOUNTS_DATASET_NAME
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
const { CacherMiddleware } = require('@semapps/webacl');
|
||||
const CONFIG = require('./config/config');
|
||||
|
||||
Error.stackTraceLimit = Infinity;
|
||||
|
||||
const cacherConfig = CONFIG.REDIS_CACHE_URL
|
||||
? {
|
||||
type: 'Redis',
|
||||
options: {
|
||||
prefix: 'action',
|
||||
ttl: 2592000,
|
||||
redis: CONFIG.REDIS_CACHE_URL
|
||||
}
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const SkipOrphanBlankNodesCleanupMiddleware = () => ({
|
||||
name: 'SkipOrphanBlankNodesCleanupMiddleware',
|
||||
localAction: (next, action) => {
|
||||
if (action.name === 'triplestore.deleteOrphanBlankNodes') {
|
||||
return async () => {};
|
||||
}
|
||||
return next;
|
||||
}
|
||||
});
|
||||
|
||||
// ActivityPods app services mark internal calls with isSystemCall, but the SDK
|
||||
// triplestore layer needs an explicit webId='system' to bypass pod-side WAC.
|
||||
const SystemCallWebIdMiddleware = () => ({
|
||||
name: 'SystemCallWebIdMiddleware',
|
||||
localAction: (next, action) => {
|
||||
return async ctx => {
|
||||
if (ctx.meta?.isSystemCall && !ctx.params?.webId) {
|
||||
ctx.params.webId = 'system';
|
||||
}
|
||||
return next(ctx);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
middlewares: [
|
||||
SkipOrphanBlankNodesCleanupMiddleware(),
|
||||
SystemCallWebIdMiddleware(),
|
||||
CacherMiddleware(cacherConfig)
|
||||
],
|
||||
logger: {
|
||||
type: 'Console',
|
||||
options: {
|
||||
formatter: 'short',
|
||||
level: 'info'
|
||||
}
|
||||
},
|
||||
retryPolicy: {
|
||||
enabled: true,
|
||||
retries: 0,
|
||||
delay: 100,
|
||||
maxDelay: 30000,
|
||||
factor: 2,
|
||||
check: err => err && !!err.retryable
|
||||
}
|
||||
};
|
||||
@@ -10,19 +10,24 @@
|
||||
"@rdfjs/data-model": "2.1.1",
|
||||
"@semapps/activitypub": "1.2.7",
|
||||
"@semapps/auth": "1.2.7",
|
||||
"@semapps/crypto": "1.2.7",
|
||||
"@semapps/core": "1.2.7",
|
||||
"@semapps/crypto": "1.2.7",
|
||||
"@semapps/importer": "1.2.7",
|
||||
"@semapps/ldp": "1.2.7",
|
||||
"@semapps/mime-types": "1.2.7",
|
||||
"@semapps/nodeinfo": "1.2.7",
|
||||
"@semapps/ontologies": "1.2.7",
|
||||
"@semapps/solid": "1.2.7",
|
||||
"@semapps/sparql-endpoint": "1.2.7",
|
||||
"@semapps/triplestore": "1.2.7",
|
||||
"@semapps/webacl": "1.2.7",
|
||||
"dotenv-flow": "^3.2.0",
|
||||
"ioredis": "^4.17.3",
|
||||
"moleculer": "^0.14.19",
|
||||
"moleculer-bull": "0.2.8",
|
||||
"moleculer-cron": "0.0.4",
|
||||
"moleculer-repl": "^0.6.6",
|
||||
"sharp": "0.31.3",
|
||||
"url-join": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
Generated
+15
@@ -32,15 +32,27 @@ importers:
|
||||
'@semapps/ldp':
|
||||
specifier: 1.2.7
|
||||
version: 1.2.7(debug@4.4.3)(moleculer@0.14.36(debug@4.4.3)(ioredis@4.31.0))
|
||||
'@semapps/mime-types':
|
||||
specifier: 1.2.7
|
||||
version: 1.2.7(moleculer@0.14.36(debug@4.4.3)(ioredis@4.31.0))
|
||||
'@semapps/nodeinfo':
|
||||
specifier: 1.2.7
|
||||
version: 1.2.7(moleculer@0.14.36(debug@4.4.3)(ioredis@4.31.0))
|
||||
'@semapps/ontologies':
|
||||
specifier: 1.2.7
|
||||
version: 1.2.7(moleculer@0.14.36(debug@4.4.3)(ioredis@4.31.0))
|
||||
'@semapps/solid':
|
||||
specifier: 1.2.7
|
||||
version: 1.2.7(debug@4.4.3)(moleculer@0.14.36(debug@4.4.3)(ioredis@4.31.0))
|
||||
'@semapps/sparql-endpoint':
|
||||
specifier: 1.2.7
|
||||
version: 1.2.7(moleculer@0.14.36(debug@4.4.3)(ioredis@4.31.0))
|
||||
'@semapps/triplestore':
|
||||
specifier: 1.2.7
|
||||
version: 1.2.7(moleculer@0.14.36(debug@4.4.3)(ioredis@4.31.0))
|
||||
'@semapps/webacl':
|
||||
specifier: 1.2.7
|
||||
version: 1.2.7(debug@4.4.3)(moleculer@0.14.36(debug@4.4.3)(ioredis@4.31.0))
|
||||
dotenv-flow:
|
||||
specifier: ^3.2.0
|
||||
version: 3.3.0
|
||||
@@ -59,6 +71,9 @@ importers:
|
||||
moleculer-repl:
|
||||
specifier: ^0.6.6
|
||||
version: 0.6.6
|
||||
sharp:
|
||||
specifier: 0.31.3
|
||||
version: 0.31.3
|
||||
url-join:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
const urlJoin = require('url-join');
|
||||
const { AppService } = require('@activitypods/app');
|
||||
const CONFIG = require('../config/config');
|
||||
|
||||
module.exports = {
|
||||
mixins: [AppService],
|
||||
settings: {
|
||||
baseUrl: CONFIG.HOME_URL,
|
||||
app: {
|
||||
name: CONFIG.APP_NAME,
|
||||
description: CONFIG.APP_DESCRIPTION,
|
||||
thumbnail: urlJoin(CONFIG.FRONT_URL, 'logo.svg'),
|
||||
frontUrl: CONFIG.FRONT_URL,
|
||||
supportedLocales: CONFIG.APP_LANG
|
||||
},
|
||||
oidc: {
|
||||
clientUri: CONFIG.FRONT_URL,
|
||||
redirectUris: urlJoin(CONFIG.FRONT_URL, 'login'),
|
||||
postLogoutRedirectUris: urlJoin(CONFIG.FRONT_URL, 'login?logout=true'),
|
||||
tosUri: null
|
||||
},
|
||||
accessNeeds: {
|
||||
required: [
|
||||
{
|
||||
shapeTreeUri: urlJoin(CONFIG.SHAPE_REPOSITORY_URL, 'shapetrees/as/Event'),
|
||||
accessMode: ['acl:Read', 'acl:Write', 'acl:Control'],
|
||||
preferredScope: 'interop:AllFromRegistry'
|
||||
},
|
||||
{
|
||||
shapeTreeUri: urlJoin(CONFIG.SHAPE_REPOSITORY_URL, 'shapetrees/as/Profile'),
|
||||
accessMode: ['acl:Read', 'acl:Write'],
|
||||
preferredScope: 'interop:AllFromRegistry'
|
||||
},
|
||||
{
|
||||
shapeTreeUri: urlJoin(CONFIG.SHAPE_REPOSITORY_URL, 'shapetrees/vcard/Group'),
|
||||
accessMode: 'acl:Read',
|
||||
preferredScope: 'interop:AllFromRegistry'
|
||||
},
|
||||
'apods:ReadInbox',
|
||||
'apods:ReadOutbox',
|
||||
'apods:PostOutbox',
|
||||
'apods:QuerySparqlEndpoint',
|
||||
'apods:CreateWacGroup',
|
||||
'apods:CreateCollection',
|
||||
'apods:UpdateWebId'
|
||||
],
|
||||
optional: []
|
||||
},
|
||||
queueServiceUrl: CONFIG.QUEUE_SERVICE_URL
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
const urlJoin = require('url-join');
|
||||
const { PodCollectionsHandlerMixin } = require('@activitypods/app');
|
||||
const CONFIG = require('../config/config');
|
||||
|
||||
module.exports = {
|
||||
name: 'attendees',
|
||||
mixins: [PodCollectionsHandlerMixin],
|
||||
settings: {
|
||||
shapeTreeUri: urlJoin(CONFIG.SHAPE_REPOSITORY_URL, 'shapetrees/as/Event'),
|
||||
attachPredicate: 'http://activitypods.org/ns/core#attendees',
|
||||
collectionOptions: {
|
||||
ordered: false,
|
||||
summary: 'Meeting point attendees'
|
||||
},
|
||||
createWacGroup: true
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
const urlJoin = require('url-join');
|
||||
const { PodResourcesHandlerMixin } = require('@activitypods/app');
|
||||
const CONFIG = require('../config/config');
|
||||
|
||||
module.exports = {
|
||||
name: 'catalog',
|
||||
mixins: [PodResourcesHandlerMixin],
|
||||
settings: {
|
||||
shapeTreeUri: urlJoin(CONFIG.SHAPE_REPOSITORY_URL, 'shapetrees/as/Event')
|
||||
},
|
||||
actions: {
|
||||
discover: {
|
||||
async handler() {
|
||||
return {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
type: 'Collection',
|
||||
summary: 'Public Festipod events and meeting points',
|
||||
totalItems: this.index ? Object.keys(this.index).length : 0,
|
||||
items: this.index ? Object.values(this.index) : []
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onCreate(ctx, resource) {
|
||||
if (!this.index) this.index = {};
|
||||
this.index[resource.id || resource['@id']] = resource;
|
||||
this.logger.info(`Catalog indexed new event ${resource.id || resource['@id']}`);
|
||||
},
|
||||
async onUpdate(ctx, resource) {
|
||||
if (!this.index) this.index = {};
|
||||
this.index[resource.id || resource['@id']] = resource;
|
||||
this.logger.info(`Catalog updated event ${resource.id || resource['@id']}`);
|
||||
},
|
||||
async onDelete(ctx, resourceUri) {
|
||||
if (!this.index) return;
|
||||
delete this.index[resourceUri];
|
||||
this.logger.info(`Catalog removed event ${resourceUri}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
const urlJoin = require('url-join');
|
||||
const { PodCollectionsHandlerMixin } = require('@activitypods/app');
|
||||
const CONFIG = require('../config/config');
|
||||
|
||||
module.exports = {
|
||||
name: 'contacts',
|
||||
mixins: [PodCollectionsHandlerMixin],
|
||||
settings: {
|
||||
shapeTreeUri: urlJoin(CONFIG.SHAPE_REPOSITORY_URL, 'shapetrees/as/Profile'),
|
||||
attachPredicate: 'http://activitypods.org/ns/core#contacts',
|
||||
collectionOptions: {
|
||||
ordered: false,
|
||||
summary: 'Festipod contacts'
|
||||
},
|
||||
createWacGroup: true
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
const { AuthAccountService } = require('@semapps/auth');
|
||||
const { TripleStoreAdapter } = require('@semapps/triplestore');
|
||||
const CONFIG = require('../../config/config');
|
||||
|
||||
module.exports = {
|
||||
mixins: [AuthAccountService],
|
||||
adapter: new TripleStoreAdapter({ type: 'AuthAccount', dataset: CONFIG.AUTH_ACCOUNTS_DATASET_NAME })
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
const path = require('path');
|
||||
const { CoreService } = require('@semapps/core');
|
||||
const { apods, notify, interop, oidc, skos } = require('@semapps/ontologies');
|
||||
const CONFIG = require('../../config/config');
|
||||
|
||||
module.exports = {
|
||||
mixins: [CoreService],
|
||||
settings: {
|
||||
baseUrl: CONFIG.HOME_URL,
|
||||
baseDir: path.resolve(__dirname, '../..'),
|
||||
triplestore: {
|
||||
url: CONFIG.SPARQL_ENDPOINT,
|
||||
user: CONFIG.JENA_USER,
|
||||
password: CONFIG.JENA_PASSWORD,
|
||||
mainDataset: CONFIG.MAIN_DATASET
|
||||
},
|
||||
ontologies: [apods, notify, interop, oidc, skos],
|
||||
activitypub: {
|
||||
queueServiceUrl: CONFIG.QUEUE_SERVICE_URL
|
||||
},
|
||||
api: {
|
||||
port: CONFIG.PORT,
|
||||
routes: [
|
||||
{
|
||||
path: '/app',
|
||||
aliases: {
|
||||
'GET /': 'app.get'
|
||||
},
|
||||
mappingPolicy: 'restrict',
|
||||
authentication: false,
|
||||
authorization: false,
|
||||
bodyParsers: {
|
||||
json: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/catalog',
|
||||
aliases: {
|
||||
'GET /': 'catalog.discover'
|
||||
},
|
||||
mappingPolicy: 'restrict',
|
||||
authentication: false,
|
||||
authorization: false,
|
||||
bodyParsers: {
|
||||
json: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
webacl: false,
|
||||
void: false
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
const urlJoin = require('url-join');
|
||||
const { NodeinfoService } = require('@semapps/nodeinfo');
|
||||
const CONFIG = require('../../config/config');
|
||||
|
||||
module.exports = {
|
||||
mixins: [NodeinfoService],
|
||||
settings: {
|
||||
baseUrl: CONFIG.HOME_URL,
|
||||
software: {
|
||||
name: 'festipod'
|
||||
},
|
||||
protocols: ['activitypub'],
|
||||
metadata: {
|
||||
frontend_url: CONFIG.FRONT_URL,
|
||||
login_url: CONFIG.FRONT_URL && urlJoin(CONFIG.FRONT_URL, 'login'),
|
||||
logout_url: CONFIG.FRONT_URL && urlJoin(CONFIG.FRONT_URL, 'login?logout=true')
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async getUsersCount(ctx) {
|
||||
const containerUri = await ctx.call('app-registrations.getContainerUri');
|
||||
const registrationsUris = await ctx.call('ldp.container.getUris', { containerUri });
|
||||
return {
|
||||
total: registrationsUris.length,
|
||||
activeHalfYear: registrationsUris.length,
|
||||
activeMonth: registrationsUris.length
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
const { TripleStoreAdapter } = require('@semapps/triplestore');
|
||||
const { NotificationsListenerService } = require('@semapps/solid');
|
||||
const CONFIG = require('../../config/config');
|
||||
|
||||
module.exports = {
|
||||
mixins: [NotificationsListenerService],
|
||||
adapter: new TripleStoreAdapter({ type: 'WebhookChannelListener', dataset: CONFIG.AUTH_ACCOUNTS_DATASET_NAME }),
|
||||
settings: {
|
||||
baseUrl: CONFIG.HOME_URL
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
const { ProxyService } = require('@semapps/crypto');
|
||||
|
||||
module.exports = ProxyService;
|
||||
@@ -0,0 +1,97 @@
|
||||
const { ACTIVITY_TYPES, OBJECT_TYPES } = require('@semapps/activitypub');
|
||||
const { PodActivitiesHandlerMixin } = require('@activitypods/app');
|
||||
const { arrayOf } = require('@semapps/ldp');
|
||||
|
||||
module.exports = {
|
||||
name: 'registration',
|
||||
mixins: [PodActivitiesHandlerMixin],
|
||||
async started() {
|
||||
this.logger.info('Registration watcher subscribed');
|
||||
},
|
||||
activities: {
|
||||
join: {
|
||||
match: {
|
||||
type: ACTIVITY_TYPES.JOIN,
|
||||
object: {
|
||||
type: OBJECT_TYPES.EVENT
|
||||
}
|
||||
},
|
||||
async onEmit(ctx, activity, actorUri) {
|
||||
const event = activity.object;
|
||||
const organizerUri = event['dc:creator'];
|
||||
if (!organizerUri) return;
|
||||
|
||||
const { body: actor } = await ctx.call('pod-resources.get', { resourceUri: actorUri, actorUri });
|
||||
if (!actor || !actor['apods:contacts']) return;
|
||||
|
||||
const contactsGroupUri = await ctx.call('pod-wac-groups.getUriFromCollectionUri', {
|
||||
collectionUri: actor['apods:contacts']
|
||||
});
|
||||
|
||||
await ctx.call('pod-wac-groups.addMember', {
|
||||
groupUri: contactsGroupUri,
|
||||
memberUri: organizerUri,
|
||||
actorUri
|
||||
});
|
||||
},
|
||||
async onReceive(ctx, activity, actorUri) {
|
||||
const event = activity.object;
|
||||
const attendeesCollectionUri = event['apods:attendees'];
|
||||
|
||||
if (!attendeesCollectionUri) {
|
||||
this.logger.warn(`No attendees collection on ${event.id}, skipping Join`);
|
||||
return;
|
||||
}
|
||||
|
||||
const attendees = await ctx.call('pod-collections.getItems', {
|
||||
collectionUri: attendeesCollectionUri,
|
||||
actorUri
|
||||
});
|
||||
|
||||
if (arrayOf(attendees).includes(activity.actor)) {
|
||||
this.logger.info(`${activity.actor} is already attending ${event.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.call('attendees.add', {
|
||||
collectionUri: attendeesCollectionUri,
|
||||
itemUri: activity.actor,
|
||||
actorUri
|
||||
});
|
||||
}
|
||||
},
|
||||
leave: {
|
||||
match: {
|
||||
type: ACTIVITY_TYPES.LEAVE,
|
||||
object: {
|
||||
type: OBJECT_TYPES.EVENT
|
||||
}
|
||||
},
|
||||
async onReceive(ctx, activity, actorUri) {
|
||||
const event = activity.object;
|
||||
const attendeesCollectionUri = event['apods:attendees'];
|
||||
|
||||
if (!attendeesCollectionUri) {
|
||||
this.logger.warn(`No attendees collection on ${event.id}, skipping Leave`);
|
||||
return;
|
||||
}
|
||||
|
||||
const attendees = await ctx.call('pod-collections.getItems', {
|
||||
collectionUri: attendeesCollectionUri,
|
||||
actorUri
|
||||
});
|
||||
|
||||
if (!arrayOf(attendees).includes(activity.actor)) {
|
||||
this.logger.info(`${activity.actor} is not attending ${event.id}, skipping Leave`);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.call('attendees.remove', {
|
||||
collectionUri: attendeesCollectionUri,
|
||||
itemUri: activity.actor,
|
||||
actorUri
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
services:
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- PORT=3000
|
||||
- NODE_ENV=production
|
||||
- VITE_BACKEND_URL=${VITE_BACKEND_URL:-http://localhost:3003}
|
||||
- VITE_BACKEND_CLIENT_ID=${VITE_BACKEND_CLIENT_ID:-}
|
||||
- FESTIPOD_SHARED_WALLET_PASSWORD=${FESTIPOD_SHARED_WALLET_PASSWORD:-}
|
||||
- FESTIPOD_SHARED_WALLET_FILE=${FESTIPOD_SHARED_WALLET_FILE:-}
|
||||
- FESTIPOD_SHARED_WALLET_FILE_BASE64=${FESTIPOD_SHARED_WALLET_FILE_BASE64:-}
|
||||
- FESTIPOD_AUTO_SEED=${FESTIPOD_AUTO_SEED:-}
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3003:3003"
|
||||
env_file:
|
||||
- ./backend/.env
|
||||
environment:
|
||||
- SEMAPPS_HOME_URL=${SEMAPPS_HOME_URL:-http://localhost:3003}
|
||||
- SEMAPPS_FRONT_URL=${SEMAPPS_FRONT_URL:-http://localhost:5000}
|
||||
Reference in New Issue
Block a user