mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-30 21:45:01 +02:00
UBERF-7090: Add Office plugins (#5725)
Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1 @@
|
||||
tsdoc-metadata.json
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@hcengineering/server-love-resources",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"author": "Hardcore Engineering Inc.",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"prettier-plugin-svelte": "^3.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/workbench": "^0.6.9",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||
"@hcengineering/love": "^0.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public 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 https://www.eclipse.org/legal/epl-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.
|
||||
//
|
||||
|
||||
import contact, { Employee, Person, PersonAccount, formatName, getName } from '@hcengineering/contact'
|
||||
import core, {
|
||||
Account,
|
||||
Ref,
|
||||
Tx,
|
||||
TxCUD,
|
||||
TxCreateDoc,
|
||||
TxMixin,
|
||||
TxProcessor,
|
||||
TxUpdateDoc,
|
||||
UserStatus
|
||||
} from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import { createPushNotification, isAllowed } from '@hcengineering/server-notification-resources'
|
||||
import love, {
|
||||
Invite,
|
||||
JoinRequest,
|
||||
ParticipantInfo,
|
||||
RequestStatus,
|
||||
RoomAccess,
|
||||
RoomInfo,
|
||||
isOffice,
|
||||
loveId
|
||||
} from '@hcengineering/love'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxMixin<Person, Employee>
|
||||
if (actualTx._class !== core.class.TxMixin) return []
|
||||
if (actualTx.mixin !== contact.mixin.Employee) return []
|
||||
const val = actualTx.attributes.active
|
||||
if (val === undefined) return []
|
||||
if (val) {
|
||||
const freeRoom = (await control.findAll(love.class.Office, { person: null }))[0]
|
||||
if (freeRoom !== undefined) {
|
||||
return [
|
||||
control.txFactory.createTxUpdateDoc(freeRoom._class, freeRoom.space, freeRoom._id, {
|
||||
person: actualTx.objectId
|
||||
})
|
||||
]
|
||||
}
|
||||
} else {
|
||||
const room = (await control.findAll(love.class.Office, { person: actualTx.objectId }))[0]
|
||||
if (room !== undefined) {
|
||||
return [
|
||||
control.txFactory.createTxUpdateDoc(room._class, room.space, room._id, {
|
||||
person: null
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
async function createUserInfo (acc: Ref<Account>, control: TriggerControl): Promise<Tx[]> {
|
||||
const account = control.modelDb.findAllSync(contact.class.PersonAccount, { _id: acc as Ref<PersonAccount> })[0]
|
||||
if (account === undefined) return []
|
||||
const personId = account.person
|
||||
const person = (await control.findAll(contact.class.Person, { _id: personId }))[0]
|
||||
const room = (await control.findAll(love.class.Office, { person: personId }))[0]
|
||||
const tx = control.txFactory.createTxCreateDoc(love.class.ParticipantInfo, love.space.Rooms, {
|
||||
person: personId,
|
||||
name: person !== undefined ? getName(control.hierarchy, person) : account.email,
|
||||
room: room?._id ?? love.ids.Reception,
|
||||
x: 0,
|
||||
y: 0
|
||||
})
|
||||
const ptx = control.txFactory.createTxApplyIf(
|
||||
love.space.Rooms,
|
||||
personId,
|
||||
[],
|
||||
[
|
||||
{
|
||||
_class: love.class.ParticipantInfo,
|
||||
query: { person }
|
||||
}
|
||||
],
|
||||
[tx]
|
||||
)
|
||||
await control.apply([ptx], true)
|
||||
return []
|
||||
}
|
||||
|
||||
async function removeUserInfo (acc: Ref<Account>, control: TriggerControl): Promise<Tx[]> {
|
||||
const account = control.modelDb.findAllSync(contact.class.PersonAccount, { _id: acc as Ref<PersonAccount> })[0]
|
||||
if (account === undefined) return []
|
||||
const person = account.person
|
||||
const infos = await control.findAll(love.class.ParticipantInfo, { person })
|
||||
for (const info of infos) {
|
||||
await control.apply([control.txFactory.createTxRemoveDoc(info._class, info.space, info._id)], true)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnUserStatus (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<UserStatus>
|
||||
if (actualTx.objectClass !== core.class.UserStatus) return []
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const createTx = actualTx as TxCreateDoc<UserStatus>
|
||||
const status = TxProcessor.createDoc2Doc(createTx)
|
||||
return await createUserInfo(status.user, control)
|
||||
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = actualTx as TxUpdateDoc<UserStatus>
|
||||
const val = updateTx.operations.online
|
||||
if (val === undefined) return []
|
||||
const status = (await control.findAll(core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||
if (status !== undefined) {
|
||||
if (val) {
|
||||
return await createUserInfo(status.user, control)
|
||||
} else {
|
||||
return await removeUserInfo(status.user, control)
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
async function roomJoinHandler (info: ParticipantInfo, control: TriggerControl, roomInfos: RoomInfo[]): Promise<Tx[]> {
|
||||
const roomInfo = roomInfos.find((ri) => ri.room === info.room)
|
||||
if (roomInfo !== undefined) {
|
||||
roomInfo.persons.push(info.person)
|
||||
return [
|
||||
control.txFactory.createTxUpdateDoc(love.class.RoomInfo, love.space.Rooms, roomInfo._id, {
|
||||
persons: Array.from(new Set([...roomInfo.persons, info.person]))
|
||||
})
|
||||
]
|
||||
} else {
|
||||
const room = (await control.findAll(love.class.Room, { _id: info.room }))[0]
|
||||
if (room === undefined) return []
|
||||
return [
|
||||
control.txFactory.createTxCreateDoc(love.class.RoomInfo, love.space.Rooms, {
|
||||
persons: [info.person],
|
||||
room: info.room,
|
||||
isOffice: isOffice(room)
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function setDefaultRoomAccess (info: ParticipantInfo, roomInfos: RoomInfo[], control: TriggerControl): Tx[] {
|
||||
const res: Tx[] = []
|
||||
const oldRoomInfo = roomInfos.find((ri) => ri.persons.includes(info.person))
|
||||
if (oldRoomInfo !== undefined) {
|
||||
oldRoomInfo.persons = oldRoomInfo.persons.filter((p) => p !== info.person)
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(love.class.RoomInfo, love.space.Rooms, oldRoomInfo._id, {
|
||||
persons: oldRoomInfo.persons
|
||||
})
|
||||
)
|
||||
if (oldRoomInfo.persons.length === 0) {
|
||||
const resetAccessTx = control.txFactory.createTxUpdateDoc(
|
||||
oldRoomInfo.isOffice ? love.class.Office : love.class.Room,
|
||||
love.space.Rooms,
|
||||
oldRoomInfo.room,
|
||||
{
|
||||
access: oldRoomInfo.isOffice ? RoomAccess.Knock : RoomAccess.Open
|
||||
}
|
||||
)
|
||||
res.push(resetAccessTx)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export async function OnParticipantInfo (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const roomInfos = await control.queryFind(love.class.RoomInfo, {})
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<ParticipantInfo>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const info = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<ParticipantInfo>)
|
||||
return await roomJoinHandler(info, control, roomInfos)
|
||||
}
|
||||
if (actualTx._class === core.class.TxRemoveDoc) {
|
||||
const removedInfo = control.removedMap.get(actualTx.objectId) as ParticipantInfo
|
||||
if (removedInfo === undefined) return []
|
||||
return setDefaultRoomAccess(removedInfo, roomInfos, control)
|
||||
}
|
||||
if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
const newRoom = (actualTx as TxUpdateDoc<ParticipantInfo>).operations.room
|
||||
if (newRoom === undefined) return []
|
||||
const info = (await control.findAll(love.class.ParticipantInfo, { _id: actualTx.objectId }, { limit: 1 }))[0]
|
||||
if (info === undefined) return []
|
||||
const res: Tx[] = []
|
||||
res.push(...setDefaultRoomAccess(info, roomInfos, control))
|
||||
res.push(...(await roomJoinHandler(info, control, roomInfos)))
|
||||
return res
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnKnock (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<JoinRequest>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const request = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (request.status === RequestStatus.Pending) {
|
||||
const roomInfo = (await control.findAll(love.class.RoomInfo, { room: request.room }))[0]
|
||||
if (roomInfo !== undefined) {
|
||||
const res: Tx[] = []
|
||||
const from = (await control.findAll(contact.class.Person, { _id: request.person }))[0]
|
||||
if (from === undefined) return []
|
||||
for (const user of roomInfo.persons) {
|
||||
const userAcc = await control.modelDb.findOne(contact.class.PersonAccount, { person: user })
|
||||
if (userAcc === undefined) continue
|
||||
if (
|
||||
await isAllowed(
|
||||
control,
|
||||
userAcc._id,
|
||||
love.ids.KnockNotification,
|
||||
notification.providers.BrowserNotification
|
||||
)
|
||||
) {
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||
const title = await translate(love.string.KnockingLabel, {})
|
||||
const body = await translate(love.string.IsKnocking, { name: formatName(from.name) })
|
||||
await createPushNotification(control, userAcc._id, title, body, request._id, from, path)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnInvite (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<Invite>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const invite = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (invite.status === RequestStatus.Pending) {
|
||||
const target = (await control.findAll(contact.class.Person, { _id: invite.target }))[0]
|
||||
if (target === undefined) return []
|
||||
const userAcc = await control.modelDb.findOne(contact.class.PersonAccount, { person: target._id })
|
||||
if (userAcc === undefined) return []
|
||||
const from = (await control.findAll(contact.class.Person, { _id: invite.from }))[0]
|
||||
if (
|
||||
await isAllowed(control, userAcc._id, love.ids.InviteNotification, notification.providers.BrowserNotification)
|
||||
) {
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||
const title = await translate(love.string.InivitingLabel, {})
|
||||
const body =
|
||||
from !== undefined
|
||||
? await translate(love.string.InvitingYou, { name: formatName(from.name) })
|
||||
: await translate(love.string.InivitingLabel, {})
|
||||
await createPushNotification(control, userAcc._id, title, body, invite._id, from, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
OnEmployee,
|
||||
OnUserStatus,
|
||||
OnParticipantInfo,
|
||||
OnKnock,
|
||||
OnInvite
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@hcengineering/server-love",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"author": "Hardcore Engineering Inc.",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/node": "~20.11.16",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"prettier-plugin-svelte": "^3.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { TriggerFunc } from '@hcengineering/server-core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const serverLoveId = 'server-love' as Plugin
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export default plugin(serverLoveId, {
|
||||
trigger: {
|
||||
OnEmployee: '' as Resource<TriggerFunc>,
|
||||
OnUserStatus: '' as Resource<TriggerFunc>,
|
||||
OnParticipantInfo: '' as Resource<TriggerFunc>,
|
||||
OnInvite: '' as Resource<TriggerFunc>,
|
||||
OnKnock: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user