mirror of
https://github.com/standardnotes/server
synced 2026-07-14 00:01:54 -04:00
wip: add group invites model
This commit is contained in:
@@ -7,6 +7,7 @@ import '../src/Controller/ItemsController'
|
||||
import '../src/Controller/LinksController'
|
||||
import '../src/Controller/GroupsController'
|
||||
import '../src/Controller/GroupUsersController'
|
||||
import '../src/Controller/GroupInvitesController'
|
||||
import '../src/Controller/ContactsController'
|
||||
|
||||
import helmet from 'helmet'
|
||||
|
||||
@@ -25,12 +25,6 @@ export class CreateGroupUser1684173023883 implements MigrationInterface {
|
||||
length: '36',
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'inviter_uuid',
|
||||
type: 'varchar',
|
||||
length: '36',
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'permissions',
|
||||
type: 'varchar',
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
|
||||
|
||||
export class CreateGroupInvites1684528646896 implements MigrationInterface {
|
||||
name = 'createGroupInvites1684528646896'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const table = new Table({
|
||||
name: 'group_invites',
|
||||
columns: [
|
||||
new TableColumn({
|
||||
name: 'uuid',
|
||||
type: 'varchar',
|
||||
length: '36',
|
||||
isPrimary: true,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'group_uuid',
|
||||
type: 'varchar',
|
||||
length: '36',
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'user_uuid',
|
||||
type: 'varchar',
|
||||
length: '36',
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'inviter_uuid',
|
||||
type: 'varchar',
|
||||
length: '255',
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'invite_type',
|
||||
type: 'varchar',
|
||||
length: '255',
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'inviter_public_key',
|
||||
type: 'varchar',
|
||||
length: '255',
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'encrypted_group_key',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'created_at_timestamp',
|
||||
type: 'bigint',
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'updated_at_timestamp',
|
||||
type: 'bigint',
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
await queryRunner.createTable(table)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable('group_invites')
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,9 @@ import {
|
||||
SQSNewRelicEventMessageHandler,
|
||||
} from '@standardnotes/domain-events-infra'
|
||||
import { UserCredentialsChangedEventHandler } from '../Domain/Handler/UserCredentialsChangedEventHandler'
|
||||
import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface'
|
||||
import { GroupInviteService } from '../Domain/GroupInvite/Service/GroupInviteService'
|
||||
import { GroupInviteFactory } from '../Domain/GroupInvite/Factory/GroupInviteFactory'
|
||||
|
||||
export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
|
||||
@@ -182,6 +185,17 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
context.container.get(TYPES.Timer),
|
||||
)
|
||||
})
|
||||
container
|
||||
.bind<GroupInviteServiceInterface>(TYPES.GroupInviteService)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new GroupInviteService(
|
||||
context.container.get(TYPES.GroupRepository),
|
||||
context.container.get(TYPES.GroupInviteRepository),
|
||||
context.container.get(TYPES.GroupInviteFactory),
|
||||
context.container.get(TYPES.Timer),
|
||||
)
|
||||
})
|
||||
|
||||
container
|
||||
.bind<SyncResponseFactory20161215>(TYPES.SyncResponseFactory20161215)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
@@ -219,6 +233,9 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
container.bind<GroupUserFactory>(TYPES.GroupUserFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GroupUserFactory(context.container.get(TYPES.Timer))
|
||||
})
|
||||
container.bind<GroupInviteFactory>(TYPES.GroupInviteFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GroupInviteFactory(context.container.get(TYPES.Timer))
|
||||
})
|
||||
|
||||
container
|
||||
.bind<UserCredentialsChangedEventHandler>(TYPES.UserCredentialsChangedEventHandler)
|
||||
|
||||
@@ -13,6 +13,7 @@ const TYPES = {
|
||||
GroupRepository: Symbol.for('GroupRepository'),
|
||||
GroupUserRepository: Symbol.for('GroupUserRepository'),
|
||||
ContactRepository: Symbol.for('ContactRepository'),
|
||||
GroupInviteRepository: Symbol.for('GroupInviteRepository'),
|
||||
|
||||
// ORM
|
||||
ORMItemRepository: Symbol.for('ORMItemRepository'),
|
||||
@@ -20,6 +21,7 @@ const TYPES = {
|
||||
ORMGroupRepository: Symbol.for('ORMGroupRepository'),
|
||||
ORMGroupUserRepository: Symbol.for('ORMGroupUserRepository'),
|
||||
ORMContactRepository: Symbol.for('ORMContactRepository'),
|
||||
ORMGroupInviteRepository: Symbol.for('ORMGroupInviteRepository'),
|
||||
|
||||
// Middleware
|
||||
AuthMiddleware: Symbol.for('AuthMiddleware'),
|
||||
@@ -31,13 +33,21 @@ const TYPES = {
|
||||
GroupProjector: Symbol.for('GroupProjector'),
|
||||
GroupUserProjector: Symbol.for('GroupUserProjector'),
|
||||
ContactProjector: Symbol.for('ContactProjector'),
|
||||
GroupInviteProjector: Symbol.for('GroupInviteProjector'),
|
||||
|
||||
// factories
|
||||
// Services
|
||||
GroupService: Symbol.for('GroupService'),
|
||||
GroupUserService: Symbol.for('GroupUserService'),
|
||||
ContactService: Symbol.for('ContactService'),
|
||||
GroupInviteService: Symbol.for('GroupInviteService'),
|
||||
|
||||
// Factories
|
||||
ItemFactory: Symbol.for('ItemFactory'),
|
||||
ItemLinkFactory: Symbol.for('ItemLinkFactory'),
|
||||
GroupFactory: Symbol.for('GroupFactory'),
|
||||
GroupUserFactory: Symbol.for('GroupUserFactory'),
|
||||
ContactFactory: Symbol.for('ContactFactory'),
|
||||
GroupInviteFactory: Symbol.for('GroupInviteFactory'),
|
||||
|
||||
// env vars
|
||||
REDIS_URL: Symbol.for('REDIS_URL'),
|
||||
@@ -74,11 +84,6 @@ const TYPES = {
|
||||
EmailBackupRequestedEventHandler: Symbol.for('EmailBackupRequestedEventHandler'),
|
||||
ItemRevisionCreationRequestedEventHandler: Symbol.for('ItemRevisionCreationRequestedEventHandler'),
|
||||
|
||||
// Services
|
||||
GroupService: Symbol.for('GroupService'),
|
||||
GroupUserService: Symbol.for('GroupUserService'),
|
||||
ContactService: Symbol.for('ContactService'),
|
||||
|
||||
ContentDecoder: Symbol.for('ContentDecoder'),
|
||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { BaseHttpController, controller, httpPost, results, httpDelete, httpGet } from 'inversify-express-utils'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { inject } from 'inversify'
|
||||
import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface'
|
||||
import { GroupInviteProjector } from '../Projection/GroupInviteProjector'
|
||||
|
||||
@controller('/groups/invites')
|
||||
export class GroupInvitesController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.GroupInviteService) private groupInviteService: GroupInviteServiceInterface,
|
||||
@inject(TYPES.GroupInviteProjector) private groupInviteProjector: GroupInviteProjector,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpPost('/', TYPES.AuthMiddleware)
|
||||
public async addUserToGroup(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.createGroupInvite({
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
groupUuid: request.params.groupUuid,
|
||||
userUuid: request.body.invitee_uuid,
|
||||
inviterUuid: response.locals.user.uuid,
|
||||
permissions: request.body.permissions,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(500, 'Could not add user to group')
|
||||
}
|
||||
|
||||
return this.json({ groupInvite: await this.groupInviteProjector.projectFull(result) })
|
||||
}
|
||||
|
||||
@httpGet('/', TYPES.AuthMiddleware)
|
||||
public async getGroupInvites(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.getGroupInvites({
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
groupUuid: request.params.groupUuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(500, 'Could not get group users')
|
||||
}
|
||||
|
||||
const { users } = result
|
||||
|
||||
const projected = users.map((groupInvite) => this.groupInviteProjector.projectFull(groupInvite))
|
||||
|
||||
return this.json({ users: projected })
|
||||
}
|
||||
|
||||
@httpDelete('/:userUuid', TYPES.AuthMiddleware)
|
||||
public async deleteGroupInvite(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.deleteGroupInvite({
|
||||
groupUuid: request.params.groupUuid,
|
||||
userUuid: request.params.userUuid,
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(500, 'Could not delete user')
|
||||
}
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
private errorResponse(status: number, message?: string, tag?: string) {
|
||||
return this.json(
|
||||
{
|
||||
error: { message, tag },
|
||||
},
|
||||
status,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ export class GroupUsersController extends BaseHttpController {
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
groupUuid: request.params.groupUuid,
|
||||
userUuid: request.body.invitee_uuid,
|
||||
inviterUuid: response.locals.user.uuid,
|
||||
permissions: request.body.permissions,
|
||||
})
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ export class GroupsController extends BaseHttpController {
|
||||
groupUuid: result.uuid,
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
userUuid: response.locals.user.uuid,
|
||||
inviterUuid: response.locals.user.uuid,
|
||||
permissions: 'write',
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { GroupInviteFactoryInterface } from './GroupInviteFactoryInterface'
|
||||
import { GroupInviteHash } from './GroupInviteHash'
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
|
||||
export class GroupInviteFactory implements GroupInviteFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
create(dto: GroupInviteHash): GroupInvite {
|
||||
const newGroupInvite = new GroupInvite()
|
||||
newGroupInvite.uuid = dto.uuid
|
||||
newGroupInvite.userUuid = dto.user_uuid
|
||||
newGroupInvite.groupUuid = dto.group_uuid
|
||||
newGroupInvite.inviterUuid = dto.inviter_uuid
|
||||
|
||||
const now = this.timer.getTimestampInMicroseconds()
|
||||
newGroupInvite.updatedAtTimestamp = now
|
||||
newGroupInvite.createdAtTimestamp = now
|
||||
|
||||
if (dto.created_at_timestamp) {
|
||||
newGroupInvite.createdAtTimestamp = dto.created_at_timestamp
|
||||
}
|
||||
|
||||
return newGroupInvite
|
||||
}
|
||||
|
||||
createStub(dto: GroupInviteHash): GroupInvite {
|
||||
const item = this.create(dto)
|
||||
return item
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
import { GroupInviteHash } from './GroupInviteHash'
|
||||
|
||||
export interface GroupInviteFactoryInterface {
|
||||
create(dto: GroupInviteHash): GroupInvite
|
||||
createStub(dto: GroupInviteHash): GroupInvite
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export type GroupInviteHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
group_uuid: string
|
||||
inviter_uuid: string
|
||||
permissions: string
|
||||
created_at_timestamp?: number
|
||||
updated_at_timestamp?: number
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity({ name: 'group_invites' })
|
||||
export class GroupInvite {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'group_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare groupUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'inviter_uuid',
|
||||
})
|
||||
declare inviterUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'invite_type',
|
||||
})
|
||||
declare inviteType: 'join' | 'key-change'
|
||||
|
||||
@Column({
|
||||
name: 'inviter_public_key',
|
||||
})
|
||||
declare inviterPublicKey: string
|
||||
|
||||
@Column({
|
||||
name: 'encrypted_group_key',
|
||||
})
|
||||
declare encryptedGroupKey: string
|
||||
|
||||
@Column({
|
||||
name: 'created_at_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare createdAtTimestamp: number
|
||||
|
||||
@Column({
|
||||
name: 'updated_at_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare updatedAtTimestamp: number
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
|
||||
export type GroupInviteQuery = {
|
||||
userUuid: string
|
||||
}
|
||||
|
||||
export type GroupInviteFindAllForUserQuery = {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
inviterUuid?: string
|
||||
includeSentAndReceived?: boolean
|
||||
}
|
||||
|
||||
export type GroupInviteFindAllForGroup = {
|
||||
groupUuid: string
|
||||
}
|
||||
|
||||
export interface GroupInviteRepositoryInterface {
|
||||
findByUuid(groupInviteUuid: string): Promise<GroupInvite | null>
|
||||
create(group: GroupInvite): Promise<GroupInvite>
|
||||
save(groupInvite: GroupInvite): Promise<GroupInvite>
|
||||
remove(group: GroupInvite): Promise<GroupInvite>
|
||||
findAllForUser(query: GroupInviteFindAllForUserQuery): Promise<GroupInvite[]>
|
||||
findAllForGroup(query: GroupInviteFindAllForGroup): Promise<GroupInvite[]>
|
||||
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupInvite | null>
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
||||
import {
|
||||
GroupInviteFindAllForGroup,
|
||||
GroupInviteFindAllForUserQuery,
|
||||
GroupInviteRepositoryInterface,
|
||||
} from './GroupInviteRepositoryInterface'
|
||||
|
||||
export class TypeORMGroupInviteRepository implements GroupInviteRepositoryInterface {
|
||||
constructor(private ormRepository: Repository<GroupInvite>) {}
|
||||
|
||||
async create(groupInvite: GroupInvite): Promise<GroupInvite> {
|
||||
return this.ormRepository.save(groupInvite)
|
||||
}
|
||||
|
||||
async save(groupInvite: GroupInvite): Promise<GroupInvite> {
|
||||
return this.ormRepository.save(groupInvite)
|
||||
}
|
||||
|
||||
findByUuid(uuid: string): Promise<GroupInvite | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group_invite')
|
||||
.where('group_invite.uuid = :uuid', {
|
||||
uuid,
|
||||
})
|
||||
.getOne()
|
||||
}
|
||||
|
||||
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupInvite | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group_invite')
|
||||
.where('group_invite.user_uuid = :userUuid', { userUuid })
|
||||
.andWhere('group_invite.group_uuid = :groupUuid', { groupUuid })
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async remove(groupInvite: GroupInvite): Promise<GroupInvite> {
|
||||
return this.ormRepository.remove(groupInvite)
|
||||
}
|
||||
|
||||
findAllForGroup(query: GroupInviteFindAllForGroup): Promise<GroupInvite[]> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group_invite')
|
||||
.where('group_invite.group_uuid = :groupUuid', { groupUuid: query.groupUuid })
|
||||
.getMany()
|
||||
}
|
||||
|
||||
async findAllForUser(query: GroupInviteFindAllForUserQuery): Promise<GroupInvite[]> {
|
||||
return this.createFindAllQueryBuilder(query).getMany()
|
||||
}
|
||||
|
||||
private createFindAllQueryBuilder(query: GroupInviteFindAllForUserQuery): SelectQueryBuilder<GroupInvite> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder('group_invite')
|
||||
|
||||
queryBuilder.where('group_invite.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
|
||||
if (query.includeSentAndReceived) {
|
||||
queryBuilder.orWhere('group_invite.sender_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
}
|
||||
|
||||
if (query.lastSyncTime) {
|
||||
queryBuilder.andWhere('group_invite.updated_at_timestamp > :lastSyncTime', {
|
||||
lastSyncTime: query.lastSyncTime,
|
||||
})
|
||||
}
|
||||
|
||||
if (query.inviterUuid) {
|
||||
queryBuilder.andWhere('group_invite.inviter_uuid = :inviterUuid', {
|
||||
inviterUuid: query.inviterUuid,
|
||||
})
|
||||
}
|
||||
|
||||
return queryBuilder
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface GetUserGroupKeysDTO {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
import { GroupInviteFactoryInterface } from '../Factory/GroupInviteFactoryInterface'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { GroupInviteRepositoryInterface } from '../Repository/GroupInviteRepositoryInterface'
|
||||
import { GroupInviteServiceInterface } from './GroupInviteServiceInterface'
|
||||
import { GetUserGroupKeysDTO } from './GetUserGroupInvitesDTO'
|
||||
import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface'
|
||||
|
||||
export class GroupInviteService implements GroupInviteServiceInterface {
|
||||
constructor(
|
||||
private groupRepository: GroupsRepositoryInterface,
|
||||
private groupInviteRepository: GroupInviteRepositoryInterface,
|
||||
private groupInviteFactory: GroupInviteFactoryInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async createGroupInvite(dto: {
|
||||
originatorUuid: string
|
||||
groupUuid: string
|
||||
userUuid: string
|
||||
inviterUuid: string
|
||||
permissions: string
|
||||
}): Promise<GroupInvite | null> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group || group.userUuid !== dto.originatorUuid) {
|
||||
return null
|
||||
}
|
||||
|
||||
const groupInvite = this.groupInviteFactory.create({
|
||||
uuid: uuidv4(),
|
||||
user_uuid: dto.userUuid,
|
||||
group_uuid: dto.groupUuid,
|
||||
inviter_uuid: dto.inviterUuid,
|
||||
permissions: dto.permissions,
|
||||
created_at_timestamp: this.timer.getTimestampInSeconds(),
|
||||
updated_at_timestamp: this.timer.getTimestampInSeconds(),
|
||||
})
|
||||
|
||||
return this.groupInviteRepository.create(groupInvite)
|
||||
}
|
||||
|
||||
getGroupInvitesForUser(dto: GetUserGroupKeysDTO): Promise<GroupInvite[]> {
|
||||
return this.groupInviteRepository.findAllForUser(dto)
|
||||
}
|
||||
|
||||
async getGroupInvites(dto: {
|
||||
groupUuid: string
|
||||
originatorUuid: string
|
||||
}): Promise<{ users: GroupInvite[]; isAdmin: boolean } | undefined> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const isUserGroupAdmin = group && group.userUuid === dto.originatorUuid
|
||||
const doesUserBelongToGroup = await this.groupInviteRepository.findByUserUuidAndGroupUuid(
|
||||
dto.originatorUuid,
|
||||
dto.groupUuid,
|
||||
)
|
||||
|
||||
if (!isUserGroupAdmin && !doesUserBelongToGroup) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const users = await this.groupInviteRepository.findAllForGroup({ groupUuid: dto.groupUuid })
|
||||
|
||||
return {
|
||||
users,
|
||||
isAdmin: isUserGroupAdmin,
|
||||
}
|
||||
}
|
||||
|
||||
async deleteGroupInvite(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise<boolean> {
|
||||
const groupInvite = await this.groupInviteRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid)
|
||||
if (!groupInvite) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAuthorized = groupInvite.inviterUuid !== dto.originatorUuid && groupInvite.userUuid !== dto.originatorUuid
|
||||
if (isAuthorized) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.groupInviteRepository.remove(groupInvite)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async deleteAllGroupInvitesForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group || group.userUuid !== dto.originatorUuid) {
|
||||
return false
|
||||
}
|
||||
|
||||
const groupInvites = await this.groupInviteRepository.findAllForGroup({ groupUuid: dto.groupUuid })
|
||||
for (const groupInvite of groupInvites) {
|
||||
await this.groupInviteRepository.remove(groupInvite)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
import { GetUserGroupKeysDTO } from './GetUserGroupInvitesDTO'
|
||||
|
||||
export interface GroupInviteServiceInterface {
|
||||
createGroupInvite(dto: {
|
||||
originatorUuid: string
|
||||
groupUuid: string
|
||||
userUuid: string
|
||||
inviterUuid: string
|
||||
permissions: string
|
||||
}): Promise<GroupInvite | null>
|
||||
|
||||
getGroupInvitesForUser(dto: GetUserGroupKeysDTO): Promise<GroupInvite[]>
|
||||
|
||||
getGroupInvites(dto: {
|
||||
groupUuid: string
|
||||
originatorUuid: string
|
||||
}): Promise<{ users: GroupInvite[]; isAdmin: boolean } | undefined>
|
||||
|
||||
deleteGroupInvite(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise<boolean>
|
||||
|
||||
deleteAllGroupInvitesForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean>
|
||||
}
|
||||
@@ -10,7 +10,6 @@ export class GroupUserFactory implements GroupUserFactoryInterface {
|
||||
const newGroupUser = new GroupUser()
|
||||
newGroupUser.uuid = dto.uuid
|
||||
newGroupUser.userUuid = dto.user_uuid
|
||||
newGroupUser.inviterUuid = dto.inviter_uuid
|
||||
newGroupUser.groupUuid = dto.group_uuid
|
||||
newGroupUser.permissions = dto.permissions
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ export type GroupUserHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
group_uuid: string
|
||||
inviter_uuid: string
|
||||
permissions: string
|
||||
created_at_timestamp?: number
|
||||
updated_at_timestamp?: number
|
||||
|
||||
@@ -17,11 +17,6 @@ export class GroupUser {
|
||||
})
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'inviter_uuid',
|
||||
})
|
||||
declare inviterUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'permissions',
|
||||
})
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export enum GroupPermission {
|
||||
export enum GroupUserPermission {
|
||||
Read = 'read',
|
||||
Write = 'write',
|
||||
Admin = 'admin',
|
||||
-1
@@ -7,7 +7,6 @@ export type GroupUserQuery = {
|
||||
export type GroupUserFindAllForUserQuery = {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
inviterUuid?: string
|
||||
includeSentAndReceived?: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -64,12 +64,6 @@ export class TypeORMGroupUserRepository implements GroupUserRepositoryInterface
|
||||
})
|
||||
}
|
||||
|
||||
if (query.inviterUuid) {
|
||||
queryBuilder.andWhere('group_user.inviter_uuid = :inviterUuid', {
|
||||
inviterUuid: query.inviterUuid,
|
||||
})
|
||||
}
|
||||
|
||||
return queryBuilder
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ export class GroupUserService implements GroupUserServiceInterface {
|
||||
originatorUuid: string
|
||||
groupUuid: string
|
||||
userUuid: string
|
||||
inviterUuid: string
|
||||
permissions: string
|
||||
}): Promise<GroupUser | null> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
@@ -31,7 +30,6 @@ export class GroupUserService implements GroupUserServiceInterface {
|
||||
uuid: uuidv4(),
|
||||
user_uuid: dto.userUuid,
|
||||
group_uuid: dto.groupUuid,
|
||||
inviter_uuid: dto.inviterUuid,
|
||||
permissions: dto.permissions,
|
||||
created_at_timestamp: this.timer.getTimestampInSeconds(),
|
||||
updated_at_timestamp: this.timer.getTimestampInSeconds(),
|
||||
@@ -72,13 +70,18 @@ export class GroupUserService implements GroupUserServiceInterface {
|
||||
}
|
||||
|
||||
async deleteGroupUser(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise<boolean> {
|
||||
const groupUser = await this.groupUserRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid)
|
||||
if (!groupUser) {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAuthorized = groupUser.inviterUuid !== dto.originatorUuid && groupUser.userUuid !== dto.originatorUuid
|
||||
if (isAuthorized) {
|
||||
const isAuthorized = group.userUuid === dto.originatorUuid
|
||||
if (!isAuthorized) {
|
||||
return false
|
||||
}
|
||||
|
||||
const groupUser = await this.groupUserRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid)
|
||||
if (!groupUser) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ export interface GroupUserServiceInterface {
|
||||
originatorUuid: string
|
||||
groupUuid: string
|
||||
userUuid: string
|
||||
inviterUuid: string
|
||||
permissions: string
|
||||
}): Promise<GroupUser | null>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
|
||||
import { ItemHash } from '../ItemHash'
|
||||
import { GroupPermission } from '../../GroupUser/Model/GroupPermission'
|
||||
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
|
||||
|
||||
export class OwnershipFilter implements ItemSaveRuleInterface {
|
||||
constructor(private groupUserService: GroupUserServiceInterface) {}
|
||||
@@ -19,7 +19,7 @@ export class OwnershipFilter implements ItemSaveRuleInterface {
|
||||
const groupAuthorization = await this.itemBelongsToAuthorizedSharedGroup(dto.userUuid, dto.itemHash)
|
||||
if (groupAuthorization) {
|
||||
if (isItemBeingRemovedFromGroup) {
|
||||
if (groupAuthorization === GroupPermission.Admin) {
|
||||
if (groupAuthorization === GroupUserPermission.Admin) {
|
||||
return {
|
||||
passed: true,
|
||||
}
|
||||
@@ -48,12 +48,12 @@ export class OwnershipFilter implements ItemSaveRuleInterface {
|
||||
private async itemBelongsToAuthorizedSharedGroup(
|
||||
userUuid: string,
|
||||
itemHash: ItemHash,
|
||||
): Promise<GroupPermission | undefined> {
|
||||
): Promise<GroupUserPermission | undefined> {
|
||||
const groupUsers = await this.groupUserService.getGroupUsersForUser({ userUuid })
|
||||
|
||||
for (const groupUser of groupUsers) {
|
||||
if (itemHash.group_uuid === groupUser.groupUuid) {
|
||||
return groupUser.permissions as GroupPermission
|
||||
return groupUser.permissions as GroupUserPermission
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export type GroupInviteProjection = {
|
||||
uuid: string
|
||||
group_uuid: string
|
||||
user_uuid: string
|
||||
inviter_uuid: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { GroupInvite } from '../Domain/GroupInvite/Model/GroupInvite'
|
||||
import { ProjectorInterface } from './ProjectorInterface'
|
||||
import { GroupInviteProjection } from './GroupInviteProjection'
|
||||
|
||||
export class GroupInviteProjector implements ProjectorInterface<GroupInvite, GroupInviteProjection> {
|
||||
projectSimple(_groupInvite: GroupInvite): GroupInviteProjection {
|
||||
throw Error('not implemented')
|
||||
}
|
||||
|
||||
projectCustom(_projectionType: string, groupInvite: GroupInvite): GroupInviteProjection {
|
||||
const fullProjection = this.projectFull(groupInvite)
|
||||
|
||||
return {
|
||||
...fullProjection,
|
||||
user_uuid: groupInvite.userUuid,
|
||||
}
|
||||
}
|
||||
|
||||
projectFull(groupInvite: GroupInvite): GroupInviteProjection {
|
||||
return {
|
||||
uuid: groupInvite.uuid,
|
||||
group_uuid: groupInvite.groupUuid,
|
||||
user_uuid: groupInvite.userUuid,
|
||||
inviter_uuid: groupInvite.inviterUuid,
|
||||
created_at_timestamp: groupInvite.createdAtTimestamp,
|
||||
updated_at_timestamp: groupInvite.updatedAtTimestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ export type GroupUserProjection = {
|
||||
uuid: string
|
||||
group_uuid: string
|
||||
user_uuid: string
|
||||
inviter_uuid: string
|
||||
permissions: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
|
||||
@@ -35,7 +35,6 @@ export class GroupUserProjector implements ProjectorInterface<GroupUser, GroupUs
|
||||
uuid: groupUser.uuid,
|
||||
group_uuid: groupUser.groupUuid,
|
||||
user_uuid: groupUser.userUuid,
|
||||
inviter_uuid: groupUser.inviterUuid,
|
||||
permissions: groupUser.permissions,
|
||||
created_at_timestamp: groupUser.createdAtTimestamp,
|
||||
updated_at_timestamp: groupUser.updatedAtTimestamp,
|
||||
|
||||
Reference in New Issue
Block a user