calendar of events

This commit is contained in:
Alex Ant
2026-03-28 21:09:57 +05:00
parent 396e17f698
commit 48ecb49a59
98 changed files with 2749 additions and 183 deletions
+90 -17
View File
@@ -1568,6 +1568,10 @@ type CallTranscription {
endedAt: DateTime
id: String!
matrixRoomId: String!
"""
Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id
"""
participants: [String!]!
roomId: String!
roomName: String!
@@ -1881,12 +1885,12 @@ type CapitalContributor {
"""ID в блокчейне"""
id: Int
"""Является ли внешним контрактом"""
is_external_contract: Boolean
"""Соглашение Благорост предоставлено при импорте (внешний документ)"""
is_external_blagorost_agreement: Boolean
"""Является ли внешним контрактом"""
is_external_contract: Boolean
"""Последнее обновление энергии"""
last_energy_update: String
@@ -3297,12 +3301,12 @@ type CapitalStory {
"""Номер блока крайней синхронизации с блокчейном"""
block_num: Float
"""Имя аккаунта кооператива"""
coopname: String!
"""Формат содержимого (markdown-текст или BPMN 2.0 XML в description)"""
content_format: CapitalStoryContentFormat!
"""Имя аккаунта кооператива"""
coopname: String!
"""Имя пользователя, создавшего историю"""
created_by: String!
@@ -3331,6 +3335,16 @@ type CapitalStory {
title: String!
}
"""
Формат содержимого требования (истории) в CAPITAL: MARKDOWN, BPMN (XML), DRAWIO (draw.io / diagrams.net XML) или MERMAID (текст диаграммы)
"""
enum CapitalStoryContentFormat {
BPMN
DRAWIO
MARKDOWN
MERMAID
}
"""Параметры фильтрации для запросов историй CAPITAL"""
input CapitalStoryFilter {
"""Фильтр по названию кооператива"""
@@ -3610,6 +3624,29 @@ type ChartOfAccountsItem {
writeoff: String!
}
type ChatCoopCalendarEvent {
createdAt: DateTime!
createdByUsername: String!
description: String
endsAt: DateTime
icsSequence: Int!
id: String!
matrixRoomId: String!
startsAt: DateTime!
title: String!
updatedAt: DateTime!
}
type ChatCoopCalendarIcsUrlResponse {
"""Полный URL ленты ICS с секретом в query (без JWT)"""
icsUrl: String!
}
type ChatCoopCalendarRoomOption {
displayLabel: String!
matrixRoomId: String!
}
input CheckMatrixUsernameInput {
username: String!
}
@@ -4147,6 +4184,14 @@ input CreateBranchInput {
trustee: String!
}
input CreateChatCoopCalendarEventInput {
description: String
endsAt: DateTime
matrixRoomId: String!
startsAt: DateTime!
title: String!
}
input CreateChildOrderInput {
"""Имя кооператива"""
coopname: String!
@@ -4614,12 +4659,12 @@ input CreateSovietIndividualDataInput {
}
input CreateStoryInput {
"""Имя аккаунта кооператива"""
coopname: String!
"""Формат содержимого; по умолчанию MARKDOWN"""
content_format: CapitalStoryContentFormat = MARKDOWN
"""Имя аккаунта кооператива"""
coopname: String!
"""Описание истории"""
description: String
@@ -7049,6 +7094,18 @@ type Mutation {
"""Создать Matrix аккаунт с именем пользователя и паролем"""
chatcoopCreateAccount(data: CreateMatrixAccountInputDTO!): Boolean!
"""Создать событие календаря"""
chatcoopCreateCalendarEvent(data: CreateChatCoopCalendarEventInput!): ChatCoopCalendarEvent!
"""Выдать или обновить персональный URL подписки ICS (секрет в query)"""
chatcoopCreateCalendarIcsSubscription: ChatCoopCalendarIcsUrlResponse!
"""Удалить событие календаря"""
chatcoopDeleteCalendarEvent(id: String!): Boolean!
"""Обновить событие календаря"""
chatcoopUpdateCalendarEvent(data: UpdateChatCoopCalendarEventInput!): ChatCoopCalendarEvent!
"""Выполнить шаг онбординга capital (создание предложения повестки)"""
completeCapitalOnboardingStep(data: CapitalOnboardingStepInput!): CapitalOnboardingState!
@@ -8855,6 +8912,14 @@ type Query {
"""Получить список транскрипций звонков"""
chatcoopGetTranscriptions(data: GetTranscriptionsInput): [CallTranscription!]!
"""Список событий календаря кооператива"""
chatcoopListCalendarEvents: [ChatCoopCalendarEvent!]!
"""
Незашифрованные комнаты из реестра ChatCoop для привязки события календаря
"""
chatcoopListCalendarRooms: [ChatCoopCalendarRoomOption!]!
"""Получить сводную информацию о аккаунте"""
getAccount(data: GetAccountInput!): Account!
@@ -10265,14 +10330,6 @@ enum StoryStatus {
PENDING
}
"""Формат содержимого требования (истории) в CAPITAL"""
enum CapitalStoryContentFormat {
BPMN
DRAWIO
MARKDOWN
MERMAID
}
input SubmitVoteInput {
"""Имя аккаунта кооператива"""
coopname: String!
@@ -10434,7 +10491,11 @@ type TranscriptionSegment {
createdAt: DateTime!
endOffset: Float!
id: String!
"""Канонический Matrix user id (@localpart:server)"""
speakerIdentity: String!
"""Отображаемое имя из Synapse (displayname)"""
speakerName: String!
startOffset: Float!
text: String!
@@ -10518,6 +10579,15 @@ input UpdateBankAccountInput {
username: String!
}
input UpdateChatCoopCalendarEventInput {
description: String
endsAt: DateTime
id: String!
matrixRoomId: String!
startsAt: DateTime!
title: String!
}
input UpdateEntrepreneurDataInput {
"""Дата рождения"""
birthdate: String!
@@ -10710,6 +10780,9 @@ input UpdateSettingsInput {
}
input UpdateStoryInput {
"""Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID)"""
content_format: CapitalStoryContentFormat
"""Описание истории"""
description: String
@@ -1,6 +1,7 @@
import { Field, InputType, Int } from '@nestjs/graphql';
import { IsNotEmpty, IsString, IsOptional, IsEnum, Min } from 'class-validator';
import { StoryStatus } from '../../../domain/enums/story-status.enum';
import { StoryContentFormat } from '../../../domain/enums/story-content-format.enum';
/**
* GraphQL Input DTO для обновления истории
@@ -38,6 +39,14 @@ export class UpdateStoryInputDTO {
@IsEnum(StoryStatus, { message: 'Неверный статус истории' })
status?: StoryStatus;
@Field(() => StoryContentFormat, {
nullable: true,
description: 'Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID)',
})
@IsOptional()
@IsEnum(StoryContentFormat, { message: 'Неверный формат содержимого истории' })
content_format?: StoryContentFormat;
@Field(() => String, {
nullable: true,
description: 'Хеш проекта (если история привязана к проекту)',
@@ -33,7 +33,6 @@ import { StoryStatus } from '../../domain/enums/story-status.enum';
import { StoryContentFormat } from '../../domain/enums/story-content-format.enum';
import { normalizeBpmnStoryDescription } from '../../domain/utils/bpmn-story-description.util';
import { EMPTY_BPMN_STORY_XML } from '../constants/empty-bpmn-story-xml';
import { EMPTY_DRAWIO_STORY_XML } from '../constants/empty-drawio-story-xml';
import { DEFAULT_MERMAID_STORY_SOURCE } from '../constants/default-mermaid-story';
import { IssuePriority } from '../../domain/enums/issue-priority.enum';
import { IssueStatus } from '../../domain/enums/issue-status.enum';
@@ -401,13 +400,18 @@ export class GenerationService {
// Определяем issue_hash с нормализацией
const issueHash = data.issue_hash ?? existingStory.issue_hash;
const nextContentFormat = data.content_format ?? existingStory.content_format;
let nextDescription = data.description ?? existingStory.description;
if (data.description !== undefined) {
if (
(existingStory.content_format === StoryContentFormat.BPMN ||
existingStory.content_format === StoryContentFormat.DRAWIO) &&
data.description !== undefined
nextContentFormat === StoryContentFormat.BPMN ||
nextContentFormat === StoryContentFormat.DRAWIO
) {
nextDescription = normalizeBpmnStoryDescription(data.description, existingStory.content_format);
nextDescription = normalizeBpmnStoryDescription(data.description, nextContentFormat);
} else {
nextDescription = data.description;
}
}
const nextTitle = data.title ?? existingStory.title;
@@ -420,7 +424,7 @@ export class GenerationService {
coopname: existingStory.coopname,
title: nextTitle,
description: nextDescription,
content_format: existingStory.content_format,
content_format: nextContentFormat,
status: data.status ?? existingStory.status,
project_hash: data.project_hash ?? existingStory.project_hash,
// Нормализация: пустая строка или undefined преобразуется в undefined
@@ -734,7 +738,6 @@ export class GenerationService {
currentUser?.role
);
}
console.log('inputData', data)
// Создаем обновленные данные для доменной сущности
const updatedIssueDatabaseData: IIssueDatabaseData = {
_id: existingIssue._id,
@@ -758,8 +761,6 @@ export class GenerationService {
},
present: existingIssue.present,
};
console.log('updatedIssueDatabaseData', updatedIssueDatabaseData)
// Создаем доменную сущность с обновленными данными
const issueEntity = new IssueDomainEntity(updatedIssueDatabaseData);
@@ -775,7 +776,7 @@ export class GenerationService {
// Сохраняем через репозиторий
const updatedIssue = await this.issueRepository.update(issueEntity);
console.log('updatedIssue', updatedIssue)
// Рассчитываем права доступа для задачи
const permissions = await this.permissionsService.calculateIssuePermissions(updatedIssue, currentUser);
@@ -365,13 +365,16 @@ export class GitHubSyncService {
await this.githubService.deleteFile(this.owner, this.repo, oldPath, `Rename: remove old file ${oldPath}`, oldSha);
}
// Создаём новый файл
// Если по целевому пути файл уже есть (другой поток синка, ручной коммит), нужен blob-sha для обновления
const newPathExistingSha = await this.githubService.getFileSha(this.owner, this.repo, newPath);
const newSha = await this.githubService.createOrUpdateFile(
this.owner,
this.repo,
newPath,
newContent,
`Rename: create new file ${newPath}`
`Rename: create new file ${newPath}`,
newPathExistingSha || undefined
);
// Обновляем индекс
@@ -477,7 +480,6 @@ export class GitHubSyncService {
if (changedFiles.length === 0) {
this.logger.debug('Нет изменённых файлов для синхронизации');
// Обновляем SHA как последний синхронизированный
await this.fileIndexRepository.upsert({
coopname: this.coopname,
entity_type: 'project',
@@ -517,7 +519,6 @@ export class GitHubSyncService {
if (mdFiles.length === 0) {
this.logger.log('В репозитории нет файлов проектов для синхронизации');
// Сохраняем SHA как последний синхронизированный, чтобы не повторять попытки
await this.fileIndexRepository.upsert({
coopname: this.coopname,
entity_type: 'project',
@@ -549,7 +550,6 @@ export class GitHubSyncService {
} catch (error: any) {
this.logger.error(`Ошибка первичной синхронизации: ${error.message}`);
// Если репозиторий пустой, просто сохраняем маркер синхронизации
await this.fileIndexRepository.upsert({
coopname: this.coopname,
entity_type: 'project',
@@ -667,6 +667,15 @@ export class GitHubSyncService {
const existing = await this.projectRepository.findByHash(projectData.hash);
if (existing) {
const titleMatches = (projectData.title || '') === (existing.title || '');
const descMatches = (projectData.description || '') === (existing.description || '');
if (titleMatches && descMatches) {
this.logger.debug(
`Проект ${projectData.hash}: title и description совпадают с БД — пропуск обновления из GitHub`
);
return;
}
// Проверяем, нужно ли обновление (по дате)
const githubUpdatedAt = projectData.updated_at ? new Date(projectData.updated_at) : null;
const dbUpdatedAt = existing._updated_at ? new Date(existing._updated_at) : null;
@@ -740,6 +749,8 @@ export class GitHubSyncService {
labels: issueData.labels,
submaster: issueData.submaster,
creators: issueData.creators,
sort_order: issueData.sort_order,
cycle_id: issueData.cycle_id,
},
chairman.username,
chairman
@@ -754,8 +765,13 @@ export class GitHubSyncService {
title: issueData.title,
description: issueData.description,
priority: issueData.priority as any,
status: issueData.status as any,
estimate: issueData.estimate,
labels: issueData.labels,
submaster: issueData.submaster,
sort_order: issueData.sort_order,
cycle_id: issueData.cycle_id,
...(issueData.creators?.length ? { creators: issueData.creators } : {}),
},
chairman.username,
chairman
@@ -792,8 +808,13 @@ export class GitHubSyncService {
title: storyData.title,
description: storyData.description,
status: storyData.status as any,
content_format: storyData.content_format,
project_hash: storyData.project_hash,
issue_hash: storyData.issue_hash,
sort_order: storyData.sort_order,
},
chairman.username
chairman.username,
chairman
);
} else {
// Создаём новое требование
@@ -806,6 +827,9 @@ export class GitHubSyncService {
issue_hash: storyData.issue_hash,
title: storyData.title,
description: storyData.description,
content_format: storyData.content_format,
status: storyData.status as any,
sort_order: storyData.sort_order,
},
chairman
);
@@ -5,6 +5,7 @@ import type { IssueDomainEntity } from '../entities/issue.entity';
import type { StoryDomainEntity } from '../entities/story.entity';
import type { ResultDomainEntity } from '../entities/result.entity';
import type { SegmentDomainEntity } from '../entities/segment.entity';
import { StoryContentFormat } from '../enums/story-content-format.enum';
/**
* Интерфейс для результата парсинга markdown
@@ -34,7 +35,6 @@ export interface ProjectMarkdownData {
export interface IssueMarkdownData {
type: 'issue';
title: string;
id: string;
hash: string;
project_hash: string;
cycle_id?: string;
@@ -58,6 +58,7 @@ export interface StoryMarkdownData {
type: 'story';
title: string;
hash: string;
content_format: StoryContentFormat;
project_hash?: string;
issue_hash?: string;
status: string;
@@ -198,6 +199,21 @@ export class FileFormatService {
return /[а-яё]/i.test(text);
}
/**
* Нормализует content_format из frontmatter; неизвестные значения → MARKDOWN.
*/
parseStoryContentFormat(raw: unknown): StoryContentFormat {
if (raw === undefined || raw === null || raw === '') {
return StoryContentFormat.MARKDOWN;
}
const s = String(raw).trim();
const allowed = Object.values(StoryContentFormat) as string[];
if (allowed.includes(s)) {
return s as StoryContentFormat;
}
return StoryContentFormat.MARKDOWN;
}
/**
* Генерирует slug из заголовка
* Для русских названий делает транслитерацию в английский
@@ -276,7 +292,6 @@ export class FileFormatService {
const frontmatter: Record<string, any> = {
type: 'issue',
title: issue.title,
id: issue.id,
hash: issue.issue_hash,
project_hash: issue.project_hash,
status: issue.status,
@@ -316,17 +331,22 @@ export class FileFormatService {
markdownToIssue(content: string): IssueMarkdownData {
const { frontmatter, body } = this.parseMarkdownFile(content);
const estimateRaw = frontmatter.estimate;
const estimate =
estimateRaw === undefined || estimateRaw === null || estimateRaw === ''
? 0
: Number(estimateRaw);
return {
type: 'issue',
title: frontmatter.title,
id: frontmatter.id,
hash: frontmatter.hash,
project_hash: frontmatter.project_hash,
cycle_id: frontmatter.cycle_id,
status: frontmatter.status,
priority: frontmatter.priority,
estimate: frontmatter.estimate,
created_by: frontmatter.created_by,
status: frontmatter.status ?? 'backlog',
priority: frontmatter.priority ?? 'medium',
estimate: Number.isFinite(estimate) ? estimate : 0,
created_by: frontmatter.created_by ?? '',
submaster: frontmatter.submaster,
creators: frontmatter.creators || [],
labels: frontmatter.labels || [],
@@ -347,6 +367,7 @@ export class FileFormatService {
type: 'story',
title: story.title,
hash: story.story_hash,
content_format: story.content_format,
status: story.status,
created_by: story.created_by,
sort_order: story.sort_order,
@@ -384,9 +405,10 @@ export class FileFormatService {
type: 'story',
title: frontmatter.title,
hash: frontmatter.hash,
content_format: this.parseStoryContentFormat(frontmatter.content_format),
project_hash: frontmatter.project_hash,
issue_hash: frontmatter.issue_hash,
status: frontmatter.status,
status: frontmatter.status ?? 'pending',
created_by: frontmatter.created_by,
description: body,
created_at: frontmatter.created_at,
@@ -1,4 +1,4 @@
import { Injectable, Logger, OnModuleInit, OnModuleDestroy, Inject } from '@nestjs/common'
import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { GitHubSyncService } from '../../application/services/github-sync.service'
import * as cron from 'node-cron'
import { config } from '~/config'
@@ -230,7 +230,15 @@ export class GitHubService {
if (error.status === 409 && retryOnConflict) {
const freshSha = await this.getFileSha(owner, repo, path, branch);
if (freshSha) {
// Рекурсивный вызов с свежим SHA, но без повторных попыток
return await this.createOrUpdateFile(owner, repo, path, content, message, freshSha, branch, false);
}
}
// 422: файл уже существует, а sha не передали — подставляем актуальный blob-sha и повторяем
const msg = String(error?.message ?? '');
if (error.status === 422 && retryOnConflict && !sha && msg.includes('sha') && msg.includes("wasn't supplied")) {
const freshSha = await this.getFileSha(owner, repo, path, branch);
if (freshSha) {
return await this.createOrUpdateFile(owner, repo, path, content, message, freshSha, branch, false);
}
}
@@ -0,0 +1,42 @@
import { Controller, Get, Query, Res, Logger } from '@nestjs/common';
import type { Response } from 'express';
import { ChatCoopCalendarApplicationService } from '../services/chatcoop-calendar-application.service';
/**
* Публичная лента ICS по секрету в query (без JWT). Google Calendar и др. клиенты опрашивают URL напрямую.
*/
@Controller('v1/extensions/chatcoop/calendar')
export class ChatCoopCalendarFeedController {
private readonly logger = new Logger(ChatCoopCalendarFeedController.name);
constructor(private readonly calendar: ChatCoopCalendarApplicationService) {}
@Get('feed.ics')
async feed(
@Query('id') subscriptionId: string,
@Query('secret') secret: string,
@Res() res: Response
): Promise<void> {
if (typeof subscriptionId !== 'string' || subscriptionId.length === 0) {
res.status(400).send('Missing id');
return;
}
if (typeof secret !== 'string' || secret.length === 0) {
res.status(400).send('Missing secret');
return;
}
try {
const body = await this.calendar.buildIcsDocumentForSubscription(subscriptionId, secret);
if (!body) {
res.status(404).send('Not found');
return;
}
res.setHeader('Content-Type', 'text/calendar; charset=utf-8');
res.setHeader('Cache-Control', 'private, max-age=300');
res.status(200).send(body);
} catch (e) {
this.logger.warn(`ICS feed error: ${String(e)}`);
res.status(500).send('Internal error');
}
}
}
@@ -0,0 +1,115 @@
import { Field, GraphQLISODateTime, InputType, Int, ObjectType } from '@nestjs/graphql';
import { IsOptional, IsString, IsUUID, MaxLength, MinLength } from 'class-validator';
import { Type } from 'class-transformer';
@ObjectType('ChatCoopCalendarRoomOption')
export class ChatCoopCalendarRoomOptionDTO {
@Field()
matrixRoomId!: string;
@Field()
displayLabel!: string;
}
@ObjectType('ChatCoopCalendarEvent')
export class ChatCoopCalendarEventDTO {
@Field()
id!: string;
@Field()
matrixRoomId!: string;
@Field()
title!: string;
@Field(() => String, { nullable: true })
description!: string | null;
@Field(() => GraphQLISODateTime)
startsAt!: Date;
@Field(() => GraphQLISODateTime, { nullable: true })
endsAt!: Date | null;
@Field()
createdByUsername!: string;
@Field(() => Int)
icsSequence!: number;
@Field(() => GraphQLISODateTime)
createdAt!: Date;
@Field(() => GraphQLISODateTime)
updatedAt!: Date;
}
@ObjectType('ChatCoopCalendarIcsUrlResponse')
export class ChatCoopCalendarIcsUrlResponseDTO {
@Field({ description: 'Полный URL ленты ICS с секретом в query (без JWT)' })
icsUrl!: string;
}
@InputType('CreateChatCoopCalendarEventInput')
export class CreateChatCoopCalendarEventInputDTO {
@Field()
@IsString()
@MinLength(1)
@MaxLength(255)
matrixRoomId!: string;
@Field()
@IsString()
@MinLength(1)
@MaxLength(500)
title!: string;
@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
@MaxLength(8000)
description?: string | null;
@Field(() => GraphQLISODateTime)
@Type(() => Date)
startsAt!: Date;
@Field(() => GraphQLISODateTime, { nullable: true })
@IsOptional()
@Type(() => Date)
endsAt?: Date | null;
}
@InputType('UpdateChatCoopCalendarEventInput')
export class UpdateChatCoopCalendarEventInputDTO {
@Field()
@IsUUID('4')
id!: string;
@Field()
@IsString()
@MinLength(1)
@MaxLength(255)
matrixRoomId!: string;
@Field()
@IsString()
@MinLength(1)
@MaxLength(500)
title!: string;
@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
@MaxLength(8000)
description?: string | null;
@Field(() => GraphQLISODateTime)
@Type(() => Date)
startsAt!: Date;
@Field(() => GraphQLISODateTime, { nullable: true })
@IsOptional()
@Type(() => Date)
endsAt?: Date | null;
}
@@ -0,0 +1,139 @@
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { UseGuards } from '@nestjs/common';
import { GqlJwtAuthGuard } from '~/application/auth/guards/graphql-jwt-auth.guard';
import { CurrentUser } from '~/application/auth/decorators/current-user.decorator';
import type { MonoAccountDomainInterface } from '~/domain/account/interfaces/mono-account-domain.interface';
import { AuthRoles } from '~/application/auth/decorators/auth.decorator';
import { RolesGuard } from '~/application/auth/guards/roles.guard';
import { ChatCoopCalendarApplicationService } from '../services/chatcoop-calendar-application.service';
import {
ChatCoopCalendarEventDTO,
ChatCoopCalendarIcsUrlResponseDTO,
ChatCoopCalendarRoomOptionDTO,
CreateChatCoopCalendarEventInputDTO,
UpdateChatCoopCalendarEventInputDTO,
} from '../dto/calendar.dto';
function toEventDto(e: {
id: string;
matrixRoomId: string;
title: string;
description: string | null;
startsAt: Date;
endsAt: Date | null;
createdByUsername: string;
icsSequence: number;
createdAt: Date;
updatedAt: Date;
}): ChatCoopCalendarEventDTO {
const dto = new ChatCoopCalendarEventDTO();
dto.id = e.id;
dto.matrixRoomId = e.matrixRoomId;
dto.title = e.title;
dto.description = e.description;
dto.startsAt = e.startsAt;
dto.endsAt = e.endsAt;
dto.createdByUsername = e.createdByUsername;
dto.icsSequence = e.icsSequence;
dto.createdAt = e.createdAt;
dto.updatedAt = e.updatedAt;
return dto;
}
@Resolver()
export class ChatCoopCalendarResolver {
constructor(private readonly calendar: ChatCoopCalendarApplicationService) {}
@Query(() => [ChatCoopCalendarRoomOptionDTO], {
name: 'chatcoopListCalendarRooms',
description: 'Незашифрованные комнаты из реестра ChatCoop для привязки события календаря',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async listRooms(): Promise<ChatCoopCalendarRoomOptionDTO[]> {
const rows = await this.calendar.listPlaintextRoomsForPicker();
return rows.map((r) => {
const dto = new ChatCoopCalendarRoomOptionDTO();
dto.matrixRoomId = r.matrixRoomId;
dto.displayLabel = r.displayLabel;
return dto;
});
}
@Query(() => [ChatCoopCalendarEventDTO], {
name: 'chatcoopListCalendarEvents',
description: 'Список событий календаря кооператива',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member', 'user'])
async listEvents(): Promise<ChatCoopCalendarEventDTO[]> {
const list = await this.calendar.listEvents();
return list.map(toEventDto);
}
@Mutation(() => ChatCoopCalendarEventDTO, {
name: 'chatcoopCreateCalendarEvent',
description: 'Создать событие календаря',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async create(
@CurrentUser() user: MonoAccountDomainInterface,
@Args('data', { type: () => CreateChatCoopCalendarEventInputDTO }) data: CreateChatCoopCalendarEventInputDTO
): Promise<ChatCoopCalendarEventDTO> {
const ev = await this.calendar.createEvent(user.username, {
matrixRoomId: data.matrixRoomId,
title: data.title,
description: data.description ?? null,
startsAt: data.startsAt,
endsAt: data.endsAt ?? null,
});
return toEventDto(ev);
}
@Mutation(() => ChatCoopCalendarEventDTO, {
name: 'chatcoopUpdateCalendarEvent',
description: 'Обновить событие календаря',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async update(
@Args('data', { type: () => UpdateChatCoopCalendarEventInputDTO }) data: UpdateChatCoopCalendarEventInputDTO
): Promise<ChatCoopCalendarEventDTO> {
const ev = await this.calendar.updateEvent({
id: data.id,
matrixRoomId: data.matrixRoomId,
title: data.title,
description: data.description ?? null,
startsAt: data.startsAt,
endsAt: data.endsAt ?? null,
});
return toEventDto(ev);
}
@Mutation(() => Boolean, {
name: 'chatcoopDeleteCalendarEvent',
description: 'Удалить событие календаря',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async delete(@Args('id') id: string): Promise<boolean> {
await this.calendar.deleteEvent(id);
return true;
}
@Mutation(() => ChatCoopCalendarIcsUrlResponseDTO, {
name: 'chatcoopCreateCalendarIcsSubscription',
description: 'Выдать или обновить персональный URL подписки ICS (секрет в query)',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member', 'user'])
async createIcs(
@CurrentUser() user: MonoAccountDomainInterface
): Promise<ChatCoopCalendarIcsUrlResponseDTO> {
const url = await this.calendar.createOrRotatePersonalIcsUrl(user.username);
const dto = new ChatCoopCalendarIcsUrlResponseDTO();
dto.icsUrl = url;
return dto;
}
}
@@ -0,0 +1,217 @@
import { Inject, Injectable } from '@nestjs/common';
import * as crypto from 'crypto';
import config from '~/config/config';
import type {
InterCalendarEventWindow,
InterCoopCalendarEventRead,
} from '@coopenomics/inter';
import type { ChatcoopManagedMatrixRoomRepository } from '../../domain/repositories/managed-matrix-room.repository';
import { CHATCOOP_MANAGED_MATRIX_ROOM_REPOSITORY } from '../../domain/repositories/managed-matrix-room.repository';
import type { ChatCoopCalendarEventRepository } from '../../domain/repositories/calendar-event.repository';
import { CHATCOOP_CALENDAR_EVENT_REPOSITORY } from '../../domain/repositories/calendar-event.repository';
import type { ChatCoopCalendarIcsSubscriptionRepository } from '../../domain/repositories/calendar-ics-subscription.repository';
import { CHATCOOP_CALENDAR_ICS_SUBSCRIPTION_REPOSITORY } from '../../domain/repositories/calendar-ics-subscription.repository';
import type { ChatCoopCalendarEventDomainEntity } from '../../domain/entities/calendar-event.entity';
function sha256Hex(plain: string): string {
return crypto.createHash('sha256').update(plain, 'utf8').digest('hex');
}
function timingSafeEqualHex(a: string, b: string): boolean {
try {
const ba = Buffer.from(a, 'hex');
const bb = Buffer.from(b, 'hex');
if (ba.length !== bb.length) {
return false;
}
return crypto.timingSafeEqual(ba, bb);
} catch {
return false;
}
}
/** Форматирование даты для ICS в UTC (базовый формат DTSTART/DTEND). */
function formatIcsUtc(d: Date): string {
const y = d.getUTCFullYear();
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
const day = String(d.getUTCDate()).padStart(2, '0');
const h = String(d.getUTCHours()).padStart(2, '0');
const min = String(d.getUTCMinutes()).padStart(2, '0');
const s = String(d.getUTCSeconds()).padStart(2, '0');
return `${y}${m}${day}T${h}${min}${s}Z`;
}
function escapeIcsText(text: string): string {
return text
.replace(/\\/g, '\\\\')
.replace(/\n/g, '\\n')
.replace(/;/g, '\\;')
.replace(/,/g, '\\,');
}
function foldIcsLine(line: string): string {
const max = 75;
if (line.length <= max) {
return line;
}
const parts: string[] = [];
let rest = line;
parts.push(rest.slice(0, max));
rest = rest.slice(max);
while (rest.length > 0) {
const chunk = rest.slice(0, max - 1);
parts.push(` ${chunk}`);
rest = rest.slice(max - 1);
}
return parts.join('\r\n');
}
@Injectable()
export class ChatCoopCalendarApplicationService {
constructor(
@Inject(CHATCOOP_MANAGED_MATRIX_ROOM_REPOSITORY)
private readonly managedRooms: ChatcoopManagedMatrixRoomRepository,
@Inject(CHATCOOP_CALENDAR_EVENT_REPOSITORY)
private readonly events: ChatCoopCalendarEventRepository,
@Inject(CHATCOOP_CALENDAR_ICS_SUBSCRIPTION_REPOSITORY)
private readonly icsSubs: ChatCoopCalendarIcsSubscriptionRepository
) {}
async listPlaintextRoomsForPicker(): Promise<{ matrixRoomId: string; displayLabel: string }[]> {
const rows = await this.managedRooms.findEligibleForSecretaryTranscription();
return rows.map((r) => ({ matrixRoomId: r.matrixRoomId, displayLabel: r.displayLabel }));
}
async listEvents(): Promise<ChatCoopCalendarEventDomainEntity[]> {
return this.events.listAll();
}
async assertPlaintextManagedRoom(matrixRoomId: string): Promise<void> {
const room = await this.managedRooms.findByMatrixRoomId(matrixRoomId);
if (!room || room.encrypted) {
throw new Error('Комната не найдена в реестре или недоступна для календаря (только незашифрованные комнаты)');
}
}
async createEvent(
coopUsername: string,
input: {
matrixRoomId: string;
title: string;
description: string | null;
startsAt: Date;
endsAt: Date | null;
}
): Promise<ChatCoopCalendarEventDomainEntity> {
await this.assertPlaintextManagedRoom(input.matrixRoomId);
return this.events.create({
matrixRoomId: input.matrixRoomId,
title: input.title,
description: input.description,
startsAt: input.startsAt,
endsAt: input.endsAt,
createdByUsername: coopUsername.toLowerCase(),
});
}
async updateEvent(input: {
id: string;
matrixRoomId: string;
title: string;
description: string | null;
startsAt: Date;
endsAt: Date | null;
}): Promise<ChatCoopCalendarEventDomainEntity> {
await this.assertPlaintextManagedRoom(input.matrixRoomId);
return this.events.update({
id: input.id,
matrixRoomId: input.matrixRoomId,
title: input.title,
description: input.description,
startsAt: input.startsAt,
endsAt: input.endsAt,
});
}
async deleteEvent(id: string): Promise<void> {
await this.events.deleteById(id);
}
/**
* Создаёт или обновляет секрет подписки и возвращает полный URL ленты ICS (без JWT).
*/
async createOrRotatePersonalIcsUrl(coopUsername: string): Promise<string> {
const rawSecret = crypto.randomBytes(32).toString('hex');
const hash = sha256Hex(rawSecret);
const sub = await this.icsSubs.rotateSecretForUser(coopUsername, hash);
const base = config.base_url.replace(/\/$/, '');
return `${base}/v1/extensions/chatcoop/calendar/feed.ics?id=${encodeURIComponent(sub.id)}&secret=${encodeURIComponent(rawSecret)}`;
}
async buildIcsDocumentForSubscription(subscriptionId: string, rawSecret: string): Promise<string | null> {
const sub = await this.icsSubs.findById(subscriptionId);
if (!sub) {
return null;
}
if (!timingSafeEqualHex(sub.secretSha256Hex, sha256Hex(rawSecret))) {
return null;
}
const all = await this.events.listAll();
const coopname = config.coopname;
const baseUrl = config.base_url.replace(/\/$/, '');
const lines: string[] = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//Coopenomics//ChatCoop//RU',
'CALSCALE:GREGORIAN',
'METHOD:PUBLISH',
];
const now = new Date();
const stamp = formatIcsUtc(now);
for (const ev of all) {
const room = await this.managedRooms.findByMatrixRoomId(ev.matrixRoomId);
const roomLabel = room?.displayLabel ?? ev.matrixRoomId;
const deepLink = `${baseUrl}/#/${coopname}/chatcoop/chat?matrix_room=${encodeURIComponent(ev.matrixRoomId)}`;
const descParts = [
ev.description ? escapeIcsText(ev.description) : '',
escapeIcsText(`Комната: ${roomLabel}`),
escapeIcsText(`Стол связи: ${deepLink}`),
].filter((p) => p.length > 0);
const description = descParts.join('\\n\\n');
const end = ev.endsAt ?? new Date(ev.startsAt.getTime() + 60 * 60 * 1000);
lines.push('BEGIN:VEVENT');
lines.push(foldIcsLine(`UID:chatcoop-calendar-${ev.id}@${coopname}`));
lines.push(`DTSTAMP:${stamp}`);
lines.push(`DTSTART:${formatIcsUtc(ev.startsAt)}`);
lines.push(`DTEND:${formatIcsUtc(end)}`);
lines.push(`SEQUENCE:${ev.icsSequence}`);
lines.push(foldIcsLine(`SUMMARY:${escapeIcsText(ev.title)}`));
lines.push(foldIcsLine(`DESCRIPTION:${description}`));
lines.push('END:VEVENT');
}
lines.push('END:VCALENDAR');
return `${lines.join('\r\n')}\r\n`;
}
async listEventsForInterPort(
projectHash: string,
window?: InterCalendarEventWindow
): Promise<InterCoopCalendarEventRead[]> {
const w =
window !== undefined
? { from: new Date(window.fromInclusive), to: new Date(window.toExclusive) }
: undefined;
const list = await this.events.listByManagedRoomProjectHashes([projectHash], w);
return list.map((ev) => ({
id: ev.id,
matrixRoomId: ev.matrixRoomId,
projectHash,
title: ev.title,
description: ev.description,
startsAtIso: ev.startsAt.toISOString(),
endsAtIso: ev.endsAt ? ev.endsAt.toISOString() : null,
createdByUsername: ev.createdByUsername,
icsSequence: ev.icsSequence,
}));
}
}
@@ -8,6 +8,8 @@ import { UnionChatService } from './domain/services/union-chat.service';
import { UnionChatTypeormRepository } from './infrastructure/repositories/union-chat.typeorm-repository';
import { UNION_CHAT_REPOSITORY } from './domain/repositories/union-chat.repository';
import { ChatCoopResolver } from './application/resolvers/chatcoop.resolver';
import { ChatCoopCalendarResolver } from './application/resolvers/chatcoop-calendar.resolver';
import { ChatCoopCalendarFeedController } from './application/controllers/chatcoop-calendar-feed.controller';
import { TranscriptionResolver } from './application/resolvers/transcription.resolver';
import { WinstonLoggerService } from '~/application/logger/logger-app.service';
import { ConfigModule } from '@nestjs/config';
@@ -44,6 +46,12 @@ import { ChatCoopSecretaryMatrixTokenService } from './application/services/chat
import { MatrixRoomMessageHistoryCronService } from './application/services/matrix-room-message-history-cron.service';
import { ChatcoopInterProjectCommunicationArtifactsAdapter } from './infrastructure/inter/chatcoop-inter-project-communication-artifacts.adapter';
import { ChatcoopInterMatrixRoomMessagingAdapter } from './infrastructure/inter/chatcoop-inter-matrix-room-messaging.adapter';
import { ChatcoopInterChatCoopCalendarAdapter } from './infrastructure/inter/chatcoop-inter-chatcoop-calendar.adapter';
import { ChatCoopCalendarApplicationService } from './application/services/chatcoop-calendar-application.service';
import { CalendarEventTypeormRepository } from './infrastructure/repositories/calendar-event.typeorm-repository';
import { CalendarIcsSubscriptionTypeormRepository } from './infrastructure/repositories/calendar-ics-subscription.typeorm-repository';
import { CHATCOOP_CALENDAR_EVENT_REPOSITORY } from './domain/repositories/calendar-event.repository';
import { CHATCOOP_CALENDAR_ICS_SUBSCRIPTION_REPOSITORY } from './domain/repositories/calendar-ics-subscription.repository';
import { CHATCOOP_STATE_REPOSITORY } from './domain/repositories/chatcoop-state.repository';
import type { ChatcoopStateRepository } from './domain/repositories/chatcoop-state.repository';
import { ChatcoopStateTypeormRepository } from './infrastructure/repositories/chatcoop-state.typeorm-repository';
@@ -503,8 +511,8 @@ export class ChatCoopPlugin extends BaseExtModule {
AccountInfrastructureModule,
],
controllers: [
// REST контроллер для LiveKit webhook
LiveKitWebhookController,
ChatCoopCalendarFeedController,
],
providers: [
// Plugin
@@ -521,6 +529,8 @@ export class ChatCoopPlugin extends BaseExtModule {
MatrixRoomMessageHistoryCronService,
ChatcoopInterProjectCommunicationArtifactsAdapter,
ChatcoopInterMatrixRoomMessagingAdapter,
ChatcoopInterChatCoopCalendarAdapter,
ChatCoopCalendarApplicationService,
// Repositories
{
@@ -568,9 +578,18 @@ export class ChatCoopPlugin extends BaseExtModule {
provide: ROOM_MESSAGE_HISTORY_REPOSITORY,
useClass: RoomMessageHistoryTypeormRepository,
},
{
provide: CHATCOOP_CALENDAR_EVENT_REPOSITORY,
useClass: CalendarEventTypeormRepository,
},
{
provide: CHATCOOP_CALENDAR_ICS_SUBSCRIPTION_REPOSITORY,
useClass: CalendarIcsSubscriptionTypeormRepository,
},
// GraphQL Resolvers
ChatCoopResolver,
ChatCoopCalendarResolver,
TranscriptionResolver,
],
exports: [
@@ -578,6 +597,7 @@ export class ChatCoopPlugin extends BaseExtModule {
ChatCoopApplicationService,
ChatcoopInterProjectCommunicationArtifactsAdapter,
ChatcoopInterMatrixRoomMessagingAdapter,
ChatcoopInterChatCoopCalendarAdapter,
],
})
export class ChatCoopPluginModule {
@@ -0,0 +1,32 @@
/** Доменная сущность события календаря ChatCoop (кооперативный календарь в незашифрованных Matrix-комнатах). */
export interface ChatCoopCalendarEventDomainEntity {
id: string;
matrixRoomId: string;
title: string;
description: string | null;
startsAt: Date;
endsAt: Date | null;
createdByUsername: string;
/** Для поля SEQUENCE в ICS при обновлениях */
icsSequence: number;
createdAt: Date;
updatedAt: Date;
}
export interface CreateChatCoopCalendarEventDomainInput {
matrixRoomId: string;
title: string;
description: string | null;
startsAt: Date;
endsAt: Date | null;
createdByUsername: string;
}
export interface UpdateChatCoopCalendarEventDomainInput {
id: string;
matrixRoomId: string;
title: string;
description: string | null;
startsAt: Date;
endsAt: Date | null;
}
@@ -0,0 +1,20 @@
import type {
ChatCoopCalendarEventDomainEntity,
CreateChatCoopCalendarEventDomainInput,
UpdateChatCoopCalendarEventDomainInput,
} from '../entities/calendar-event.entity';
export interface ChatCoopCalendarEventRepository {
create(input: CreateChatCoopCalendarEventDomainInput): Promise<ChatCoopCalendarEventDomainEntity>;
update(input: UpdateChatCoopCalendarEventDomainInput): Promise<ChatCoopCalendarEventDomainEntity>;
deleteById(id: string): Promise<void>;
findById(id: string): Promise<ChatCoopCalendarEventDomainEntity | null>;
listAll(): Promise<ChatCoopCalendarEventDomainEntity[]>;
/** События в комнатах с заданным project_hash в реестре управляемых комнат */
listByManagedRoomProjectHashes(
projectHashes: string[],
window?: { from: Date; to: Date }
): Promise<ChatCoopCalendarEventDomainEntity[]>;
}
export const CHATCOOP_CALENDAR_EVENT_REPOSITORY = Symbol('ChatCoopCalendarEventRepository');
@@ -0,0 +1,17 @@
/** Подписка ICS: один секрет на пользователя кооператива (username пайщика). */
export interface ChatCoopCalendarIcsSubscriptionDomainEntity {
id: string;
coopUsername: string;
secretSha256Hex: string;
createdAt: Date;
}
export interface ChatCoopCalendarIcsSubscriptionRepository {
create(coopUsername: string, secretSha256Hex: string): Promise<ChatCoopCalendarIcsSubscriptionDomainEntity>;
findById(id: string): Promise<ChatCoopCalendarIcsSubscriptionDomainEntity | null>;
/** Заменить секрет существующей подписки пользователя (ротация URL). */
rotateSecretForUser(coopUsername: string, secretSha256Hex: string): Promise<ChatCoopCalendarIcsSubscriptionDomainEntity>;
findByUsername(coopUsername: string): Promise<ChatCoopCalendarIcsSubscriptionDomainEntity | null>;
}
export const CHATCOOP_CALENDAR_ICS_SUBSCRIPTION_REPOSITORY = Symbol('ChatCoopCalendarIcsSubscriptionRepository');
@@ -8,6 +8,8 @@ import { TranscriptionSegmentTypeormEntity } from '../entities/transcription-seg
import { ManagedMatrixRoomTypeormEntity } from '../entities/managed-matrix-room.typeorm-entity';
import { RoomMessageHistoryTypeormEntity } from '../entities/room-message-history.typeorm-entity';
import { ChatcoopStateTypeormEntity } from '../entities/chatcoop-state.typeorm-entity';
import { CalendarEventTypeormEntity } from '../entities/calendar-event.typeorm-entity';
import { CalendarIcsSubscriptionTypeormEntity } from '../entities/calendar-ics-subscription.typeorm-entity';
// Все TypeORM-сущности расширения chatcoop
const CHATCOOP_ENTITIES = [
@@ -19,6 +21,8 @@ const CHATCOOP_ENTITIES = [
ManagedMatrixRoomTypeormEntity,
RoomMessageHistoryTypeormEntity,
ChatcoopStateTypeormEntity,
CalendarEventTypeormEntity,
CalendarIcsSubscriptionTypeormEntity,
];
@Module({
@@ -0,0 +1,36 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';
@Entity('chatcoop_calendar_events')
export class CalendarEventTypeormEntity {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Index('IDX_chatcoop_calendar_events_matrix_room')
@Column({ name: 'matrix_room_id', type: 'varchar', length: 255 })
matrixRoomId!: string;
@Column({ type: 'varchar', length: 500 })
title!: string;
@Column({ type: 'text', nullable: true })
description!: string | null;
@Column({ name: 'starts_at', type: 'timestamptz' })
startsAt!: Date;
@Column({ name: 'ends_at', type: 'timestamptz', nullable: true })
endsAt!: Date | null;
@Index('IDX_chatcoop_calendar_events_created_by')
@Column({ name: 'created_by_username', type: 'varchar', length: 255 })
createdByUsername!: string;
@Column({ name: 'ics_sequence', type: 'int', default: 0 })
icsSequence!: number;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt!: Date;
}
@@ -0,0 +1,17 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, Index } from 'typeorm';
@Entity('chatcoop_calendar_ics_subscriptions')
export class CalendarIcsSubscriptionTypeormEntity {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Index({ unique: true })
@Column({ name: 'coop_username', type: 'varchar', length: 255 })
coopUsername!: string;
@Column({ name: 'secret_sha256_hex', type: 'varchar', length: 64 })
secretSha256Hex!: string;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
}
@@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
import type { InterChatCoopCalendarPort, InterCalendarEventWindow } from '@coopenomics/inter';
import { ChatCoopCalendarApplicationService } from '../../application/services/chatcoop-calendar-application.service';
@Injectable()
export class ChatcoopInterChatCoopCalendarAdapter implements InterChatCoopCalendarPort {
constructor(private readonly calendar: ChatCoopCalendarApplicationService) {}
async listEventsByProjectHash(input: {
projectHash: string;
window?: InterCalendarEventWindow;
}) {
return this.calendar.listEventsForInterPort(input.projectHash, input.window);
}
}
@@ -0,0 +1,19 @@
import type { ChatCoopCalendarEventDomainEntity } from '../../domain/entities/calendar-event.entity';
import type { CalendarEventTypeormEntity } from '../entities/calendar-event.typeorm-entity';
export class CalendarEventMapper {
static toDomain(row: CalendarEventTypeormEntity): ChatCoopCalendarEventDomainEntity {
return {
id: row.id,
matrixRoomId: row.matrixRoomId,
title: row.title,
description: row.description,
startsAt: row.startsAt,
endsAt: row.endsAt,
createdByUsername: row.createdByUsername,
icsSequence: row.icsSequence,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
}
@@ -0,0 +1,97 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import type {
ChatCoopCalendarEventRepository,
} from '../../domain/repositories/calendar-event.repository';
import type {
ChatCoopCalendarEventDomainEntity,
CreateChatCoopCalendarEventDomainInput,
UpdateChatCoopCalendarEventDomainInput,
} from '../../domain/entities/calendar-event.entity';
import { CalendarEventTypeormEntity } from '../entities/calendar-event.typeorm-entity';
import { ManagedMatrixRoomTypeormEntity } from '../entities/managed-matrix-room.typeorm-entity';
import { CalendarEventMapper } from '../mappers/calendar-event.mapper';
@Injectable()
export class CalendarEventTypeormRepository implements ChatCoopCalendarEventRepository {
constructor(
@InjectRepository(CalendarEventTypeormEntity)
private readonly repo: Repository<CalendarEventTypeormEntity>
) {}
async create(input: CreateChatCoopCalendarEventDomainInput): Promise<ChatCoopCalendarEventDomainEntity> {
const row = this.repo.create({
matrixRoomId: input.matrixRoomId,
title: input.title,
description: input.description,
startsAt: input.startsAt,
endsAt: input.endsAt,
createdByUsername: input.createdByUsername,
icsSequence: 0,
});
const saved = await this.repo.save(row);
return CalendarEventMapper.toDomain(saved);
}
async update(input: UpdateChatCoopCalendarEventDomainInput): Promise<ChatCoopCalendarEventDomainEntity> {
const existing = await this.repo.findOne({ where: { id: input.id } });
if (!existing) {
throw new Error('Событие календаря не найдено');
}
const nextSeq = existing.icsSequence + 1;
await this.repo.update(
{ id: input.id },
{
matrixRoomId: input.matrixRoomId,
title: input.title,
description: input.description,
startsAt: input.startsAt,
endsAt: input.endsAt,
icsSequence: nextSeq,
}
);
const updated = await this.repo.findOneOrFail({ where: { id: input.id } });
return CalendarEventMapper.toDomain(updated);
}
async deleteById(id: string): Promise<void> {
await this.repo.delete({ id });
}
async findById(id: string): Promise<ChatCoopCalendarEventDomainEntity | null> {
const row = await this.repo.findOne({ where: { id } });
return row ? CalendarEventMapper.toDomain(row) : null;
}
async listAll(): Promise<ChatCoopCalendarEventDomainEntity[]> {
const rows = await this.repo.find({ order: { startsAt: 'ASC' } });
return rows.map(CalendarEventMapper.toDomain);
}
async listByManagedRoomProjectHashes(
projectHashes: string[],
window?: { from: Date; to: Date }
): Promise<ChatCoopCalendarEventDomainEntity[]> {
if (projectHashes.length === 0) {
return [];
}
const qb = this.repo
.createQueryBuilder('e')
.innerJoin(
ManagedMatrixRoomTypeormEntity,
'r',
'r.matrixRoomId = e.matrixRoomId AND r.encrypted = :enc AND r.projectHash IN (:...hashes)',
{ enc: false, hashes: projectHashes }
)
.orderBy('e.startsAt', 'ASC');
if (window) {
qb.andWhere('(e.endsAt IS NULL OR e.endsAt > :from)', { from: window.from });
qb.andWhere('e.startsAt < :to', { to: window.to });
}
const rows = await qb.getMany();
return rows.map(CalendarEventMapper.toDomain);
}
}
@@ -0,0 +1,61 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import type {
ChatCoopCalendarIcsSubscriptionDomainEntity,
ChatCoopCalendarIcsSubscriptionRepository,
} from '../../domain/repositories/calendar-ics-subscription.repository';
import { CalendarIcsSubscriptionTypeormEntity } from '../entities/calendar-ics-subscription.typeorm-entity';
@Injectable()
export class CalendarIcsSubscriptionTypeormRepository implements ChatCoopCalendarIcsSubscriptionRepository {
constructor(
@InjectRepository(CalendarIcsSubscriptionTypeormEntity)
private readonly repo: Repository<CalendarIcsSubscriptionTypeormEntity>
) {}
async create(
coopUsername: string,
secretSha256Hex: string
): Promise<ChatCoopCalendarIcsSubscriptionDomainEntity> {
const row = this.repo.create({
coopUsername: coopUsername.toLowerCase(),
secretSha256Hex,
});
const saved = await this.repo.save(row);
return this.toDomain(saved);
}
async findById(id: string): Promise<ChatCoopCalendarIcsSubscriptionDomainEntity | null> {
const row = await this.repo.findOne({ where: { id } });
return row ? this.toDomain(row) : null;
}
async findByUsername(coopUsername: string): Promise<ChatCoopCalendarIcsSubscriptionDomainEntity | null> {
const row = await this.repo.findOne({ where: { coopUsername: coopUsername.toLowerCase() } });
return row ? this.toDomain(row) : null;
}
async rotateSecretForUser(
coopUsername: string,
secretSha256Hex: string
): Promise<ChatCoopCalendarIcsSubscriptionDomainEntity> {
const u = coopUsername.toLowerCase();
const existing = await this.repo.findOne({ where: { coopUsername: u } });
if (!existing) {
return this.create(coopUsername, secretSha256Hex);
}
await this.repo.update({ id: existing.id }, { secretSha256Hex });
const refreshed = await this.repo.findOneOrFail({ where: { id: existing.id } });
return this.toDomain(refreshed);
}
private toDomain(row: CalendarIcsSubscriptionTypeormEntity): ChatCoopCalendarIcsSubscriptionDomainEntity {
return {
id: row.id,
coopUsername: row.coopUsername,
secretSha256Hex: row.secretSha256Hex,
createdAt: row.createdAt,
};
}
}
@@ -1,8 +1,13 @@
import { Global, Module } from '@nestjs/common';
import { INTER_MATRIX_ROOM_MESSAGING, INTER_PROJECT_COMMUNICATION_ARTIFACTS } from '@coopenomics/inter';
import {
INTER_CHATCOOP_CALENDAR,
INTER_MATRIX_ROOM_MESSAGING,
INTER_PROJECT_COMMUNICATION_ARTIFACTS,
} from '@coopenomics/inter';
import { ChatCoopPluginModule } from './chatcoop/chatcoop-extension.module';
import { ChatcoopInterProjectCommunicationArtifactsAdapter } from './chatcoop/infrastructure/inter/chatcoop-inter-project-communication-artifacts.adapter';
import { ChatcoopInterMatrixRoomMessagingAdapter } from './chatcoop/infrastructure/inter/chatcoop-inter-matrix-room-messaging.adapter';
import { ChatcoopInterChatCoopCalendarAdapter } from './chatcoop/infrastructure/inter/chatcoop-inter-chatcoop-calendar.adapter';
/**
* Глобальная привязка порта @coopenomics/inter к реализации ChatCoop.
@@ -20,7 +25,15 @@ import { ChatcoopInterMatrixRoomMessagingAdapter } from './chatcoop/infrastructu
provide: INTER_MATRIX_ROOM_MESSAGING,
useExisting: ChatcoopInterMatrixRoomMessagingAdapter,
},
{
provide: INTER_CHATCOOP_CALENDAR,
useExisting: ChatcoopInterChatCoopCalendarAdapter,
},
],
exports: [
INTER_PROJECT_COMMUNICATION_ARTIFACTS,
INTER_MATRIX_ROOM_MESSAGING,
INTER_CHATCOOP_CALENDAR,
],
exports: [INTER_PROJECT_COMMUNICATION_ARTIFACTS, INTER_MATRIX_ROOM_MESSAGING],
})
export class InterCommunicationBridgeModule {}
+46 -3
View File
@@ -171,6 +171,7 @@ export const AllTypesProps: Record<string,any> = {
CapitalSegmentFilter:{
status:"SegmentStatus"
},
CapitalStoryContentFormat: "enum" as const,
CapitalStoryFilter:{
status:"StoryStatus"
},
@@ -301,6 +302,14 @@ export const AllTypesProps: Record<string,any> = {
},
CreateMatrixAccountInputDTO:{
},
CreateChatCoopCalendarEventInput:{
endsAt:"DateTime",
startsAt:"DateTime"
},
UpdateChatCoopCalendarEventInput:{
endsAt:"DateTime",
startsAt:"DateTime"
},
CreateOrganizationDataInput:{
bank_account:"BankAccountInput",
@@ -854,6 +863,15 @@ export const AllTypesProps: Record<string,any> = {
chatcoopCreateAccount:{
data:"CreateMatrixAccountInputDTO"
},
chatcoopCreateCalendarEvent:{
data:"CreateChatCoopCalendarEventInput"
},
chatcoopDeleteCalendarEvent:{
},
chatcoopUpdateCalendarEvent:{
data:"UpdateChatCoopCalendarEventInput"
},
completeCapitalOnboardingStep:{
data:"CapitalOnboardingStepInput"
},
@@ -1649,7 +1667,6 @@ export const AllTypesProps: Record<string,any> = {
},
StoryStatus: "enum" as const,
CapitalStoryContentFormat: "enum" as const,
SubmitVoteInput:{
votes:"VoteDistributionInput"
},
@@ -1707,6 +1724,7 @@ export const AllTypesProps: Record<string,any> = {
},
UpdateStoryInput:{
content_format:"CapitalStoryContentFormat",
status:"StoryStatus"
},
UserStatus: "enum" as const,
@@ -2143,8 +2161,8 @@ export const ReturnTypes: Record<string,any> = {
generator_offer_hash:"String",
hours_per_day:"Float",
id:"Int",
is_external_contract:"Boolean",
is_external_blagorost_agreement:"Boolean",
is_external_contract:"Boolean",
last_energy_update:"String",
level:"Int",
main_wallet:"ProgramWallet",
@@ -2568,8 +2586,8 @@ export const ReturnTypes: Record<string,any> = {
_id:"String",
_updated_at:"DateTime",
block_num:"Float",
coopname:"String",
content_format:"CapitalStoryContentFormat",
coopname:"String",
created_by:"String",
description:"String",
issue_hash:"String",
@@ -2993,6 +3011,25 @@ export const ReturnTypes: Record<string,any> = {
iframeUrl:"String",
matrixUsername:"String"
},
ChatCoopCalendarEvent:{
createdAt:"DateTime",
createdByUsername:"String",
description:"String",
endsAt:"DateTime",
icsSequence:"Int",
id:"String",
matrixRoomId:"String",
startsAt:"DateTime",
title:"String",
updatedAt:"DateTime"
},
ChatCoopCalendarIcsUrlResponse:{
icsUrl:"String"
},
ChatCoopCalendarRoomOption:{
displayLabel:"String",
matrixRoomId:"String"
},
Meet:{
authorization:"DocumentAggregate",
close_at:"DateTime",
@@ -3169,6 +3206,10 @@ export const ReturnTypes: Record<string,any> = {
chairmanConfirmApprove:"Approval",
chairmanDeclineApprove:"Approval",
chatcoopCreateAccount:"Boolean",
chatcoopCreateCalendarIcsSubscription:"ChatCoopCalendarIcsUrlResponse",
chatcoopCreateCalendarEvent:"ChatCoopCalendarEvent",
chatcoopDeleteCalendarEvent:"Boolean",
chatcoopUpdateCalendarEvent:"ChatCoopCalendarEvent",
completeCapitalOnboardingStep:"CapitalOnboardingState",
completeChairmanAgendaStep:"ChairmanOnboardingState",
completeChairmanGeneralMeetStep:"ChairmanOnboardingState",
@@ -3672,6 +3713,8 @@ export const ReturnTypes: Record<string,any> = {
chairmanApprovals:"PaginatedChairmanApprovalsPaginationResult",
chatcoopCheckUsernameAvailability:"Boolean",
chatcoopGetAccountStatus:"MatrixAccountStatusResponseDTO",
chatcoopListCalendarEvents:"ChatCoopCalendarEvent",
chatcoopListCalendarRooms:"ChatCoopCalendarRoomOption",
chatcoopGetTranscription:"CallTranscriptionWithSegments",
chatcoopGetTranscriptions:"CallTranscription",
getAccount:"Account",
+258 -39
View File
@@ -2240,6 +2240,7 @@ export type ValueTypes = {
endedAt?:boolean | `@${string}`,
id?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
participants?:boolean | `@${string}`,
roomId?:boolean | `@${string}`,
roomName?:boolean | `@${string}`,
@@ -2480,10 +2481,10 @@ export type ValueTypes = {
hours_per_day?:boolean | `@${string}`,
/** ID в блокчейне */
id?:boolean | `@${string}`,
/** Является ли внешним контрактом */
is_external_contract?:boolean | `@${string}`,
/** Соглашение Благорост предоставлено при импорте (внешний документ) */
is_external_blagorost_agreement?:boolean | `@${string}`,
/** Является ли внешним контрактом */
is_external_contract?:boolean | `@${string}`,
/** Последнее обновление энергии */
last_energy_update?:boolean | `@${string}`,
/** Уровень участника */
@@ -3492,10 +3493,10 @@ export type ValueTypes = {
_updated_at?:boolean | `@${string}`,
/** Номер блока крайней синхронизации с блокчейном */
block_num?:boolean | `@${string}`,
/** Имя аккаунта кооператива */
coopname?:boolean | `@${string}`,
/** Формат содержимого (markdown-текст или BPMN 2.0 XML в description) */
content_format?:boolean | `@${string}`,
/** Имя аккаунта кооператива */
coopname?:boolean | `@${string}`,
/** Имя пользователя, создавшего историю */
created_by?:boolean | `@${string}`,
/** Описание истории */
@@ -3517,6 +3518,8 @@ export type ValueTypes = {
__typename?: boolean | `@${string}`,
['...on CapitalStory']?: Omit<ValueTypes["CapitalStory"], "...on CapitalStory">
}>;
/** Формат содержимого требования (истории) в CAPITAL: MARKDOWN, BPMN (XML), DRAWIO (draw.io / diagrams.net XML) или MERMAID (текст диаграммы) */
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
/** Параметры фильтрации для запросов историй CAPITAL */
["CapitalStoryFilter"]: {
/** Фильтр по названию кооператива */
@@ -4271,6 +4274,21 @@ export type ValueTypes = {
["CreateMatrixAccountInputDTO"]: {
password: string | Variable<any, string>,
username: string | Variable<any, string>
};
["CreateChatCoopCalendarEventInput"]: {
description?: string | undefined | null | Variable<any, string>,
endsAt?: ValueTypes["DateTime"] | undefined | null | Variable<any, string>,
matrixRoomId: string | Variable<any, string>,
startsAt: ValueTypes["DateTime"] | Variable<any, string>,
title: string | Variable<any, string>
};
["UpdateChatCoopCalendarEventInput"]: {
description?: string | undefined | null | Variable<any, string>,
endsAt?: ValueTypes["DateTime"] | undefined | null | Variable<any, string>,
id: string | Variable<any, string>,
matrixRoomId: string | Variable<any, string>,
startsAt: ValueTypes["DateTime"] | Variable<any, string>,
title: string | Variable<any, string>
};
["CreateOrganizationDataInput"]: {
/** Банковский счет организации */
@@ -4406,10 +4424,10 @@ export type ValueTypes = {
phone: string | Variable<any, string>
};
["CreateStoryInput"]: {
/** Имя аккаунта кооператива */
coopname: string | Variable<any, string>,
/** Формат содержимого; по умолчанию MARKDOWN */
content_format?: ValueTypes["CapitalStoryContentFormat"] | undefined | null | Variable<any, string>,
/** Имя аккаунта кооператива */
coopname: string | Variable<any, string>,
/** Описание истории */
description?: string | undefined | null | Variable<any, string>,
/** Хеш задачи (если история привязана к задаче) */
@@ -5724,6 +5742,32 @@ export type ValueTypes = {
matrixUsername?:boolean | `@${string}`,
__typename?: boolean | `@${string}`,
['...on MatrixAccountStatusResponseDTO']?: Omit<ValueTypes["MatrixAccountStatusResponseDTO"], "...on MatrixAccountStatusResponseDTO">
}>;
["ChatCoopCalendarEvent"]: AliasType<{
createdAt?:boolean | `@${string}`,
createdByUsername?:boolean | `@${string}`,
description?:boolean | `@${string}`,
endsAt?:boolean | `@${string}`,
icsSequence?:boolean | `@${string}`,
id?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
startsAt?:boolean | `@${string}`,
title?:boolean | `@${string}`,
updatedAt?:boolean | `@${string}`,
__typename?: boolean | `@${string}`,
['...on ChatCoopCalendarEvent']?: Omit<ValueTypes["ChatCoopCalendarEvent"], "...on ChatCoopCalendarEvent">
}>;
["ChatCoopCalendarIcsUrlResponse"]: AliasType<{
/** Полный URL ленты ICS с секретом в query (без JWT) */
icsUrl?:boolean | `@${string}`,
__typename?: boolean | `@${string}`,
['...on ChatCoopCalendarIcsUrlResponse']?: Omit<ValueTypes["ChatCoopCalendarIcsUrlResponse"], "...on ChatCoopCalendarIcsUrlResponse">
}>;
["ChatCoopCalendarRoomOption"]: AliasType<{
displayLabel?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
__typename?: boolean | `@${string}`,
['...on ChatCoopCalendarRoomOption']?: Omit<ValueTypes["ChatCoopCalendarRoomOption"], "...on ChatCoopCalendarRoomOption">
}>;
/** Данные о собрании кооператива */
["Meet"]: AliasType<{
@@ -6012,6 +6056,11 @@ capitalUpdateStory?: [{ data: ValueTypes["UpdateStoryInput"] | Variable<any, str
chairmanConfirmApprove?: [{ data: ValueTypes["ConfirmApproveInput"] | Variable<any, string>},ValueTypes["Approval"]],
chairmanDeclineApprove?: [{ data: ValueTypes["DeclineApproveInput"] | Variable<any, string>},ValueTypes["Approval"]],
chatcoopCreateAccount?: [{ data: ValueTypes["CreateMatrixAccountInputDTO"] | Variable<any, string>},boolean | `@${string}`],
/** Выдать или обновить персональный URL подписки ICS (секрет в query) */
chatcoopCreateCalendarIcsSubscription?:ValueTypes["ChatCoopCalendarIcsUrlResponse"],
chatcoopCreateCalendarEvent?: [{ data: ValueTypes["CreateChatCoopCalendarEventInput"] | Variable<any, string>},ValueTypes["ChatCoopCalendarEvent"]],
chatcoopDeleteCalendarEvent?: [{ id: string | Variable<any, string>},boolean | `@${string}`],
chatcoopUpdateCalendarEvent?: [{ data: ValueTypes["UpdateChatCoopCalendarEventInput"] | Variable<any, string>},ValueTypes["ChatCoopCalendarEvent"]],
completeCapitalOnboardingStep?: [{ data: ValueTypes["CapitalOnboardingStepInput"] | Variable<any, string>},ValueTypes["CapitalOnboardingState"]],
completeChairmanAgendaStep?: [{ data: ValueTypes["ChairmanOnboardingAgendaInput"] | Variable<any, string>},ValueTypes["ChairmanOnboardingState"]],
completeChairmanGeneralMeetStep?: [{ data: ValueTypes["ChairmanOnboardingGeneralMeetInput"] | Variable<any, string>},ValueTypes["ChairmanOnboardingState"]],
@@ -7182,6 +7231,10 @@ chairmanApprovals?: [{ filter?: ValueTypes["ApprovalFilter"] | undefined | null
chatcoopCheckUsernameAvailability?: [{ data: ValueTypes["CheckMatrixUsernameInput"] | Variable<any, string>},boolean | `@${string}`],
/** Проверить статус Matrix аккаунта пользователя и получить iframe URL */
chatcoopGetAccountStatus?:ValueTypes["MatrixAccountStatusResponseDTO"],
/** Список событий календаря кооператива */
chatcoopListCalendarEvents?:ValueTypes["ChatCoopCalendarEvent"],
/** Незашифрованные комнаты из реестра ChatCoop для привязки события календаря */
chatcoopListCalendarRooms?:ValueTypes["ChatCoopCalendarRoomOption"],
chatcoopGetTranscription?: [{ data: ValueTypes["GetTranscriptionInput"] | Variable<any, string>},ValueTypes["CallTranscriptionWithSegments"]],
chatcoopGetTranscriptions?: [{ data?: ValueTypes["GetTranscriptionsInput"] | undefined | null | Variable<any, string>},ValueTypes["CallTranscription"]],
getAccount?: [{ data: ValueTypes["GetAccountInput"] | Variable<any, string>},ValueTypes["Account"]],
@@ -8144,8 +8197,6 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
};
/** Статус истории в системе CAPITAL */
["StoryStatus"]:StoryStatus;
/** Формат содержимого требования (истории) в CAPITAL */
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
["SubmitVoteInput"]: {
/** Имя аккаунта кооператива */
coopname: string | Variable<any, string>,
@@ -8270,7 +8321,9 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
createdAt?:boolean | `@${string}`,
endOffset?:boolean | `@${string}`,
id?:boolean | `@${string}`,
/** Канонический Matrix user id (@localpart:server) */
speakerIdentity?:boolean | `@${string}`,
/** Отображаемое имя из Synapse (displayname) */
speakerName?:boolean | `@${string}`,
startOffset?:boolean | `@${string}`,
text?:boolean | `@${string}`,
@@ -8462,6 +8515,8 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
provider_name?: string | undefined | null | Variable<any, string>
};
["UpdateStoryInput"]: {
/** Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID) */
content_format?: ValueTypes["CapitalStoryContentFormat"] | undefined | null | Variable<any, string>,
/** Описание истории */
description?: string | undefined | null | Variable<any, string>,
/** Хеш задачи (если история привязана к задаче) */
@@ -9759,6 +9814,7 @@ export type ResolverInputTypes = {
endedAt?:boolean | `@${string}`,
id?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
participants?:boolean | `@${string}`,
roomId?:boolean | `@${string}`,
roomName?:boolean | `@${string}`,
@@ -9992,10 +10048,10 @@ export type ResolverInputTypes = {
hours_per_day?:boolean | `@${string}`,
/** ID в блокчейне */
id?:boolean | `@${string}`,
/** Является ли внешним контрактом */
is_external_contract?:boolean | `@${string}`,
/** Соглашение Благорост предоставлено при импорте (внешний документ) */
is_external_blagorost_agreement?:boolean | `@${string}`,
/** Является ли внешним контрактом */
is_external_contract?:boolean | `@${string}`,
/** Последнее обновление энергии */
last_energy_update?:boolean | `@${string}`,
/** Уровень участника */
@@ -10981,10 +11037,10 @@ export type ResolverInputTypes = {
_updated_at?:boolean | `@${string}`,
/** Номер блока крайней синхронизации с блокчейном */
block_num?:boolean | `@${string}`,
/** Имя аккаунта кооператива */
coopname?:boolean | `@${string}`,
/** Формат содержимого (markdown-текст или BPMN 2.0 XML в description) */
content_format?:boolean | `@${string}`,
/** Имя аккаунта кооператива */
coopname?:boolean | `@${string}`,
/** Имя пользователя, создавшего историю */
created_by?:boolean | `@${string}`,
/** Описание истории */
@@ -11005,6 +11061,8 @@ export type ResolverInputTypes = {
title?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
/** Формат содержимого требования (истории) в CAPITAL: MARKDOWN, BPMN (XML), DRAWIO (draw.io / diagrams.net XML) или MERMAID (текст диаграммы) */
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
/** Параметры фильтрации для запросов историй CAPITAL */
["CapitalStoryFilter"]: {
/** Фильтр по названию кооператива */
@@ -11750,6 +11808,21 @@ export type ResolverInputTypes = {
["CreateMatrixAccountInputDTO"]: {
password: string,
username: string
};
["CreateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: ResolverInputTypes["DateTime"] | undefined | null,
matrixRoomId: string,
startsAt: ResolverInputTypes["DateTime"],
title: string
};
["UpdateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: ResolverInputTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
startsAt: ResolverInputTypes["DateTime"],
title: string
};
["CreateOrganizationDataInput"]: {
/** Банковский счет организации */
@@ -11885,10 +11958,10 @@ export type ResolverInputTypes = {
phone: string
};
["CreateStoryInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого; по умолчанию MARKDOWN */
content_format?: ResolverInputTypes["CapitalStoryContentFormat"] | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -13169,6 +13242,29 @@ export type ResolverInputTypes = {
iframeUrl?:boolean | `@${string}`,
matrixUsername?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["ChatCoopCalendarEvent"]: AliasType<{
createdAt?:boolean | `@${string}`,
createdByUsername?:boolean | `@${string}`,
description?:boolean | `@${string}`,
endsAt?:boolean | `@${string}`,
icsSequence?:boolean | `@${string}`,
id?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
startsAt?:boolean | `@${string}`,
title?:boolean | `@${string}`,
updatedAt?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["ChatCoopCalendarIcsUrlResponse"]: AliasType<{
/** Полный URL ленты ICS с секретом в query (без JWT) */
icsUrl?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["ChatCoopCalendarRoomOption"]: AliasType<{
displayLabel?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
/** Данные о собрании кооператива */
["Meet"]: AliasType<{
@@ -13450,6 +13546,11 @@ capitalUpdateStory?: [{ data: ResolverInputTypes["UpdateStoryInput"]},ResolverIn
chairmanConfirmApprove?: [{ data: ResolverInputTypes["ConfirmApproveInput"]},ResolverInputTypes["Approval"]],
chairmanDeclineApprove?: [{ data: ResolverInputTypes["DeclineApproveInput"]},ResolverInputTypes["Approval"]],
chatcoopCreateAccount?: [{ data: ResolverInputTypes["CreateMatrixAccountInputDTO"]},boolean | `@${string}`],
/** Выдать или обновить персональный URL подписки ICS (секрет в query) */
chatcoopCreateCalendarIcsSubscription?:ResolverInputTypes["ChatCoopCalendarIcsUrlResponse"],
chatcoopCreateCalendarEvent?: [{ data: ResolverInputTypes["CreateChatCoopCalendarEventInput"]},ResolverInputTypes["ChatCoopCalendarEvent"]],
chatcoopDeleteCalendarEvent?: [{ id: string},boolean | `@${string}`],
chatcoopUpdateCalendarEvent?: [{ data: ResolverInputTypes["UpdateChatCoopCalendarEventInput"]},ResolverInputTypes["ChatCoopCalendarEvent"]],
completeCapitalOnboardingStep?: [{ data: ResolverInputTypes["CapitalOnboardingStepInput"]},ResolverInputTypes["CapitalOnboardingState"]],
completeChairmanAgendaStep?: [{ data: ResolverInputTypes["ChairmanOnboardingAgendaInput"]},ResolverInputTypes["ChairmanOnboardingState"]],
completeChairmanGeneralMeetStep?: [{ data: ResolverInputTypes["ChairmanOnboardingGeneralMeetInput"]},ResolverInputTypes["ChairmanOnboardingState"]],
@@ -14571,6 +14672,10 @@ chairmanApprovals?: [{ filter?: ResolverInputTypes["ApprovalFilter"] | undefined
chatcoopCheckUsernameAvailability?: [{ data: ResolverInputTypes["CheckMatrixUsernameInput"]},boolean | `@${string}`],
/** Проверить статус Matrix аккаунта пользователя и получить iframe URL */
chatcoopGetAccountStatus?:ResolverInputTypes["MatrixAccountStatusResponseDTO"],
/** Список событий календаря кооператива */
chatcoopListCalendarEvents?:ResolverInputTypes["ChatCoopCalendarEvent"],
/** Незашифрованные комнаты из реестра ChatCoop для привязки события календаря */
chatcoopListCalendarRooms?:ResolverInputTypes["ChatCoopCalendarRoomOption"],
chatcoopGetTranscription?: [{ data: ResolverInputTypes["GetTranscriptionInput"]},ResolverInputTypes["CallTranscriptionWithSegments"]],
chatcoopGetTranscriptions?: [{ data?: ResolverInputTypes["GetTranscriptionsInput"] | undefined | null},ResolverInputTypes["CallTranscription"]],
getAccount?: [{ data: ResolverInputTypes["GetAccountInput"]},ResolverInputTypes["Account"]],
@@ -15515,8 +15620,6 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
};
/** Статус истории в системе CAPITAL */
["StoryStatus"]:StoryStatus;
/** Формат содержимого требования (истории) в CAPITAL */
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
["SubmitVoteInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
@@ -15634,7 +15737,9 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
createdAt?:boolean | `@${string}`,
endOffset?:boolean | `@${string}`,
id?:boolean | `@${string}`,
/** Канонический Matrix user id (@localpart:server) */
speakerIdentity?:boolean | `@${string}`,
/** Отображаемое имя из Synapse (displayname) */
speakerName?:boolean | `@${string}`,
startOffset?:boolean | `@${string}`,
text?:boolean | `@${string}`,
@@ -15825,6 +15930,8 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
provider_name?: string | undefined | null
};
["UpdateStoryInput"]: {
/** Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID) */
content_format?: ResolverInputTypes["CapitalStoryContentFormat"] | undefined | null,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -17095,6 +17202,7 @@ export type ModelTypes = {
endedAt?: ModelTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
participants: Array<string>,
roomId: string,
roomName: string,
@@ -17321,10 +17429,10 @@ export type ModelTypes = {
hours_per_day?: number | undefined | null,
/** ID в блокчейне */
id?: number | undefined | null,
/** Является ли внешним контрактом */
is_external_contract?: boolean | undefined | null,
/** Соглашение Благорост предоставлено при импорте (внешний документ) */
is_external_blagorost_agreement?: boolean | undefined | null,
/** Является ли внешним контрактом */
is_external_contract?: boolean | undefined | null,
/** Последнее обновление энергии */
last_energy_update?: string | undefined | null,
/** Уровень участника */
@@ -18287,10 +18395,10 @@ export type ModelTypes = {
_updated_at: ModelTypes["DateTime"],
/** Номер блока крайней синхронизации с блокчейном */
block_num?: number | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого (markdown-текст или BPMN 2.0 XML в description) */
content_format: ModelTypes["CapitalStoryContentFormat"],
/** Имя аккаунта кооператива */
coopname: string,
/** Имя пользователя, создавшего историю */
created_by: string,
/** Описание истории */
@@ -18310,6 +18418,7 @@ export type ModelTypes = {
/** Название истории */
title: string
};
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
/** Параметры фильтрации для запросов историй CAPITAL */
["CapitalStoryFilter"]: {
/** Фильтр по названию кооператива */
@@ -19043,6 +19152,21 @@ export type ModelTypes = {
["CreateMatrixAccountInputDTO"]: {
password: string,
username: string
};
["CreateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: ModelTypes["DateTime"] | undefined | null,
matrixRoomId: string,
startsAt: ModelTypes["DateTime"],
title: string
};
["UpdateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: ModelTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
startsAt: ModelTypes["DateTime"],
title: string
};
["CreateOrganizationDataInput"]: {
/** Банковский счет организации */
@@ -19178,10 +19302,10 @@ export type ModelTypes = {
phone: string
};
["CreateStoryInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого; по умолчанию MARKDOWN */
content_format?: ModelTypes["CapitalStoryContentFormat"] | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -20417,6 +20541,26 @@ export type ModelTypes = {
hasAccount: boolean,
iframeUrl?: string | undefined | null,
matrixUsername?: string | undefined | null
};
["ChatCoopCalendarEvent"]: {
createdAt: ModelTypes["DateTime"],
createdByUsername: string,
description?: string | undefined | null,
endsAt?: ModelTypes["DateTime"] | undefined | null,
icsSequence: number,
id: string,
matrixRoomId: string,
startsAt: ModelTypes["DateTime"],
title: string,
updatedAt: ModelTypes["DateTime"]
};
["ChatCoopCalendarIcsUrlResponse"]: {
/** Полный URL ленты ICS с секретом в query (без JWT) */
icsUrl: string
};
["ChatCoopCalendarRoomOption"]: {
displayLabel: string,
matrixRoomId: string
};
/** Данные о собрании кооператива */
["Meet"]: {
@@ -20771,6 +20915,14 @@ export type ModelTypes = {
chairmanDeclineApprove: ModelTypes["Approval"],
/** Создать Matrix аккаунт с именем пользователя и паролем */
chatcoopCreateAccount: boolean,
/** Выдать или обновить персональный URL подписки ICS (секрет в query) */
chatcoopCreateCalendarIcsSubscription: ModelTypes["ChatCoopCalendarIcsUrlResponse"],
/** Создать событие календаря */
chatcoopCreateCalendarEvent: ModelTypes["ChatCoopCalendarEvent"],
/** Удалить событие календаря */
chatcoopDeleteCalendarEvent: boolean,
/** Обновить событие календаря */
chatcoopUpdateCalendarEvent: ModelTypes["ChatCoopCalendarEvent"],
/** Выполнить шаг онбординга capital (создание предложения повестки) */
completeCapitalOnboardingStep: ModelTypes["CapitalOnboardingState"],
/** Выполнить один из шагов онбординга (создание предложения повестки) */
@@ -21952,6 +22104,10 @@ export type ModelTypes = {
chatcoopCheckUsernameAvailability: boolean,
/** Проверить статус Matrix аккаунта пользователя и получить iframe URL */
chatcoopGetAccountStatus: ModelTypes["MatrixAccountStatusResponseDTO"],
/** Список событий календаря кооператива */
chatcoopListCalendarEvents: Array<ModelTypes["ChatCoopCalendarEvent"]>,
/** Незашифрованные комнаты из реестра ChatCoop для привязки события календаря */
chatcoopListCalendarRooms: Array<ModelTypes["ChatCoopCalendarRoomOption"]>,
/** Получить детальную транскрипцию с сегментами */
chatcoopGetTranscription?: ModelTypes["CallTranscriptionWithSegments"] | undefined | null,
/** Получить список транскрипций звонков */
@@ -22902,7 +23058,6 @@ export type ModelTypes = {
project_hash: string
};
["StoryStatus"]:StoryStatus;
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
["SubmitVoteInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
@@ -23012,7 +23167,9 @@ export type ModelTypes = {
createdAt: ModelTypes["DateTime"],
endOffset: number,
id: string,
/** Канонический Matrix user id (@localpart:server) */
speakerIdentity: string,
/** Отображаемое имя из Synapse (displayname) */
speakerName: string,
startOffset: number,
text: string
@@ -23201,6 +23358,8 @@ export type ModelTypes = {
provider_name?: string | undefined | null
};
["UpdateStoryInput"]: {
/** Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID) */
content_format?: ModelTypes["CapitalStoryContentFormat"] | undefined | null,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -24516,6 +24675,7 @@ export type GraphQLTypes = {
endedAt?: GraphQLTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
participants: Array<string>,
roomId: string,
roomName: string,
@@ -24756,10 +24916,10 @@ export type GraphQLTypes = {
hours_per_day?: number | undefined | null,
/** ID в блокчейне */
id?: number | undefined | null,
/** Является ли внешним контрактом */
is_external_contract?: boolean | undefined | null,
/** Соглашение Благорост предоставлено при импорте (внешний документ) */
is_external_blagorost_agreement?: boolean | undefined | null,
/** Является ли внешним контрактом */
is_external_contract?: boolean | undefined | null,
/** Последнее обновление энергии */
last_energy_update?: string | undefined | null,
/** Уровень участника */
@@ -25768,10 +25928,10 @@ export type GraphQLTypes = {
_updated_at: GraphQLTypes["DateTime"],
/** Номер блока крайней синхронизации с блокчейном */
block_num?: number | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого (markdown-текст или BPMN 2.0 XML в description) */
content_format: GraphQLTypes["CapitalStoryContentFormat"],
/** Имя аккаунта кооператива */
coopname: string,
/** Имя пользователя, создавшего историю */
created_by: string,
/** Описание истории */
@@ -25792,6 +25952,8 @@ export type GraphQLTypes = {
title: string,
['...on CapitalStory']: Omit<GraphQLTypes["CapitalStory"], "...on CapitalStory">
};
/** Формат содержимого требования (истории) в CAPITAL: MARKDOWN, BPMN (XML), DRAWIO (draw.io / diagrams.net XML) или MERMAID (текст диаграммы) */
["CapitalStoryContentFormat"]: CapitalStoryContentFormat;
/** Параметры фильтрации для запросов историй CAPITAL */
["CapitalStoryFilter"]: {
/** Фильтр по названию кооператива */
@@ -26546,6 +26708,21 @@ export type GraphQLTypes = {
["CreateMatrixAccountInputDTO"]: {
password: string,
username: string
};
["CreateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: GraphQLTypes["DateTime"] | undefined | null,
matrixRoomId: string,
startsAt: GraphQLTypes["DateTime"],
title: string
};
["UpdateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: GraphQLTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
startsAt: GraphQLTypes["DateTime"],
title: string
};
["CreateOrganizationDataInput"]: {
/** Банковский счет организации */
@@ -26681,10 +26858,10 @@ export type GraphQLTypes = {
phone: string
};
["CreateStoryInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого; по умолчанию MARKDOWN */
content_format?: GraphQLTypes["CapitalStoryContentFormat"] | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -27999,6 +28176,32 @@ export type GraphQLTypes = {
iframeUrl?: string | undefined | null,
matrixUsername?: string | undefined | null,
['...on MatrixAccountStatusResponseDTO']: Omit<GraphQLTypes["MatrixAccountStatusResponseDTO"], "...on MatrixAccountStatusResponseDTO">
};
["ChatCoopCalendarEvent"]: {
__typename: "ChatCoopCalendarEvent",
createdAt: GraphQLTypes["DateTime"],
createdByUsername: string,
description?: string | undefined | null,
endsAt?: GraphQLTypes["DateTime"] | undefined | null,
icsSequence: number,
id: string,
matrixRoomId: string,
startsAt: GraphQLTypes["DateTime"],
title: string,
updatedAt: GraphQLTypes["DateTime"],
['...on ChatCoopCalendarEvent']: Omit<GraphQLTypes["ChatCoopCalendarEvent"], "...on ChatCoopCalendarEvent">
};
["ChatCoopCalendarIcsUrlResponse"]: {
__typename: "ChatCoopCalendarIcsUrlResponse",
/** Полный URL ленты ICS с секретом в query (без JWT) */
icsUrl: string,
['...on ChatCoopCalendarIcsUrlResponse']: Omit<GraphQLTypes["ChatCoopCalendarIcsUrlResponse"], "...on ChatCoopCalendarIcsUrlResponse">
};
["ChatCoopCalendarRoomOption"]: {
__typename: "ChatCoopCalendarRoomOption",
displayLabel: string,
matrixRoomId: string,
['...on ChatCoopCalendarRoomOption']: Omit<GraphQLTypes["ChatCoopCalendarRoomOption"], "...on ChatCoopCalendarRoomOption">
};
/** Данные о собрании кооператива */
["Meet"]: {
@@ -28368,6 +28571,14 @@ export type GraphQLTypes = {
chairmanDeclineApprove: GraphQLTypes["Approval"],
/** Создать Matrix аккаунт с именем пользователя и паролем */
chatcoopCreateAccount: boolean,
/** Выдать или обновить персональный URL подписки ICS (секрет в query) */
chatcoopCreateCalendarIcsSubscription: GraphQLTypes["ChatCoopCalendarIcsUrlResponse"],
/** Создать событие календаря */
chatcoopCreateCalendarEvent: GraphQLTypes["ChatCoopCalendarEvent"],
/** Удалить событие календаря */
chatcoopDeleteCalendarEvent: boolean,
/** Обновить событие календаря */
chatcoopUpdateCalendarEvent: GraphQLTypes["ChatCoopCalendarEvent"],
/** Выполнить шаг онбординга capital (создание предложения повестки) */
completeCapitalOnboardingStep: GraphQLTypes["CapitalOnboardingState"],
/** Выполнить один из шагов онбординга (создание предложения повестки) */
@@ -29668,6 +29879,10 @@ export type GraphQLTypes = {
chatcoopCheckUsernameAvailability: boolean,
/** Проверить статус Matrix аккаунта пользователя и получить iframe URL */
chatcoopGetAccountStatus: GraphQLTypes["MatrixAccountStatusResponseDTO"],
/** Список событий календаря кооператива */
chatcoopListCalendarEvents: Array<GraphQLTypes["ChatCoopCalendarEvent"]>,
/** Незашифрованные комнаты из реестра ChatCoop для привязки события календаря */
chatcoopListCalendarRooms: Array<GraphQLTypes["ChatCoopCalendarRoomOption"]>,
/** Получить детальную транскрипцию с сегментами */
chatcoopGetTranscription?: GraphQLTypes["CallTranscriptionWithSegments"] | undefined | null,
/** Получить список транскрипций звонков */
@@ -30656,8 +30871,6 @@ export type GraphQLTypes = {
};
/** Статус истории в системе CAPITAL */
["StoryStatus"]: StoryStatus;
/** Формат содержимого требования (истории) в CAPITAL */
["CapitalStoryContentFormat"]: CapitalStoryContentFormat;
["SubmitVoteInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
@@ -30783,7 +30996,9 @@ export type GraphQLTypes = {
createdAt: GraphQLTypes["DateTime"],
endOffset: number,
id: string,
/** Канонический Matrix user id (@localpart:server) */
speakerIdentity: string,
/** Отображаемое имя из Synapse (displayname) */
speakerName: string,
startOffset: number,
text: string,
@@ -30974,6 +31189,8 @@ export type GraphQLTypes = {
provider_name?: string | undefined | null
};
["UpdateStoryInput"]: {
/** Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID) */
content_format?: GraphQLTypes["CapitalStoryContentFormat"] | undefined | null,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -31192,6 +31409,13 @@ export enum CapitalOnboardingStep {
generator_offer_template = "generator_offer_template",
generator_program_template = "generator_program_template"
}
/** Формат содержимого требования (истории) в CAPITAL: MARKDOWN, BPMN (XML), DRAWIO (draw.io / diagrams.net XML) или MERMAID (текст диаграммы) */
export enum CapitalStoryContentFormat {
BPMN = "BPMN",
DRAWIO = "DRAWIO",
MARKDOWN = "MARKDOWN",
MERMAID = "MERMAID"
}
export enum ChairmanOnboardingAgendaStep {
participant_application = "participant_application",
privacy_agreement = "privacy_agreement",
@@ -31473,13 +31697,6 @@ export enum StoryStatus {
COMPLETED = "COMPLETED",
PENDING = "PENDING"
}
/** Формат содержимого требования (истории) в CAPITAL */
export enum CapitalStoryContentFormat {
BPMN = "BPMN",
DRAWIO = "DRAWIO",
MARKDOWN = "MARKDOWN",
MERMAID = "MERMAID"
}
/** Состояние контроллера кооператива */
export enum SystemStatus {
active = "active",
@@ -31559,6 +31776,7 @@ type ZEUS_VARIABLES = {
["CapitalOnboardingStepInput"]: ValueTypes["CapitalOnboardingStepInput"];
["CapitalProjectFilter"]: ValueTypes["CapitalProjectFilter"];
["CapitalSegmentFilter"]: ValueTypes["CapitalSegmentFilter"];
["CapitalStoryContentFormat"]: ValueTypes["CapitalStoryContentFormat"];
["CapitalStoryFilter"]: ValueTypes["CapitalStoryFilter"];
["CapitalTimeEntriesFilter"]: ValueTypes["CapitalTimeEntriesFilter"];
["CapitalTimeStatsInput"]: ValueTypes["CapitalTimeStatsInput"];
@@ -31601,6 +31819,8 @@ type ZEUS_VARIABLES = {
["CreateInitialPaymentInput"]: ValueTypes["CreateInitialPaymentInput"];
["CreateIssueInput"]: ValueTypes["CreateIssueInput"];
["CreateMatrixAccountInputDTO"]: ValueTypes["CreateMatrixAccountInputDTO"];
["CreateChatCoopCalendarEventInput"]: ValueTypes["CreateChatCoopCalendarEventInput"];
["UpdateChatCoopCalendarEventInput"]: ValueTypes["UpdateChatCoopCalendarEventInput"];
["CreateOrganizationDataInput"]: ValueTypes["CreateOrganizationDataInput"];
["CreateParentOfferInput"]: ValueTypes["CreateParentOfferInput"];
["CreateProcessTemplateInput"]: ValueTypes["CreateProcessTemplateInput"];
@@ -31794,7 +32014,6 @@ type ZEUS_VARIABLES = {
["StartVotingInput"]: ValueTypes["StartVotingInput"];
["StopProjectInput"]: ValueTypes["StopProjectInput"];
["StoryStatus"]: ValueTypes["StoryStatus"];
["CapitalStoryContentFormat"]: ValueTypes["CapitalStoryContentFormat"];
["SubmitVoteInput"]: ValueTypes["SubmitVoteInput"];
["SupplyOnRequestInput"]: ValueTypes["SupplyOnRequestInput"];
["SystemStatus"]: ValueTypes["SystemStatus"];
@@ -26,6 +26,7 @@ interface IProjectStore {
projects: Ref<IProjectsPagination>;
loadProjects: (data: IGetProjectsInput, append?: boolean) => Promise<IProjectsPagination>;
addProjectToList: (projectData: IProject) => void;
removeProjectFromList: (projectHash: string) => void;
loadProject: (data: IGetProjectInput) => Promise<IGetProjectOutput>;
projectWithRelations: Ref<IProjectWithRelations | null>;
loadProjectWithRelations: (
@@ -91,6 +92,19 @@ export const useProjectStore = defineStore(namespace, (): IProjectStore => {
}
};
const removeProjectFromList = (projectHash: string) => {
const idx = projects.value.items.findIndex(
(p) => p.project_hash === projectHash,
);
if (idx !== -1) {
projects.value.items.splice(idx, 1);
projects.value.totalCount = Math.max(0, projects.value.totalCount - 1);
}
if (projectWithRelations.value?.project_hash === projectHash) {
projectWithRelations.value = null;
}
};
const loadProject = async (data: IGetProjectInput): Promise<IGetProjectOutput> => {
const loadedData = await api.loadProject(data);
if (!loadedData) return;
@@ -172,6 +186,7 @@ export const useProjectStore = defineStore(namespace, (): IProjectStore => {
projectWithRelations,
loadProjects,
addProjectToList,
removeProjectFromList,
loadProject,
loadProjectWithRelations,
loadProjectLogs,
@@ -1 +1,2 @@
export { useDeleteIssue } from './model';
export * from './ui';
@@ -0,0 +1,75 @@
<template lang="pug">
q-btn(
v-if='canDelete'
flat
size="sm"
color='negative'
class='full-width q-mt-md'
:label='label'
@click='showDialog = true'
:loading='isSubmitting'
)
q-dialog(v-model='showDialog', @hide='close')
ModalBase(title='Удаление задачи')
Form.q-pa-sm(
:handler-submit='confirmDelete'
:is-submitting='isSubmitting'
:button-cancel-txt='"Отменить"'
:button-submit-txt='"Удалить"'
@cancel='close'
)
div(style='max-width: 360px')
p Вы уверены, что хотите удалить задачу?
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { FailAlert, SuccessAlert } from 'src/shared/api';
import { ModalBase } from 'src/shared/ui/ModalBase';
import { Form } from 'src/shared/ui/Form';
import { useDeleteIssue } from '../model';
interface Props {
issueHash: string;
projectHash: string;
canDelete?: boolean;
label?: string;
}
const props = withDefaults(defineProps<Props>(), {
canDelete: false,
label: 'Удалить',
});
const emit = defineEmits<{
deleted: [];
close: [];
}>();
const { deleteIssue: deleteIssueAction } = useDeleteIssue();
const isSubmitting = ref(false);
const showDialog = ref(false);
const close = () => {
showDialog.value = false;
emit('close');
};
const confirmDelete = async () => {
isSubmitting.value = true;
try {
await deleteIssueAction(
{ issue_hash: props.issueHash },
props.projectHash,
);
SuccessAlert('Задача удалена');
emit('deleted');
close();
} catch (e: unknown) {
FailAlert(e, 'Возникла ошибка при удалении');
close();
} finally {
isSubmitting.value = false;
}
};
</script>
@@ -0,0 +1 @@
export { default as DeleteIssueButton } from './DeleteIssueButton.vue';
@@ -10,6 +10,7 @@ ContributorSelector(
placeholder=''
class='creators-selector'
label='Исполнители'
style="width: 220px;"
)
</template>
@@ -1,11 +1,14 @@
import { ref, type Ref } from 'vue';
import type { Mutations } from '@coopenomics/sdk';
import { api } from '../api';
import { useProjectStore } from 'app/extensions/capital/entities/Project/model';
export type IDeleteProjectInput =
Mutations.Capital.DeleteProject.IInput['data'];
export function useDeleteProject() {
const projectStore = useProjectStore();
const initialDeleteProjectInput: IDeleteProjectInput = {
coopname: '',
project_hash: '',
@@ -26,6 +29,8 @@ export function useDeleteProject() {
async function deleteProject(data: IDeleteProjectInput) {
const transaction = await api.deleteProject(data);
projectStore.removeProjectFromList(data.project_hash);
// Сбрасываем deleteProjectInput после выполнения deleteProject
resetInput(deleteProjectInput, initialDeleteProjectInput);
@@ -0,0 +1,86 @@
<template lang="pug">
q-btn(
v-if='canDelete'
flat
color='negative'
class='full-width q-mt-md'
label='Удалить'
@click='showDialog = true'
:loading='isSubmitting'
size="sm"
)
q-dialog(v-model='showDialog', @hide='close')
ModalBase(:title='dialogTitle')
Form.q-pa-sm(
:handler-submit='confirmDelete'
:is-submitting='isSubmitting'
:button-cancel-txt='"Отменить"'
:button-submit-txt='"Удалить"'
@cancel='close'
)
div(style='max-width: 360px')
p {{ confirmMessage }}
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { FailAlert, SuccessAlert } from 'src/shared/api';
import { ModalBase } from 'src/shared/ui/ModalBase';
import { Form } from 'src/shared/ui/Form';
import { useDeleteProject } from '../model';
interface Props {
coopname: string;
projectHash: string;
canDelete?: boolean;
/** Подпись сущности в тексте подтверждения (например «проект» или «компонент») */
entityLabel?: string;
}
const props = withDefaults(defineProps<Props>(), {
canDelete: false,
entityLabel: 'проект',
});
const emit = defineEmits<{
deleted: [];
close: [];
}>();
const { deleteProject: deleteProjectAction } = useDeleteProject();
const isSubmitting = ref(false);
const showDialog = ref(false);
const dialogTitle = computed(() => {
const first = props.entityLabel.charAt(0).toUpperCase();
const rest = props.entityLabel.slice(1);
return `Удаление ${first}${rest}`;
});
const confirmMessage = computed(
() => `Вы уверены, что хотите удалить ${props.entityLabel}?`,
);
const close = () => {
showDialog.value = false;
emit('close');
};
const confirmDelete = async () => {
isSubmitting.value = true;
try {
await deleteProjectAction({
coopname: props.coopname,
project_hash: props.projectHash,
});
SuccessAlert('Удалено');
emit('deleted');
close();
} catch (e: unknown) {
FailAlert(e, 'Возникла ошибка при удалении');
close();
} finally {
isSubmitting.value = false;
}
};
</script>
@@ -1 +1,2 @@
export { default as DeleteProjectButton } from './DeleteProjectButton.vue';
export { default as DeleteProjectSidebarButton } from './DeleteProjectSidebarButton.vue';
@@ -1,4 +1,5 @@
import { ref, type Ref } from 'vue';
import { Zeus } from '@coopenomics/sdk';
import {
useStoryStore,
type ICreateStoryInput,
@@ -15,6 +16,7 @@ export function useCreateStory() {
story_hash: '',
coopname: '',
title: '',
content_format: Zeus.CapitalStoryContentFormat.MARKDOWN,
};
const createStoryInput = ref<ICreateStoryInput>({
@@ -86,24 +86,6 @@ q-card.column.no-wrap.edit-req-panel(
:padded='false'
)
q-card-actions.q-pa-md(
v-if='variant === "dialog" || (canEdit && hasChanges)'
align='right'
)
q-btn(
v-if='variant === "dialog" && !hasChanges'
flat
label='Закрыть'
@click='handleClose'
)
template(v-if='canEdit && hasChanges')
q-btn(flat label='Отменить' @click='resetChanges' :disable='isSaving')
q-btn(
label='Сохранить'
color='primary'
@click='handleSave'
:loading='isSaving'
)
</template>
<script setup lang="ts">
@@ -219,6 +201,9 @@ const handleSave = async () => {
story_hash: props.requirement.story_hash,
title: localTitle.value,
description: localDescription.value,
content_format:
props.requirement.content_format ??
Zeus.CapitalStoryContentFormat.MARKDOWN,
};
const updatedRequirement = await updateStory(updateData);
@@ -8,6 +8,7 @@ div.column.full-height
:project="project"
@field-change="handleFieldChange"
@update:title="handleTitleUpdate"
@project-deleted="handleProjectDeleted"
)
// Правая колонка с контентом подстраниц (снизу)
@@ -91,6 +92,7 @@ div.column.full-height
:project="project"
@field-change="handleFieldChange"
@update:title="handleTitleUpdate"
@project-deleted="handleProjectDeleted"
)
template(#after)
@@ -436,6 +438,19 @@ const handleTitleUpdate = (value: string) => {
}
};
const handleProjectDeleted = () => {
const coopname = route.params.coopname as string;
const parentHash = project.value?.parent_hash;
if (parentHash) {
router.push({
name: 'project-description',
params: { coopname, project_hash: parentHash },
});
return;
}
router.push({ name: 'projects-list', params: { coopname } });
};
// Обработчик создания задачи
const handleIssueCreated = () => {
// Можно добавить логику обновления списка задач
@@ -9,6 +9,7 @@ div.column.full-height
:issue="issue"
:permissions="issue.permissions"
:parent-project="parentProject"
:project-hash="projectHash"
@field-change="handleFieldChange"
@update:title="handleTitleUpdate"
@update:status="handleStatusUpdate"
@@ -16,6 +17,7 @@ div.column.full-height
@update:estimate="handleEstimateUpdate"
@creators-set="handleCreatorsSet"
@issue-updated="handleIssueUpdated"
@issue-deleted="handleIssueDeleted"
)
// Правая колонка с контентом задачи (снизу)
@@ -62,6 +64,7 @@ div.column.full-height
:issue="issue"
:permissions="issue.permissions"
:parent-project="parentProject"
:project-hash="projectHash"
@field-change="handleFieldChange"
@update:title="handleTitleUpdate"
@update:status="handleStatusUpdate"
@@ -69,6 +72,7 @@ div.column.full-height
@update:estimate="handleEstimateUpdate"
@creators-set="handleCreatorsSet"
@issue-updated="handleIssueUpdated"
@issue-deleted="handleIssueDeleted"
)
template(#after)
@@ -335,6 +339,17 @@ const handleIssueUpdated = (updatedIssue: any) => {
}
};
const handleIssueDeleted = () => {
const coopname = route.params.coopname as string;
router.push({
name: 'component-tasks',
params: {
coopname,
project_hash: projectHash.value,
},
});
};
// Инициализация
onMounted(async () => {
// Загружаем сохраненную ширину sidebar
@@ -8,6 +8,7 @@ div.column.full-height
:project="project"
@field-change="handleFieldChange"
@update:title="handleTitleUpdate"
@project-deleted="handleProjectDeleted"
)
// Правая колонка с контентом подстраниц (снизу)
@@ -84,6 +85,7 @@ div.column.full-height
:project="project"
@field-change="handleFieldChange"
@update:title="handleTitleUpdate"
@project-deleted="handleProjectDeleted"
)
template(#after)
@@ -427,6 +429,11 @@ const handleTitleUpdate = (value: string) => {
}
};
const handleProjectDeleted = () => {
const coopname = route.params.coopname as string;
router.push({ name: 'projects-list', params: { coopname } });
};
// Регистрируем действия в header
onMounted(async () => {
// Загружаем сохраненную ширину sidebar
@@ -16,7 +16,7 @@ q-card(flat)
:props='props',
@click.prevent.stop='handleCommitClick(props.row.commit_hash)',
)
q-td(style='width: 55px')
q-td(style='width: 35px')
ExpandToggleButton(
:expanded='expanded[props.row.commit_hash]',
@click='handleToggleExpand(props.row.commit_hash)'
@@ -16,12 +16,21 @@ div.q-pa-md
// Элементы управления компонентом
ProjectControls(:project='project').full-width
DeleteProjectSidebarButton(
v-if='project'
:coopname='project.coopname'
:project-hash='project.project_hash'
:can-delete='project.permissions?.can_delete_project ?? false'
entity-label='компонент'
@deleted='emit("project-deleted")'
)
</template>
<script lang="ts" setup>
import type { IProject } from 'app/extensions/capital/entities/Project/model'
import { ProjectTitleEditor, ProjectControls, ComponentToProjectPathWidget } from 'app/extensions/capital/widgets'
import { DeleteProjectSidebarButton } from 'app/extensions/capital/features/Project/DeleteProject'
interface Props {
project: IProject | null | undefined
@@ -32,6 +41,7 @@ defineProps<Props>()
const emit = defineEmits<{
fieldChange: []
'update:title': [value: string]
'project-deleted': []
}>()
// Обработчик изменения полей
@@ -18,8 +18,8 @@ div
)
q-td
.row.items-center(style='padding-left:25px; min-height: 48px')
// Кнопка раскрытия (55px)
.col-auto(style='width: 55px; flex-shrink: 0')
// Кнопка раскрытия (35px)
.col-auto(style='width: 35px; flex-shrink: 0')
ExpandToggleButton(
:expanded='expanded[props.row.project_hash]',
@click='handleToggleComponent(props.row.project_hash)'
@@ -29,7 +29,7 @@ q-card(flat)
style='cursor: pointer'
:class='{ "connection-expired": !isConnectionActive(tableProps.row) }'
)
q-td(style='width: 55px')
q-td(style='width: 35px')
ExpandToggleButton(
:expanded='expanded[tableProps.row.username]',
@click='handleToggleExpand(tableProps.row.username)'
@@ -26,17 +26,27 @@ div.q-pa-md
@issue-updated='handleIssueUpdated'
).q-mb-md.full-width
DeleteIssueButton(
v-if='issue && projectHash'
:issue-hash='issue.issue_hash'
:project-hash='projectHash'
:can-delete='permissions?.can_delete_issue ?? false'
@deleted='emit("issue-deleted")'
)
</template>
<script lang="ts" setup>
import type { IIssue, IIssuePermissions } from 'app/extensions/capital/entities/Issue/model'
import { IssueTitleEditor, IssueControls, ProjectPathWidget } from 'app/extensions/capital/widgets'
import { DeleteIssueButton } from 'app/extensions/capital/features/Issue/DeleteIssue'
interface Props {
issue: IIssue | null | undefined
permissions?: IIssuePermissions | null
parentProject?: any
/** Хеш проекта/компонента-владельца списка задач (для стора и удаления) */
projectHash?: string
}
defineProps<Props>()
@@ -49,6 +59,7 @@ const emit = defineEmits<{
'update:estimate': [value: number]
'creators-set': [creators: any[]]
'issue-updated': [issue: any]
'issue-deleted': []
}>()
// Используем ProjectPathWidget для отображения пути к родительскому элементу
@@ -30,7 +30,6 @@ div
q-tr(:props='props')
q-td
.row.items-center(style='padding-left: 12px; min-height: 48px')
.col-auto(style='width: 55px; flex-shrink: 0')
.col-auto(style='width: 100px; padding-left: 20px; flex-shrink: 0')
q-icon(name='task', size='xs').q-mr-xs
span.list-item-title(@click.stop='handleIssueClick(props.row)') {{ '#' + props.row.id }}
@@ -41,7 +40,7 @@ div
size='xs'
)
.col(style='width: 400px; padding-left: 40px')
.col(style='width: 400px; ')
.list-item-title(
@click.stop='handleIssueClick(props.row)'
style='display: inline-block; vertical-align: top; word-wrap: break-word; white-space: normal'
@@ -92,7 +91,7 @@ div
q-tr(:props='props')
q-td
.row.items-center(style='padding-left: 12px; min-height: 48px')
.col-auto(style='width: 55px; flex-shrink: 0')
.col-auto(style='width: 35px; flex-shrink: 0')
.col-auto(style='width: 100px; padding-left: 20px; flex-shrink: 0')
q-icon(name='task', size='xs').q-mr-xs
span.list-item-title(@click.stop='handleIssueClick(props.row)') {{ '#' + props.row.id }}
@@ -104,7 +103,7 @@ div
:estimation='props.row.estimate'
size='xs'
)
.col(style='width: 400px; padding-left: 40px')
.col(style='width: 400px; ')
.list-item-title(
@click.stop='handleIssueClick(props.row)'
style='display: inline-block; vertical-align: top; word-wrap: break-word; white-space: normal'
@@ -17,7 +17,7 @@ q-card(flat)
@click='handleProjectClick(tableProps.row.project_hash)'
style='cursor: pointer'
)
q-td(style='width: 55px')
q-td(style='width: 35px')
ExpandToggleButton(
:expanded='expanded[tableProps.row.project_hash]',
@click='handleToggleExpand(tableProps.row.project_hash)'
@@ -16,7 +16,7 @@ q-card(flat)
q-tr(
:props='tableProps'
)
q-td(style='width: 55px')
q-td(style='width: 35px')
ExpandToggleButton(
:expanded='expanded[tableProps.row.project_hash]',
@click='handleToggleExpand(tableProps.row.project_hash)'
@@ -11,11 +11,21 @@ div.q-pa-md
// Элементы управления проектом
ProjectControls(:project='project').full-width
DeleteProjectSidebarButton(
v-if='project'
:coopname='project.coopname'
:project-hash='project.project_hash'
:can-delete='project.permissions?.can_delete_project ?? false'
entity-label='проект'
@deleted='emit("project-deleted")'
)
</template>
<script lang="ts" setup>
import type { IProject } from 'app/extensions/capital/entities/Project/model'
import { ProjectTitleEditor, ProjectControls } from 'app/extensions/capital/widgets'
import { DeleteProjectSidebarButton } from 'app/extensions/capital/features/Project/DeleteProject'
interface Props {
project: IProject | null | undefined
@@ -26,6 +36,7 @@ defineProps<Props>()
const emit = defineEmits<{
fieldChange: []
'update:title': [value: string]
'project-deleted': []
}>()
// Обработчик изменения полей
@@ -27,7 +27,7 @@ q-card(flat, style='margin-left: 20px; margin-top: 8px;')
:style='isVotingCompleted ? "cursor: pointer" : ""'
)
q-td(style='width: 55px')
q-td(style='width: 35px')
ExpandToggleButton(
:expanded='expanded[tableProps.row.username]',
:disable='!isResultStatus',
@@ -30,8 +30,8 @@ q-card(flat)
)
q-td
.row.items-center(style='padding-left: 12px; min-height: 48px')
// Кнопка раскрытия (55px)
.col-auto(style='width: 55px; flex-shrink: 0')
// Кнопка раскрытия (35px)
.col-auto(style='width: 35px; flex-shrink: 0')
ExpandToggleButton(
:expanded='expanded[props.row.project_hash]',
@click='handleToggleExpand(props.row.project_hash)'
@@ -23,8 +23,8 @@ div
)
q-td
.row.items-center(style='padding: 12px; min-height: 48px')
// Пустое пространство для выравнивания с проектами/компонентами (55px)
.col-auto(style='width: 55px; flex-shrink: 0')
// Пустое пространство для выравнивания с проектами/компонентами (35px)
.col-auto(style='width: 35px; flex-shrink: 0')
// Иконка типа (кликабельная)
@@ -18,7 +18,7 @@ q-card(flat, style='margin-left: 20px; ')
@click='handleSegmentClick(tableProps.row.username)'
style='cursor: pointer'
)
q-td(style='width: 55px')
q-td(style='width: 35px')
ExpandToggleButton(
:expanded='expanded[tableProps.row.username]',
@click='handleToggleExpand(tableProps.row.username)'
@@ -248,6 +248,7 @@ const handleCreateStory = async () => {
const storyData = {
title: newStoryTitle.value.trim(),
content_format: Zeus.CapitalStoryContentFormat.MARKDOWN,
...props.filter, // Добавляем фильтр (project_hash или issue_hash)
} as ICreateStoryInput;
@@ -336,6 +337,8 @@ const handleStatusChange = async (
const updateData: IUpdateStoryInput = {
story_hash: story.story_hash,
status: newStatus,
content_format:
story.content_format ?? Zeus.CapitalStoryContentFormat.MARKDOWN,
};
// Обновляем историю через API
@@ -17,7 +17,7 @@ q-card(flat, style='margin-left: 20px; margin-top: 8px;')
@click='handleIssueClick(props.row.issue_hash)'
style='cursor: pointer'
)
q-td(style='width: 55px')
q-td(style='width: 35px')
ExpandToggleButton(
:expanded='expanded[props.row.issue_hash]',
@click='handleToggleExpand(props.row.issue_hash)'
@@ -19,7 +19,7 @@ q-card(flat)
@click='handleProjectClick(props.row.project_hash)'
style='cursor: pointer'
)
q-td(style='width: 55px')
q-td(style='width: 35px')
ExpandToggleButton(
:expanded='expanded[props.row.project_hash]',
@click='handleToggleExpand(props.row.project_hash)'
@@ -0,0 +1,61 @@
import { client } from 'src/shared/api/client'
import { Mutations, Queries, Zeus } from '@coopenomics/sdk'
import type { IChatCoopCalendarEvent, IChatCoopCalendarRoomOption } from '../model/types'
type CreateCalendarEventData = Zeus.ModelTypes['CreateChatCoopCalendarEventInput']
type UpdateCalendarEventData = Zeus.ModelTypes['UpdateChatCoopCalendarEventInput']
async function listRooms(): Promise<IChatCoopCalendarRoomOption[]> {
const { [Queries.ChatCoop.ListCalendarRooms.name]: rows } = await client.Query(
Queries.ChatCoop.ListCalendarRooms.query,
)
return rows ?? []
}
async function listEvents(): Promise<IChatCoopCalendarEvent[]> {
const { [Queries.ChatCoop.ListCalendarEvents.name]: rows } = await client.Query(
Queries.ChatCoop.ListCalendarEvents.query,
)
return rows ?? []
}
async function createEvent(data: CreateCalendarEventData): Promise<IChatCoopCalendarEvent> {
const { [Mutations.ChatCoop.CreateCalendarEvent.name]: row } = await client.Mutation(
Mutations.ChatCoop.CreateCalendarEvent.mutation,
{ variables: { data } },
)
return row
}
async function updateEvent(data: UpdateCalendarEventData): Promise<IChatCoopCalendarEvent> {
const { [Mutations.ChatCoop.UpdateCalendarEvent.name]: row } = await client.Mutation(
Mutations.ChatCoop.UpdateCalendarEvent.mutation,
{ variables: { data } },
)
return row
}
async function deleteEvent(id: string): Promise<boolean> {
const { [Mutations.ChatCoop.DeleteCalendarEvent.name]: ok } = await client.Mutation(
Mutations.ChatCoop.DeleteCalendarEvent.mutation,
{ variables: { id } },
)
return ok
}
async function createIcsSubscription(): Promise<string> {
const { [Mutations.ChatCoop.CreateCalendarIcsSubscription.name]: row } = await client.Mutation(
Mutations.ChatCoop.CreateCalendarIcsSubscription.mutation,
{ variables: {} },
)
return row.icsUrl
}
export const api = {
listRooms,
listEvents,
createEvent,
updateEvent,
deleteEvent,
createIcsSubscription,
}
@@ -0,0 +1,2 @@
export * from './api'
export * from './model'
@@ -0,0 +1,2 @@
export * from './types'
export * from './store'
@@ -0,0 +1,53 @@
import { defineStore } from 'pinia'
import { ref, type Ref } from 'vue'
import { api } from '../api'
import type { IChatCoopCalendarEvent, IChatCoopCalendarRoomOption } from './types'
const namespace = 'chatCoopCalendarStore'
interface IChatCoopCalendarStore {
rooms: Ref<IChatCoopCalendarRoomOption[]>
events: Ref<IChatCoopCalendarEvent[]>
isLoading: Ref<boolean>
error: Ref<string | null>
loadAll: () => Promise<void>
clearError: () => void
}
export const useChatCoopCalendarStore = defineStore(
namespace,
(): IChatCoopCalendarStore => {
const rooms = ref<IChatCoopCalendarRoomOption[]>([])
const events = ref<IChatCoopCalendarEvent[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
const loadAll = async (): Promise<void> => {
isLoading.value = true
error.value = null
try {
const [roomRows, eventRows] = await Promise.all([api.listRooms(), api.listEvents()])
rooms.value = roomRows
events.value = eventRows
} catch (err: unknown) {
console.error('ChatCoop calendar load failed:', err)
error.value = 'Не удалось загрузить календарь.'
} finally {
isLoading.value = false
}
}
const clearError = (): void => {
error.value = null
}
return {
rooms,
events,
isLoading,
error,
loadAll,
clearError,
}
},
)
@@ -0,0 +1,4 @@
import type { Zeus } from '@coopenomics/sdk'
export type IChatCoopCalendarEvent = Zeus.ModelTypes['ChatCoopCalendarEvent']
export type IChatCoopCalendarRoomOption = Zeus.ModelTypes['ChatCoopCalendarRoomOption']
@@ -1,2 +1,3 @@
export * from './ChatCoopChat';
export * from './Transcription';
export * as ChatCoopCalendar from './ChatCoopCalendar';
export * as ChatCoopChat from './ChatCoopChat';
export * as Transcription from './Transcription';
@@ -0,0 +1 @@
export { DeleteCalendarEventDialog } from './ui'
@@ -0,0 +1,56 @@
<template lang="pug">
q-dialog(:model-value="modelValue", @update:model-value="onUpdateVisible")
q-card
q-card-section Удалить событие «{{ target?.title }}»?
q-card-actions(align="right")
q-btn(flat, label="Отмена", @click="close")
q-btn(color="negative", label="Удалить", :loading="deleting", @click="confirmDelete")
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { api } from '../../../../entities/ChatCoopCalendar/api'
import { useChatCoopCalendarStore } from '../../../../entities/ChatCoopCalendar/model/store'
import type { IChatCoopCalendarEvent } from '../../../../entities/ChatCoopCalendar/model/types'
const props = defineProps<{
modelValue: boolean
target: IChatCoopCalendarEvent | null
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>()
const calendarStore = useChatCoopCalendarStore()
const deleting = ref(false)
function close(): void {
emit('update:modelValue', false)
}
function onUpdateVisible(open: boolean): void {
emit('update:modelValue', open)
}
watch(
() => props.modelValue,
(open) => {
if (!open) deleting.value = false
},
)
async function confirmDelete(): Promise<void> {
if (!props.target) return
deleting.value = true
try {
await api.deleteEvent(props.target.id)
close()
await calendarStore.loadAll()
} catch (e: unknown) {
console.error(e)
} finally {
deleting.value = false
}
}
</script>
@@ -0,0 +1 @@
export { default as DeleteCalendarEventDialog } from './DeleteCalendarEventDialog.vue'
@@ -0,0 +1 @@
export { SaveCalendarEventDialog } from './ui'
@@ -0,0 +1,124 @@
<template lang="pug">
q-dialog(v-model="dialogOpen", persistent)
q-card(style="min-width: 360px; max-width: 520px")
q-card-section.row.items-center
.text-h6 {{ editingId ? 'Изменить событие' : 'Новое событие' }}
q-space
q-btn(icon="close", flat, round, dense, v-close-popup)
q-separator
q-card-section
q-select(
v-model="form.matrixRoomId",
:options="roomOptions",
option-value="value",
option-label="label",
emit-value,
map-options,
label="Комната Matrix",
outlined,
dense
)
q-input.q-mt-sm(v-model="form.title", label="Заголовок", outlined, dense)
q-input.q-mt-sm(v-model="form.description", label="Описание", type="textarea", outlined, dense, autogrow)
q-input.q-mt-sm(
v-model="form.startsAtLocal",
label="Начало",
type="datetime-local",
outlined,
dense
)
q-input.q-mt-sm(v-model="form.endsAtLocal", label="Окончание (необязательно)", type="datetime-local", outlined, dense)
q-card-actions(align="right")
q-btn(flat, label="Отмена", v-close-popup)
q-btn(color="primary", :label="editingId ? 'Сохранить' : 'Создать'", :loading="saving", @click="submitForm")
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { api } from '../../../../entities/ChatCoopCalendar/api'
import { useChatCoopCalendarStore } from '../../../../entities/ChatCoopCalendar/model/store'
import type { IChatCoopCalendarEvent } from '../../../../entities/ChatCoopCalendar/model/types'
import { parseDatetimeLocalValue, toDatetimeLocalValue } from '../../../../shared/lib/calendarDateFormat'
const calendarStore = useChatCoopCalendarStore()
const dialogOpen = ref(false)
const saving = ref(false)
const editingId = ref<string | null>(null)
const form = ref({
matrixRoomId: '' as string,
title: '',
description: '',
startsAtLocal: '',
endsAtLocal: '',
})
const roomOptions = computed(() =>
calendarStore.rooms.map((r) => ({ label: r.displayLabel, value: r.matrixRoomId })),
)
function resetForm(): void {
editingId.value = null
form.value = {
matrixRoomId: '',
title: '',
description: '',
startsAtLocal: '',
endsAtLocal: '',
}
}
function openCreate(): void {
resetForm()
dialogOpen.value = true
}
function openEdit(row: IChatCoopCalendarEvent): void {
editingId.value = row.id
form.value = {
matrixRoomId: row.matrixRoomId,
title: row.title,
description: row.description ?? '',
startsAtLocal: toDatetimeLocalValue(row.startsAt),
endsAtLocal: row.endsAt ? toDatetimeLocalValue(row.endsAt) : '',
}
dialogOpen.value = true
}
async function submitForm(): Promise<void> {
const starts = parseDatetimeLocalValue(form.value.startsAtLocal)
if (!starts || !form.value.matrixRoomId || !form.value.title.trim()) return
const ends = parseDatetimeLocalValue(form.value.endsAtLocal)
saving.value = true
try {
if (editingId.value) {
await api.updateEvent({
id: editingId.value,
matrixRoomId: form.value.matrixRoomId,
title: form.value.title.trim(),
description: form.value.description.trim() || null,
startsAt: starts,
endsAt: ends,
})
} else {
await api.createEvent({
matrixRoomId: form.value.matrixRoomId,
title: form.value.title.trim(),
description: form.value.description.trim() || null,
startsAt: starts,
endsAt: ends,
})
}
dialogOpen.value = false
await calendarStore.loadAll()
} catch (e: unknown) {
console.error(e)
} finally {
saving.value = false
}
}
defineExpose({ openCreate, openEdit })
</script>
@@ -0,0 +1 @@
export { default as SaveCalendarEventDialog } from './SaveCalendarEventDialog.vue'
@@ -1,5 +1,5 @@
import { markRaw } from 'vue';
import { ChatCoopPage, MobileClientPage, TranscriptionsPage, TranscriptionDetailPage } from './pages';
import { CalendarPage, ChatCoopPage, MobileClientPage, TranscriptionsPage, TranscriptionDetailPage } from './pages';
import { agreementsBase } from 'src/shared/lib/consts/workspaces';
import type { IWorkspaceConfig } from 'src/shared/lib/types/workspace';
@@ -34,6 +34,19 @@ export default async function (): Promise<IWorkspaceConfig[]> {
},
children: [],
},
{
path: 'calendar',
name: 'chatcoop-calendar',
component: markRaw(CalendarPage),
meta: {
title: 'Календарь событий',
icon: 'fa-solid fa-calendar-days',
roles: ['chairman', 'member', 'user'],
agreements: agreementsBase,
requiresAuth: true,
},
children: [],
},
{
path: 'mobile',
name: 'chatcoop-mobile',
@@ -0,0 +1 @@
export { CalendarPage } from './ui'
@@ -0,0 +1,55 @@
<template lang="pug">
q-page(padding)
CalendarErrorBanner
CalendarPageToolbar(:on-refresh="refresh", :on-request-create="onRequestCreate")
SaveCalendarEventDialog(v-if="canManageCalendarEvents", ref="saveDialogRef")
DeleteCalendarEventDialog(
v-if="canManageCalendarEvents",
v-model="deleteDialogOpen",
:target="deleteTarget"
)
CalendarEventsTable(:on-edit="onEdit", :on-delete="onDelete")
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'
import { DeleteCalendarEventDialog } from '../../../features/Calendar/DeleteEvent'
import { SaveCalendarEventDialog } from '../../../features/Calendar/SaveEvent'
import { useChatCoopCalendarStore } from '../../../entities/ChatCoopCalendar/model/store'
import type { IChatCoopCalendarEvent } from '../../../entities/ChatCoopCalendar/model/types'
import { CalendarErrorBanner } from '../../../widgets/CalendarErrorBanner'
import { CalendarEventsTable } from '../../../widgets/CalendarEventsTable'
import { CalendarPageToolbar } from '../../../widgets/CalendarPageToolbar'
import { useCalendarBoardPermissions } from '../../../shared/lib/useCalendarBoardPermissions'
const calendarStore = useChatCoopCalendarStore()
const { canManageCalendarEvents } = useCalendarBoardPermissions()
const saveDialogRef = ref<InstanceType<typeof SaveCalendarEventDialog> | null>(null)
const deleteDialogOpen = ref(false)
const deleteTarget = ref<IChatCoopCalendarEvent | null>(null)
watch(deleteDialogOpen, (open) => {
if (!open) deleteTarget.value = null
})
function refresh(): void {
void calendarStore.loadAll()
}
function onRequestCreate(): void {
saveDialogRef.value?.openCreate()
}
function onEdit(row: IChatCoopCalendarEvent): void {
saveDialogRef.value?.openEdit(row)
}
function onDelete(row: IChatCoopCalendarEvent): void {
deleteTarget.value = row
deleteDialogOpen.value = true
}
onMounted(() => {
void calendarStore.loadAll()
})
</script>
@@ -0,0 +1 @@
export { default as CalendarPage } from './CalendarPage.vue'
@@ -16,7 +16,7 @@ div
// Лоадер пока загружается iframe Matrix клиента
WindowLoader(
v-else-if="chatcoopStore.accountStatus?.iframeUrl && isIframeLoading",
v-else-if="iframeSrc && isIframeLoading",
text="Загрузка клиента..."
)
@@ -33,9 +33,9 @@ div
// Iframe с Matrix клиентом после полной загрузки
iframe(
v-else-if="chatcoopStore.accountStatus?.iframeUrl",
v-else-if="iframeSrc",
v-show="!isIframeLoading",
:src="chatcoopStore.accountStatus.iframeUrl",
:src="iframeSrc",
class="matrix-iframe",
frameborder="0",
width="100%",
@@ -46,21 +46,35 @@ div
</template>
<script lang="ts" setup>
import { ref, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { computed, ref, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { WindowLoader } from 'src/shared/ui/Loader';
import { useChatCoopChatStore } from '../../../entities/ChatCoopChat/model';
import { MatrixRegistration } from '../../../widgets/MatrixRegistration';
import { useWindowSize } from 'src/shared/hooks/useWindowSize';
import { buildMatrixIframeSrc } from '../../../shared/lib/matrixIframeDeepLink';
const chatcoopStore = useChatCoopChatStore();
const router = useRouter();
const route = useRoute();
const { isMobile } = useWindowSize();
const isIframeLoading = ref(true);
let iframeLoadTimeout: number | null = null;
const matrixRoomParam = computed((): string | undefined => {
const q = route.query.matrix_room;
if (Array.isArray(q)) return q[0]?.toString();
return typeof q === 'string' ? q : undefined;
});
const iframeSrc = computed((): string | undefined => {
const base = chatcoopStore.accountStatus?.iframeUrl;
if (!base) return undefined;
return buildMatrixIframeSrc(base, matrixRoomParam.value);
});
function startIframeLoading() {
if (!chatcoopStore.accountStatus?.iframeUrl) return;
if (!iframeSrc.value) return;
isIframeLoading.value = true;
@@ -76,7 +90,7 @@ function startIframeLoading() {
}
// Сбрасываем состояние загрузки iframe при изменении URL
watch(() => chatcoopStore.accountStatus?.iframeUrl, () => {
watch(iframeSrc, () => {
startIframeLoading();
});
@@ -1,3 +1,4 @@
export { CalendarPage } from './CalendarPage';
export { ChatCoopPage } from './ChatCoopPage';
export { MobileClientPage } from './MobileClientPage';
export { TranscriptionsPage } from './TranscriptionsPage';
@@ -0,0 +1,20 @@
export function formatCalendarDateTime(v: Date | string | null | undefined): string {
if (v == null) return '—'
const d = v instanceof Date ? v : new Date(v)
if (Number.isNaN(d.getTime())) return '—'
return d.toLocaleString('ru-RU')
}
export function toDatetimeLocalValue(d: Date | string | null | undefined): string {
if (d == null) return ''
const x = d instanceof Date ? d : new Date(d)
if (Number.isNaN(x.getTime())) return ''
const pad = (n: number) => String(n).padStart(2, '0')
return `${x.getFullYear()}-${pad(x.getMonth() + 1)}-${pad(x.getDate())}T${pad(x.getHours())}:${pad(x.getMinutes())}`
}
export function parseDatetimeLocalValue(s: string): Date | null {
if (!s.trim()) return null
const d = new Date(s)
return Number.isNaN(d.getTime()) ? null : d
}
@@ -0,0 +1,12 @@
/**
* Element Web: открыть комнату через hash-маршрут `#/room/<roomId>`.
* Базовый `iframeUrl` от бэкенда может уже содержать hash — для deep link он заменяется.
*/
export function buildMatrixIframeSrc(baseUrl: string, matrixRoomId: string | undefined): string {
const trimmed = matrixRoomId?.trim()
if (!trimmed) {
return baseUrl
}
const withoutHash = baseUrl.split('#')[0] ?? baseUrl
return `${withoutHash}#/room/${encodeURIComponent(trimmed)}`
}
@@ -0,0 +1,11 @@
import { computed, type ComputedRef } from 'vue'
import { useSessionStore } from 'src/entities/Session'
/** Создание / редактирование / удаление событий календаря — только председатель и член совета (с бэкендом согласовано). */
export function useCalendarBoardPermissions(): {
canManageCalendarEvents: ComputedRef<boolean>
} {
const session = useSessionStore()
const canManageCalendarEvents = computed(() => session.isChairman || session.isMember)
return { canManageCalendarEvents }
}
@@ -0,0 +1 @@
export * from './ui'
@@ -0,0 +1,14 @@
<template lang="pug">
q-banner(v-if="calendarStore.error", class="bg-negative text-white q-mb-md", dense)
template(#avatar)
q-icon(name="fa-solid fa-triangle-exclamation")
| {{ calendarStore.error }}
template(#action)
q-btn(flat, color="white", @click="calendarStore.clearError") OK
</template>
<script lang="ts" setup>
import { useChatCoopCalendarStore } from '../../../entities/ChatCoopCalendar/model/store'
const calendarStore = useChatCoopCalendarStore()
</script>
@@ -0,0 +1 @@
export { default as CalendarErrorBanner } from './CalendarErrorBanner.vue'
@@ -0,0 +1 @@
export * from './ui'
@@ -0,0 +1,77 @@
<template lang="pug">
q-table(
flat,
bordered,
:rows="calendarStore.events",
:columns="columns",
row-key="id",
:loading="calendarStore.isLoading",
no-data-label="Нет событий"
)
template(#body-cell-room="props")
q-td(:props="props") {{ roomLabel(props.row.matrixRoomId) }}
template(#body-cell-starts="props")
q-td(:props="props") {{ formatCalendarDateTime(props.row.startsAt) }}
template(#body-cell-ends="props")
q-td(:props="props") {{ props.row.endsAt ? formatCalendarDateTime(props.row.endsAt) : '—' }}
template(#body-cell-actions="props")
q-td(:props="props")
q-btn(
flat,
dense,
round,
icon="fa-solid fa-comments",
@click="goChat(props.row.matrixRoomId)"
)
q-tooltip Открыть комнату в мессенджере
q-btn(
v-if="canManageCalendarEvents",
flat,
dense,
round,
icon="fa-solid fa-pen",
@click="onEdit(props.row)"
)
q-btn(
v-if="canManageCalendarEvents",
flat,
dense,
round,
color="negative",
icon="fa-solid fa-trash",
@click="onDelete(props.row)"
)
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import { useChatCoopCalendarStore } from '../../../entities/ChatCoopCalendar/model/store'
import type { IChatCoopCalendarEvent } from '../../../entities/ChatCoopCalendar/model/types'
import { formatCalendarDateTime } from '../../../shared/lib/calendarDateFormat'
import { useCalendarBoardPermissions } from '../../../shared/lib/useCalendarBoardPermissions'
defineProps<{
onEdit: (row: IChatCoopCalendarEvent) => void
onDelete: (row: IChatCoopCalendarEvent) => void
}>()
const calendarStore = useChatCoopCalendarStore()
const router = useRouter()
const { canManageCalendarEvents } = useCalendarBoardPermissions()
const columns = [
{ name: 'title', label: 'Событие', field: 'title', align: 'left' as const },
{ name: 'room', label: 'Комната', field: 'matrixRoomId', align: 'left' as const },
{ name: 'starts', label: 'Начало', field: 'startsAt', align: 'left' as const },
{ name: 'ends', label: 'Окончание', field: 'endsAt', align: 'left' as const },
{ name: 'actions', label: '', field: 'id', align: 'right' as const },
]
function roomLabel(matrixRoomId: string): string {
return calendarStore.rooms.find((r) => r.matrixRoomId === matrixRoomId)?.displayLabel ?? matrixRoomId
}
function goChat(matrixRoomId: string): void {
void router.push({ name: 'chatcoop-chat', query: { matrix_room: matrixRoomId } })
}
</script>
@@ -0,0 +1 @@
export { default as CalendarEventsTable } from './CalendarEventsTable.vue'
@@ -0,0 +1 @@
export * from './ui'
@@ -0,0 +1,67 @@
<template lang="pug">
div
.row.items-center.q-mb-md
.text-h6 Календарь событий
q-space
q-btn.q-mr-sm(
color="primary",
outline,
:loading="calendarStore.isLoading",
@click="onRefresh"
)
q-icon(name="fa-solid fa-rotate").q-mr-sm
span Обновить
q-btn.q-mr-sm(
color="secondary",
outline,
:loading="icsLoading",
@click="toggleIcsPanel"
)
q-icon(name="fa-solid fa-link").q-mr-sm
span Установить
q-btn(
v-if="canManageCalendarEvents",
color="primary",
icon="fa-solid fa-plus",
@click="onRequestCreate"
) Новое событие
q-card(v-if="icsPanelOpen && icsUrl", flat, class="q-mb-md")
q-card-section
.text-subtitle2.q-mb-sm Скопируйте ссылку и импортируйте в ваш календарь для синхронизации событий
CopyableInput(:model-value="icsUrl", label="URL", standout)
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { CopyableInput } from 'src/shared/ui/CopyableInput'
import { api } from '../../../entities/ChatCoopCalendar/api'
import { useChatCoopCalendarStore } from '../../../entities/ChatCoopCalendar/model/store'
import { useCalendarBoardPermissions } from '../../../shared/lib/useCalendarBoardPermissions'
defineProps<{
onRefresh: () => void
onRequestCreate: () => void
}>()
const calendarStore = useChatCoopCalendarStore()
const { canManageCalendarEvents } = useCalendarBoardPermissions()
const icsUrl = ref<string | null>(null)
const icsPanelOpen = ref(false)
const icsLoading = ref(false)
async function toggleIcsPanel(): Promise<void> {
if (icsPanelOpen.value) {
icsPanelOpen.value = false
return
}
icsLoading.value = true
try {
icsUrl.value = await api.createIcsSubscription()
icsPanelOpen.value = true
} catch (e: unknown) {
console.error(e)
} finally {
icsLoading.value = false
}
}
</script>
@@ -0,0 +1 @@
export { default as CalendarPageToolbar } from './CalendarPageToolbar.vue'
@@ -0,0 +1,34 @@
/**
* Read-модель события календаря ChatCoop для межмодульного контракта (Capital и др.).
* Без Nest/TypeORM — только plain-типы.
*/
export interface InterCalendarEventWindow {
/** ISO 8601 */
fromInclusive: string;
/** ISO 8601 */
toExclusive: string;
}
export interface InterCoopCalendarEventRead {
id: string;
matrixRoomId: string;
/** Из реестра управляемых комнат; null если комната не в реестре */
projectHash: string | null;
title: string;
description: string | null;
startsAtIso: string;
endsAtIso: string | null;
createdByUsername: string;
icsSequence: number;
}
/**
* Порт: чтение событий календаря по комнатам проекта Capital и опционально по окну времени.
* Реализация — расширение chatcoop; регистрация через InterCommunicationBridgeModule.
*/
export interface InterChatCoopCalendarPort {
listEventsByProjectHash(input: {
projectHash: string;
window?: InterCalendarEventWindow;
}): Promise<InterCoopCalendarEventRead[]>;
}
+11 -1
View File
@@ -1,4 +1,8 @@
export { INTER_MATRIX_ROOM_MESSAGING, INTER_PROJECT_COMMUNICATION_ARTIFACTS } from './tokens';
export {
INTER_CHATCOOP_CALENDAR,
INTER_MATRIX_ROOM_MESSAGING,
INTER_PROJECT_COMMUNICATION_ARTIFACTS,
} from './tokens';
export type {
InterCompletedCallTranscriptionHead,
@@ -14,3 +18,9 @@ export type {
InterMatrixSendTextAndPinInput,
InterMatrixUnpinAndRedactAnnouncementInput,
} from './matrix-room-messaging.port';
export type {
InterCalendarEventWindow,
InterChatCoopCalendarPort,
InterCoopCalendarEventRead,
} from './chatcoop-calendar.port';
+3
View File
@@ -7,3 +7,6 @@ export const INTER_PROJECT_COMMUNICATION_ARTIFACTS = Symbol.for(
/** Matrix: отправка сообщений и закрепления (ChatCoop / MatrixApiService). */
export const INTER_MATRIX_ROOM_MESSAGING = Symbol.for('InterMatrixRoomMessaging');
/** ChatCoop: чтение событий календаря для Capital и др. */
export const INTER_CHATCOOP_CALENDAR = Symbol.for('InterChatCoopCalendar');
@@ -0,0 +1,19 @@
import { chatCoopCalendarEventSelector } from '../../selectors/chatcoop'
import { $, type GraphQLTypes, type InputType, type ModelTypes, Selector } from '../../zeus/index'
export const name = 'chatcoopCreateCalendarEvent'
export const mutation = Selector('Mutation')({
[name]: [{ data: $('data', 'CreateChatCoopCalendarEventInput!') }, chatCoopCalendarEventSelector],
})
export interface IInput {
/**
* @private
*/
[key: string]: unknown
data: ModelTypes['CreateChatCoopCalendarEventInput']
}
export type IOutput = InputType<GraphQLTypes['Mutation'], typeof mutation>
@@ -0,0 +1,17 @@
import { chatCoopCalendarIcsUrlSelector } from '../../selectors/chatcoop'
import { type GraphQLTypes, type InputType, Selector } from '../../zeus/index'
export const name = 'chatcoopCreateCalendarIcsSubscription'
export const mutation = Selector('Mutation')({
[name]: chatCoopCalendarIcsUrlSelector,
})
export interface IInput {
/**
* @private
*/
[key: string]: unknown
}
export type IOutput = InputType<GraphQLTypes['Mutation'], typeof mutation>
@@ -0,0 +1,18 @@
import { $, type GraphQLTypes, type InputType, Selector } from '../../zeus/index'
export const name = 'chatcoopDeleteCalendarEvent'
export const mutation = Selector('Mutation')({
[name]: [{ id: $('id', 'String!') }, true],
})
export interface IInput {
/**
* @private
*/
[key: string]: unknown
id: string
}
export type IOutput = InputType<GraphQLTypes['Mutation'], typeof mutation>
@@ -1 +1,5 @@
export * as CreateAccount from './createAccount'
export * as CreateCalendarEvent from './createCalendarEvent'
export * as CreateCalendarIcsSubscription from './createCalendarIcsSubscription'
export * as DeleteCalendarEvent from './deleteCalendarEvent'
export * as UpdateCalendarEvent from './updateCalendarEvent'
@@ -0,0 +1,19 @@
import { chatCoopCalendarEventSelector } from '../../selectors/chatcoop'
import { $, type GraphQLTypes, type InputType, type ModelTypes, Selector } from '../../zeus/index'
export const name = 'chatcoopUpdateCalendarEvent'
export const mutation = Selector('Mutation')({
[name]: [{ data: $('data', 'UpdateChatCoopCalendarEventInput!') }, chatCoopCalendarEventSelector],
})
export interface IInput {
/**
* @private
*/
[key: string]: unknown
data: ModelTypes['UpdateChatCoopCalendarEventInput']
}
export type IOutput = InputType<GraphQLTypes['Mutation'], typeof mutation>
@@ -2,3 +2,5 @@ export * as CheckUsernameAvailability from './checkUsernameAvailability'
export * as GetAccountStatus from './getAccountStatus'
export * as GetTranscription from './getTranscription'
export * as GetTranscriptions from './getTranscriptions'
export * as ListCalendarEvents from './listCalendarEvents'
export * as ListCalendarRooms from './listCalendarRooms'
@@ -0,0 +1,17 @@
import { chatCoopCalendarEventSelector } from '../../selectors/chatcoop'
import { type GraphQLTypes, type InputType, Selector } from '../../zeus/index'
export const name = 'chatcoopListCalendarEvents'
export const query = Selector('Query')({
[name]: chatCoopCalendarEventSelector,
})
export interface IInput {
/**
* @private
*/
[key: string]: unknown
}
export type IOutput = InputType<GraphQLTypes['Query'], typeof query>
@@ -0,0 +1,17 @@
import { chatCoopCalendarRoomOptionSelector } from '../../selectors/chatcoop'
import { type GraphQLTypes, type InputType, Selector } from '../../zeus/index'
export const name = 'chatcoopListCalendarRooms'
export const query = Selector('Query')({
[name]: chatCoopCalendarRoomOptionSelector,
})
export interface IInput {
/**
* @private
*/
[key: string]: unknown
}
export type IOutput = InputType<GraphQLTypes['Query'], typeof query>
@@ -15,5 +15,50 @@ export type chatcoopAccountStatusModel = ModelTypes['MatrixAccountStatusResponse
export const chatcoopAccountStatusSelector = Selector('MatrixAccountStatusResponseDTO')(rawChatCoopAccountStatusSelector)
export { rawChatCoopAccountStatusSelector }
const rawChatCoopCalendarRoomOptionSelector = {
matrixRoomId: true,
displayLabel: true,
}
const _validateCalendarRoom: MakeAllFieldsRequired<ValueTypes['ChatCoopCalendarRoomOption']> =
rawChatCoopCalendarRoomOptionSelector
export type chatCoopCalendarRoomOptionModel = ModelTypes['ChatCoopCalendarRoomOption']
export const chatCoopCalendarRoomOptionSelector = Selector('ChatCoopCalendarRoomOption')(
rawChatCoopCalendarRoomOptionSelector,
)
const rawChatCoopCalendarEventSelector = {
id: true,
matrixRoomId: true,
title: true,
description: true,
startsAt: true,
endsAt: true,
createdByUsername: true,
icsSequence: true,
createdAt: true,
updatedAt: true,
}
const _validateCalendarEvent: MakeAllFieldsRequired<ValueTypes['ChatCoopCalendarEvent']> =
rawChatCoopCalendarEventSelector
export type chatCoopCalendarEventModel = ModelTypes['ChatCoopCalendarEvent']
export const chatCoopCalendarEventSelector = Selector('ChatCoopCalendarEvent')(rawChatCoopCalendarEventSelector)
const rawChatCoopCalendarIcsUrlSelector = {
icsUrl: true,
}
const _validateCalendarIcs: MakeAllFieldsRequired<ValueTypes['ChatCoopCalendarIcsUrlResponse']> =
rawChatCoopCalendarIcsUrlSelector
export const chatCoopCalendarIcsUrlSelector = Selector('ChatCoopCalendarIcsUrlResponse')(
rawChatCoopCalendarIcsUrlSelector,
)
// Экспорт селекторов для транскрипций
export * from './transcription'
+46 -3
View File
@@ -171,6 +171,7 @@ export const AllTypesProps: Record<string,any> = {
CapitalSegmentFilter:{
status:"SegmentStatus"
},
CapitalStoryContentFormat: "enum" as const,
CapitalStoryFilter:{
status:"StoryStatus"
},
@@ -259,6 +260,10 @@ export const AllTypesProps: Record<string,any> = {
},
CreateBranchInput:{
},
CreateChatCoopCalendarEventInput:{
endsAt:"DateTime",
startsAt:"DateTime"
},
CreateChildOrderInput:{
document:"ReturnByAssetStatementSignedDocumentInput"
@@ -854,6 +859,15 @@ export const AllTypesProps: Record<string,any> = {
chatcoopCreateAccount:{
data:"CreateMatrixAccountInputDTO"
},
chatcoopCreateCalendarEvent:{
data:"CreateChatCoopCalendarEventInput"
},
chatcoopDeleteCalendarEvent:{
},
chatcoopUpdateCalendarEvent:{
data:"UpdateChatCoopCalendarEventInput"
},
completeCapitalOnboardingStep:{
data:"CapitalOnboardingStepInput"
},
@@ -1649,7 +1663,6 @@ export const AllTypesProps: Record<string,any> = {
},
StoryStatus: "enum" as const,
CapitalStoryContentFormat: "enum" as const,
SubmitVoteInput:{
votes:"VoteDistributionInput"
},
@@ -1680,6 +1693,10 @@ export const AllTypesProps: Record<string,any> = {
UpdateBankAccountInput:{
data:"BankAccountInput"
},
UpdateChatCoopCalendarEventInput:{
endsAt:"DateTime",
startsAt:"DateTime"
},
UpdateEntrepreneurDataInput:{
country:"Country",
details:"EntrepreneurDetailsInput"
@@ -1707,6 +1724,7 @@ export const AllTypesProps: Record<string,any> = {
},
UpdateStoryInput:{
content_format:"CapitalStoryContentFormat",
status:"StoryStatus"
},
UserStatus: "enum" as const,
@@ -2142,8 +2160,8 @@ export const ReturnTypes: Record<string,any> = {
generator_offer_hash:"String",
hours_per_day:"Float",
id:"Int",
is_external_contract:"Boolean",
is_external_blagorost_agreement:"Boolean",
is_external_contract:"Boolean",
last_energy_update:"String",
level:"Int",
main_wallet:"ProgramWallet",
@@ -2567,8 +2585,8 @@ export const ReturnTypes: Record<string,any> = {
_id:"String",
_updated_at:"DateTime",
block_num:"Float",
coopname:"String",
content_format:"CapitalStoryContentFormat",
coopname:"String",
created_by:"String",
description:"String",
issue_hash:"String",
@@ -2656,6 +2674,25 @@ export const ReturnTypes: Record<string,any> = {
name:"String",
writeoff:"String"
},
ChatCoopCalendarEvent:{
createdAt:"DateTime",
createdByUsername:"String",
description:"String",
endsAt:"DateTime",
icsSequence:"Int",
id:"String",
matrixRoomId:"String",
startsAt:"DateTime",
title:"String",
updatedAt:"DateTime"
},
ChatCoopCalendarIcsUrlResponse:{
icsUrl:"String"
},
ChatCoopCalendarRoomOption:{
displayLabel:"String",
matrixRoomId:"String"
},
ContactsDTO:{
chairman:"PublicChairman",
details:"OrganizationDetails",
@@ -3168,6 +3205,10 @@ export const ReturnTypes: Record<string,any> = {
chairmanConfirmApprove:"Approval",
chairmanDeclineApprove:"Approval",
chatcoopCreateAccount:"Boolean",
chatcoopCreateCalendarEvent:"ChatCoopCalendarEvent",
chatcoopCreateCalendarIcsSubscription:"ChatCoopCalendarIcsUrlResponse",
chatcoopDeleteCalendarEvent:"Boolean",
chatcoopUpdateCalendarEvent:"ChatCoopCalendarEvent",
completeCapitalOnboardingStep:"CapitalOnboardingState",
completeChairmanAgendaStep:"ChairmanOnboardingState",
completeChairmanGeneralMeetStep:"ChairmanOnboardingState",
@@ -3673,6 +3714,8 @@ export const ReturnTypes: Record<string,any> = {
chatcoopGetAccountStatus:"MatrixAccountStatusResponseDTO",
chatcoopGetTranscription:"CallTranscriptionWithSegments",
chatcoopGetTranscriptions:"CallTranscription",
chatcoopListCalendarEvents:"ChatCoopCalendarEvent",
chatcoopListCalendarRooms:"ChatCoopCalendarRoomOption",
getAccount:"Account",
getAccounts:"AccountsPaginationResult",
getActions:"PaginatedActionsPaginationResult",
+252 -39
View File
@@ -2031,6 +2031,7 @@ export type ValueTypes = {
endedAt?:boolean | `@${string}`,
id?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
participants?:boolean | `@${string}`,
roomId?:boolean | `@${string}`,
roomName?:boolean | `@${string}`,
@@ -2264,10 +2265,10 @@ export type ValueTypes = {
hours_per_day?:boolean | `@${string}`,
/** ID в блокчейне */
id?:boolean | `@${string}`,
/** Является ли внешним контрактом */
is_external_contract?:boolean | `@${string}`,
/** Соглашение Благорост предоставлено при импорте (внешний документ) */
is_external_blagorost_agreement?:boolean | `@${string}`,
/** Является ли внешним контрактом */
is_external_contract?:boolean | `@${string}`,
/** Последнее обновление энергии */
last_energy_update?:boolean | `@${string}`,
/** Уровень участника */
@@ -3253,10 +3254,10 @@ export type ValueTypes = {
_updated_at?:boolean | `@${string}`,
/** Номер блока крайней синхронизации с блокчейном */
block_num?:boolean | `@${string}`,
/** Имя аккаунта кооператива */
coopname?:boolean | `@${string}`,
/** Формат содержимого (markdown-текст или BPMN 2.0 XML в description) */
content_format?:boolean | `@${string}`,
/** Имя аккаунта кооператива */
coopname?:boolean | `@${string}`,
/** Имя пользователя, создавшего историю */
created_by?:boolean | `@${string}`,
/** Описание истории */
@@ -3277,6 +3278,8 @@ export type ValueTypes = {
title?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
/** Формат содержимого требования (истории) в CAPITAL: MARKDOWN, BPMN (XML), DRAWIO (draw.io / diagrams.net XML) или MERMAID (текст диаграммы) */
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
/** Параметры фильтрации для запросов историй CAPITAL */
["CapitalStoryFilter"]: {
/** Фильтр по названию кооператива */
@@ -3467,6 +3470,29 @@ export type ValueTypes = {
/** Списанные средства */
writeoff?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["ChatCoopCalendarEvent"]: AliasType<{
createdAt?:boolean | `@${string}`,
createdByUsername?:boolean | `@${string}`,
description?:boolean | `@${string}`,
endsAt?:boolean | `@${string}`,
icsSequence?:boolean | `@${string}`,
id?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
startsAt?:boolean | `@${string}`,
title?:boolean | `@${string}`,
updatedAt?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["ChatCoopCalendarIcsUrlResponse"]: AliasType<{
/** Полный URL ленты ICS с секретом в query (без JWT) */
icsUrl?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["ChatCoopCalendarRoomOption"]: AliasType<{
displayLabel?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["CheckMatrixUsernameInput"]: {
username: string | Variable<any, string>
@@ -3834,6 +3860,13 @@ export type ValueTypes = {
short_name: string | Variable<any, string>,
/** Имя аккаунта уполномоченного (председателя) кооперативного участка */
trustee: string | Variable<any, string>
};
["CreateChatCoopCalendarEventInput"]: {
description?: string | undefined | null | Variable<any, string>,
endsAt?: ValueTypes["DateTime"] | undefined | null | Variable<any, string>,
matrixRoomId: string | Variable<any, string>,
startsAt: ValueTypes["DateTime"] | Variable<any, string>,
title: string | Variable<any, string>
};
["CreateChildOrderInput"]: {
/** Имя кооператива */
@@ -4157,10 +4190,10 @@ export type ValueTypes = {
phone: string | Variable<any, string>
};
["CreateStoryInput"]: {
/** Имя аккаунта кооператива */
coopname: string | Variable<any, string>,
/** Формат содержимого; по умолчанию MARKDOWN */
content_format?: ValueTypes["CapitalStoryContentFormat"] | undefined | null | Variable<any, string>,
/** Имя аккаунта кооператива */
coopname: string | Variable<any, string>,
/** Описание истории */
description?: string | undefined | null | Variable<any, string>,
/** Хеш задачи (если история привязана к задаче) */
@@ -5722,6 +5755,11 @@ capitalUpdateStory?: [{ data: ValueTypes["UpdateStoryInput"] | Variable<any, str
chairmanConfirmApprove?: [{ data: ValueTypes["ConfirmApproveInput"] | Variable<any, string>},ValueTypes["Approval"]],
chairmanDeclineApprove?: [{ data: ValueTypes["DeclineApproveInput"] | Variable<any, string>},ValueTypes["Approval"]],
chatcoopCreateAccount?: [{ data: ValueTypes["CreateMatrixAccountInputDTO"] | Variable<any, string>},boolean | `@${string}`],
chatcoopCreateCalendarEvent?: [{ data: ValueTypes["CreateChatCoopCalendarEventInput"] | Variable<any, string>},ValueTypes["ChatCoopCalendarEvent"]],
/** Выдать или обновить персональный URL подписки ICS (секрет в query) */
chatcoopCreateCalendarIcsSubscription?:ValueTypes["ChatCoopCalendarIcsUrlResponse"],
chatcoopDeleteCalendarEvent?: [{ id: string | Variable<any, string>},boolean | `@${string}`],
chatcoopUpdateCalendarEvent?: [{ data: ValueTypes["UpdateChatCoopCalendarEventInput"] | Variable<any, string>},ValueTypes["ChatCoopCalendarEvent"]],
completeCapitalOnboardingStep?: [{ data: ValueTypes["CapitalOnboardingStepInput"] | Variable<any, string>},ValueTypes["CapitalOnboardingState"]],
completeChairmanAgendaStep?: [{ data: ValueTypes["ChairmanOnboardingAgendaInput"] | Variable<any, string>},ValueTypes["ChairmanOnboardingState"]],
completeChairmanGeneralMeetStep?: [{ data: ValueTypes["ChairmanOnboardingGeneralMeetInput"] | Variable<any, string>},ValueTypes["ChairmanOnboardingState"]],
@@ -6843,6 +6881,10 @@ chatcoopCheckUsernameAvailability?: [{ data: ValueTypes["CheckMatrixUsernameInpu
chatcoopGetAccountStatus?:ValueTypes["MatrixAccountStatusResponseDTO"],
chatcoopGetTranscription?: [{ data: ValueTypes["GetTranscriptionInput"] | Variable<any, string>},ValueTypes["CallTranscriptionWithSegments"]],
chatcoopGetTranscriptions?: [{ data?: ValueTypes["GetTranscriptionsInput"] | undefined | null | Variable<any, string>},ValueTypes["CallTranscription"]],
/** Список событий календаря кооператива */
chatcoopListCalendarEvents?:ValueTypes["ChatCoopCalendarEvent"],
/** Незашифрованные комнаты из реестра ChatCoop для привязки события календаря */
chatcoopListCalendarRooms?:ValueTypes["ChatCoopCalendarRoomOption"],
getAccount?: [{ data: ValueTypes["GetAccountInput"] | Variable<any, string>},ValueTypes["Account"]],
getAccounts?: [{ data?: ValueTypes["GetAccountsInput"] | undefined | null | Variable<any, string>, options?: ValueTypes["PaginationInput"] | undefined | null | Variable<any, string>},ValueTypes["AccountsPaginationResult"]],
getActions?: [{ filters?: ValueTypes["ActionFiltersInput"] | undefined | null | Variable<any, string>, pagination?: ValueTypes["PaginationInput"] | undefined | null | Variable<any, string>},ValueTypes["PaginatedActionsPaginationResult"]],
@@ -7785,8 +7827,6 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
};
/** Статус истории в системе CAPITAL */
["StoryStatus"]:StoryStatus;
/** Формат содержимого требования (истории) в CAPITAL */
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
["SubmitVoteInput"]: {
/** Имя аккаунта кооператива */
coopname: string | Variable<any, string>,
@@ -7904,7 +7944,9 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
createdAt?:boolean | `@${string}`,
endOffset?:boolean | `@${string}`,
id?:boolean | `@${string}`,
/** Канонический Matrix user id (@localpart:server) */
speakerIdentity?:boolean | `@${string}`,
/** Отображаемое имя из Synapse (displayname) */
speakerName?:boolean | `@${string}`,
startOffset?:boolean | `@${string}`,
text?:boolean | `@${string}`,
@@ -7961,6 +8003,14 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
method_id: string | Variable<any, string>,
/** Имя аккаунта пользователя */
username: string | Variable<any, string>
};
["UpdateChatCoopCalendarEventInput"]: {
description?: string | undefined | null | Variable<any, string>,
endsAt?: ValueTypes["DateTime"] | undefined | null | Variable<any, string>,
id: string | Variable<any, string>,
matrixRoomId: string | Variable<any, string>,
startsAt: ValueTypes["DateTime"] | Variable<any, string>,
title: string | Variable<any, string>
};
["UpdateEntrepreneurDataInput"]: {
/** Дата рождения */
@@ -8095,6 +8145,8 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
provider_name?: string | undefined | null | Variable<any, string>
};
["UpdateStoryInput"]: {
/** Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID) */
content_format?: ValueTypes["CapitalStoryContentFormat"] | undefined | null | Variable<any, string>,
/** Описание истории */
description?: string | undefined | null | Variable<any, string>,
/** Хеш задачи (если история привязана к задаче) */
@@ -9386,6 +9438,7 @@ export type ResolverInputTypes = {
endedAt?:boolean | `@${string}`,
id?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
participants?:boolean | `@${string}`,
roomId?:boolean | `@${string}`,
roomName?:boolean | `@${string}`,
@@ -9619,10 +9672,10 @@ export type ResolverInputTypes = {
hours_per_day?:boolean | `@${string}`,
/** ID в блокчейне */
id?:boolean | `@${string}`,
/** Является ли внешним контрактом */
is_external_contract?:boolean | `@${string}`,
/** Соглашение Благорост предоставлено при импорте (внешний документ) */
is_external_blagorost_agreement?:boolean | `@${string}`,
/** Является ли внешним контрактом */
is_external_contract?:boolean | `@${string}`,
/** Последнее обновление энергии */
last_energy_update?:boolean | `@${string}`,
/** Уровень участника */
@@ -10608,10 +10661,10 @@ export type ResolverInputTypes = {
_updated_at?:boolean | `@${string}`,
/** Номер блока крайней синхронизации с блокчейном */
block_num?:boolean | `@${string}`,
/** Имя аккаунта кооператива */
coopname?:boolean | `@${string}`,
/** Формат содержимого (markdown-текст или BPMN 2.0 XML в description) */
content_format?:boolean | `@${string}`,
/** Имя аккаунта кооператива */
coopname?:boolean | `@${string}`,
/** Имя пользователя, создавшего историю */
created_by?:boolean | `@${string}`,
/** Описание истории */
@@ -10632,6 +10685,8 @@ export type ResolverInputTypes = {
title?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
/** Формат содержимого требования (истории) в CAPITAL: MARKDOWN, BPMN (XML), DRAWIO (draw.io / diagrams.net XML) или MERMAID (текст диаграммы) */
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
/** Параметры фильтрации для запросов историй CAPITAL */
["CapitalStoryFilter"]: {
/** Фильтр по названию кооператива */
@@ -10822,6 +10877,29 @@ export type ResolverInputTypes = {
/** Списанные средства */
writeoff?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["ChatCoopCalendarEvent"]: AliasType<{
createdAt?:boolean | `@${string}`,
createdByUsername?:boolean | `@${string}`,
description?:boolean | `@${string}`,
endsAt?:boolean | `@${string}`,
icsSequence?:boolean | `@${string}`,
id?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
startsAt?:boolean | `@${string}`,
title?:boolean | `@${string}`,
updatedAt?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["ChatCoopCalendarIcsUrlResponse"]: AliasType<{
/** Полный URL ленты ICS с секретом в query (без JWT) */
icsUrl?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["ChatCoopCalendarRoomOption"]: AliasType<{
displayLabel?:boolean | `@${string}`,
matrixRoomId?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["CheckMatrixUsernameInput"]: {
username: string
@@ -11189,6 +11267,13 @@ export type ResolverInputTypes = {
short_name: string,
/** Имя аккаунта уполномоченного (председателя) кооперативного участка */
trustee: string
};
["CreateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: ResolverInputTypes["DateTime"] | undefined | null,
matrixRoomId: string,
startsAt: ResolverInputTypes["DateTime"],
title: string
};
["CreateChildOrderInput"]: {
/** Имя кооператива */
@@ -11512,10 +11597,10 @@ export type ResolverInputTypes = {
phone: string
};
["CreateStoryInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого; по умолчанию MARKDOWN */
content_format?: ResolverInputTypes["CapitalStoryContentFormat"] | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -13077,6 +13162,11 @@ capitalUpdateStory?: [{ data: ResolverInputTypes["UpdateStoryInput"]},ResolverIn
chairmanConfirmApprove?: [{ data: ResolverInputTypes["ConfirmApproveInput"]},ResolverInputTypes["Approval"]],
chairmanDeclineApprove?: [{ data: ResolverInputTypes["DeclineApproveInput"]},ResolverInputTypes["Approval"]],
chatcoopCreateAccount?: [{ data: ResolverInputTypes["CreateMatrixAccountInputDTO"]},boolean | `@${string}`],
chatcoopCreateCalendarEvent?: [{ data: ResolverInputTypes["CreateChatCoopCalendarEventInput"]},ResolverInputTypes["ChatCoopCalendarEvent"]],
/** Выдать или обновить персональный URL подписки ICS (секрет в query) */
chatcoopCreateCalendarIcsSubscription?:ResolverInputTypes["ChatCoopCalendarIcsUrlResponse"],
chatcoopDeleteCalendarEvent?: [{ id: string},boolean | `@${string}`],
chatcoopUpdateCalendarEvent?: [{ data: ResolverInputTypes["UpdateChatCoopCalendarEventInput"]},ResolverInputTypes["ChatCoopCalendarEvent"]],
completeCapitalOnboardingStep?: [{ data: ResolverInputTypes["CapitalOnboardingStepInput"]},ResolverInputTypes["CapitalOnboardingState"]],
completeChairmanAgendaStep?: [{ data: ResolverInputTypes["ChairmanOnboardingAgendaInput"]},ResolverInputTypes["ChairmanOnboardingState"]],
completeChairmanGeneralMeetStep?: [{ data: ResolverInputTypes["ChairmanOnboardingGeneralMeetInput"]},ResolverInputTypes["ChairmanOnboardingState"]],
@@ -14200,6 +14290,10 @@ chatcoopCheckUsernameAvailability?: [{ data: ResolverInputTypes["CheckMatrixUser
chatcoopGetAccountStatus?:ResolverInputTypes["MatrixAccountStatusResponseDTO"],
chatcoopGetTranscription?: [{ data: ResolverInputTypes["GetTranscriptionInput"]},ResolverInputTypes["CallTranscriptionWithSegments"]],
chatcoopGetTranscriptions?: [{ data?: ResolverInputTypes["GetTranscriptionsInput"] | undefined | null},ResolverInputTypes["CallTranscription"]],
/** Список событий календаря кооператива */
chatcoopListCalendarEvents?:ResolverInputTypes["ChatCoopCalendarEvent"],
/** Незашифрованные комнаты из реестра ChatCoop для привязки события календаря */
chatcoopListCalendarRooms?:ResolverInputTypes["ChatCoopCalendarRoomOption"],
getAccount?: [{ data: ResolverInputTypes["GetAccountInput"]},ResolverInputTypes["Account"]],
getAccounts?: [{ data?: ResolverInputTypes["GetAccountsInput"] | undefined | null, options?: ResolverInputTypes["PaginationInput"] | undefined | null},ResolverInputTypes["AccountsPaginationResult"]],
getActions?: [{ filters?: ResolverInputTypes["ActionFiltersInput"] | undefined | null, pagination?: ResolverInputTypes["PaginationInput"] | undefined | null},ResolverInputTypes["PaginatedActionsPaginationResult"]],
@@ -15142,8 +15236,6 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
};
/** Статус истории в системе CAPITAL */
["StoryStatus"]:StoryStatus;
/** Формат содержимого требования (истории) в CAPITAL */
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
["SubmitVoteInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
@@ -15261,7 +15353,9 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
createdAt?:boolean | `@${string}`,
endOffset?:boolean | `@${string}`,
id?:boolean | `@${string}`,
/** Канонический Matrix user id (@localpart:server) */
speakerIdentity?:boolean | `@${string}`,
/** Отображаемое имя из Synapse (displayname) */
speakerName?:boolean | `@${string}`,
startOffset?:boolean | `@${string}`,
text?:boolean | `@${string}`,
@@ -15318,6 +15412,14 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
method_id: string,
/** Имя аккаунта пользователя */
username: string
};
["UpdateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: ResolverInputTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
startsAt: ResolverInputTypes["DateTime"],
title: string
};
["UpdateEntrepreneurDataInput"]: {
/** Дата рождения */
@@ -15452,6 +15554,8 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
provider_name?: string | undefined | null
};
["UpdateStoryInput"]: {
/** Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID) */
content_format?: ResolverInputTypes["CapitalStoryContentFormat"] | undefined | null,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -16721,6 +16825,7 @@ export type ModelTypes = {
endedAt?: ModelTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
participants: Array<string>,
roomId: string,
roomName: string,
@@ -16947,10 +17052,10 @@ export type ModelTypes = {
hours_per_day?: number | undefined | null,
/** ID в блокчейне */
id?: number | undefined | null,
/** Является ли внешним контрактом */
is_external_contract?: boolean | undefined | null,
/** Соглашение Благорост предоставлено при импорте (внешний документ) */
is_external_blagorost_agreement?: boolean | undefined | null,
/** Является ли внешним контрактом */
is_external_contract?: boolean | undefined | null,
/** Последнее обновление энергии */
last_energy_update?: string | undefined | null,
/** Уровень участника */
@@ -17913,10 +18018,10 @@ export type ModelTypes = {
_updated_at: ModelTypes["DateTime"],
/** Номер блока крайней синхронизации с блокчейном */
block_num?: number | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого (markdown-текст или BPMN 2.0 XML в description) */
content_format: ModelTypes["CapitalStoryContentFormat"],
/** Имя аккаунта кооператива */
coopname: string,
/** Имя пользователя, создавшего историю */
created_by: string,
/** Описание истории */
@@ -17936,6 +18041,7 @@ export type ModelTypes = {
/** Название истории */
title: string
};
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
/** Параметры фильтрации для запросов историй CAPITAL */
["CapitalStoryFilter"]: {
/** Фильтр по названию кооператива */
@@ -18120,6 +18226,26 @@ export type ModelTypes = {
name: string,
/** Списанные средства */
writeoff: string
};
["ChatCoopCalendarEvent"]: {
createdAt: ModelTypes["DateTime"],
createdByUsername: string,
description?: string | undefined | null,
endsAt?: ModelTypes["DateTime"] | undefined | null,
icsSequence: number,
id: string,
matrixRoomId: string,
startsAt: ModelTypes["DateTime"],
title: string,
updatedAt: ModelTypes["DateTime"]
};
["ChatCoopCalendarIcsUrlResponse"]: {
/** Полный URL ленты ICS с секретом в query (без JWT) */
icsUrl: string
};
["ChatCoopCalendarRoomOption"]: {
displayLabel: string,
matrixRoomId: string
};
["CheckMatrixUsernameInput"]: {
username: string
@@ -18481,6 +18607,13 @@ export type ModelTypes = {
short_name: string,
/** Имя аккаунта уполномоченного (председателя) кооперативного участка */
trustee: string
};
["CreateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: ModelTypes["DateTime"] | undefined | null,
matrixRoomId: string,
startsAt: ModelTypes["DateTime"],
title: string
};
["CreateChildOrderInput"]: {
/** Имя кооператива */
@@ -18804,10 +18937,10 @@ export type ModelTypes = {
phone: string
};
["CreateStoryInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого; по умолчанию MARKDOWN */
content_format?: ModelTypes["CapitalStoryContentFormat"] | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -20397,6 +20530,14 @@ export type ModelTypes = {
chairmanDeclineApprove: ModelTypes["Approval"],
/** Создать Matrix аккаунт с именем пользователя и паролем */
chatcoopCreateAccount: boolean,
/** Создать событие календаря */
chatcoopCreateCalendarEvent: ModelTypes["ChatCoopCalendarEvent"],
/** Выдать или обновить персональный URL подписки ICS (секрет в query) */
chatcoopCreateCalendarIcsSubscription: ModelTypes["ChatCoopCalendarIcsUrlResponse"],
/** Удалить событие календаря */
chatcoopDeleteCalendarEvent: boolean,
/** Обновить событие календаря */
chatcoopUpdateCalendarEvent: ModelTypes["ChatCoopCalendarEvent"],
/** Выполнить шаг онбординга capital (создание предложения повестки) */
completeCapitalOnboardingStep: ModelTypes["CapitalOnboardingState"],
/** Выполнить один из шагов онбординга (создание предложения повестки) */
@@ -21582,6 +21723,10 @@ export type ModelTypes = {
chatcoopGetTranscription?: ModelTypes["CallTranscriptionWithSegments"] | undefined | null,
/** Получить список транскрипций звонков */
chatcoopGetTranscriptions: Array<ModelTypes["CallTranscription"]>,
/** Список событий календаря кооператива */
chatcoopListCalendarEvents: Array<ModelTypes["ChatCoopCalendarEvent"]>,
/** Незашифрованные комнаты из реестра ChatCoop для привязки события календаря */
chatcoopListCalendarRooms: Array<ModelTypes["ChatCoopCalendarRoomOption"]>,
/** Получить сводную информацию о аккаунте */
getAccount: ModelTypes["Account"],
/** Получить сводную информацию о аккаунтах системы */
@@ -22528,7 +22673,6 @@ export type ModelTypes = {
project_hash: string
};
["StoryStatus"]:StoryStatus;
["CapitalStoryContentFormat"]:CapitalStoryContentFormat;
["SubmitVoteInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
@@ -22638,7 +22782,9 @@ export type ModelTypes = {
createdAt: ModelTypes["DateTime"],
endOffset: number,
id: string,
/** Канонический Matrix user id (@localpart:server) */
speakerIdentity: string,
/** Отображаемое имя из Synapse (displayname) */
speakerName: string,
startOffset: number,
text: string
@@ -22693,6 +22839,14 @@ export type ModelTypes = {
method_id: string,
/** Имя аккаунта пользователя */
username: string
};
["UpdateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: ModelTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
startsAt: ModelTypes["DateTime"],
title: string
};
["UpdateEntrepreneurDataInput"]: {
/** Дата рождения */
@@ -22827,6 +22981,8 @@ export type ModelTypes = {
provider_name?: string | undefined | null
};
["UpdateStoryInput"]: {
/** Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID) */
content_format?: ModelTypes["CapitalStoryContentFormat"] | undefined | null,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -24116,6 +24272,7 @@ export type GraphQLTypes = {
endedAt?: GraphQLTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
participants: Array<string>,
roomId: string,
roomName: string,
@@ -24349,10 +24506,10 @@ export type GraphQLTypes = {
hours_per_day?: number | undefined | null,
/** ID в блокчейне */
id?: number | undefined | null,
/** Является ли внешним контрактом */
is_external_contract?: boolean | undefined | null,
/** Соглашение Благорост предоставлено при импорте (внешний документ) */
is_external_blagorost_agreement?: boolean | undefined | null,
/** Является ли внешним контрактом */
is_external_contract?: boolean | undefined | null,
/** Последнее обновление энергии */
last_energy_update?: string | undefined | null,
/** Уровень участника */
@@ -25338,10 +25495,10 @@ export type GraphQLTypes = {
_updated_at: GraphQLTypes["DateTime"],
/** Номер блока крайней синхронизации с блокчейном */
block_num?: number | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого (markdown-текст или BPMN 2.0 XML в description) */
content_format: GraphQLTypes["CapitalStoryContentFormat"],
/** Имя аккаунта кооператива */
coopname: string,
/** Имя пользователя, создавшего историю */
created_by: string,
/** Описание истории */
@@ -25361,6 +25518,8 @@ export type GraphQLTypes = {
/** Название истории */
title: string
};
/** Формат содержимого требования (истории) в CAPITAL: MARKDOWN, BPMN (XML), DRAWIO (draw.io / diagrams.net XML) или MERMAID (текст диаграммы) */
["CapitalStoryContentFormat"]: CapitalStoryContentFormat;
/** Параметры фильтрации для запросов историй CAPITAL */
["CapitalStoryFilter"]: {
/** Фильтр по названию кооператива */
@@ -25551,6 +25710,29 @@ export type GraphQLTypes = {
name: string,
/** Списанные средства */
writeoff: string
};
["ChatCoopCalendarEvent"]: {
__typename: "ChatCoopCalendarEvent",
createdAt: GraphQLTypes["DateTime"],
createdByUsername: string,
description?: string | undefined | null,
endsAt?: GraphQLTypes["DateTime"] | undefined | null,
icsSequence: number,
id: string,
matrixRoomId: string,
startsAt: GraphQLTypes["DateTime"],
title: string,
updatedAt: GraphQLTypes["DateTime"]
};
["ChatCoopCalendarIcsUrlResponse"]: {
__typename: "ChatCoopCalendarIcsUrlResponse",
/** Полный URL ленты ICS с секретом в query (без JWT) */
icsUrl: string
};
["ChatCoopCalendarRoomOption"]: {
__typename: "ChatCoopCalendarRoomOption",
displayLabel: string,
matrixRoomId: string
};
["CheckMatrixUsernameInput"]: {
username: string
@@ -25918,6 +26100,13 @@ export type GraphQLTypes = {
short_name: string,
/** Имя аккаунта уполномоченного (председателя) кооперативного участка */
trustee: string
};
["CreateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: GraphQLTypes["DateTime"] | undefined | null,
matrixRoomId: string,
startsAt: GraphQLTypes["DateTime"],
title: string
};
["CreateChildOrderInput"]: {
/** Имя кооператива */
@@ -26241,10 +26430,10 @@ export type GraphQLTypes = {
phone: string
};
["CreateStoryInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
/** Формат содержимого; по умолчанию MARKDOWN */
content_format?: GraphQLTypes["CapitalStoryContentFormat"] | undefined | null,
/** Имя аккаунта кооператива */
coopname: string,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -27887,6 +28076,14 @@ export type GraphQLTypes = {
chairmanDeclineApprove: GraphQLTypes["Approval"],
/** Создать Matrix аккаунт с именем пользователя и паролем */
chatcoopCreateAccount: boolean,
/** Создать событие календаря */
chatcoopCreateCalendarEvent: GraphQLTypes["ChatCoopCalendarEvent"],
/** Выдать или обновить персональный URL подписки ICS (секрет в query) */
chatcoopCreateCalendarIcsSubscription: GraphQLTypes["ChatCoopCalendarIcsUrlResponse"],
/** Удалить событие календаря */
chatcoopDeleteCalendarEvent: boolean,
/** Обновить событие календаря */
chatcoopUpdateCalendarEvent: GraphQLTypes["ChatCoopCalendarEvent"],
/** Выполнить шаг онбординга capital (создание предложения повестки) */
completeCapitalOnboardingStep: GraphQLTypes["CapitalOnboardingState"],
/** Выполнить один из шагов онбординга (создание предложения повестки) */
@@ -29140,6 +29337,10 @@ export type GraphQLTypes = {
chatcoopGetTranscription?: GraphQLTypes["CallTranscriptionWithSegments"] | undefined | null,
/** Получить список транскрипций звонков */
chatcoopGetTranscriptions: Array<GraphQLTypes["CallTranscription"]>,
/** Список событий календаря кооператива */
chatcoopListCalendarEvents: Array<GraphQLTypes["ChatCoopCalendarEvent"]>,
/** Незашифрованные комнаты из реестра ChatCoop для привязки события календаря */
chatcoopListCalendarRooms: Array<GraphQLTypes["ChatCoopCalendarRoomOption"]>,
/** Получить сводную информацию о аккаунте */
getAccount: GraphQLTypes["Account"],
/** Получить сводную информацию о аккаунтах системы */
@@ -30106,8 +30307,6 @@ export type GraphQLTypes = {
};
/** Статус истории в системе CAPITAL */
["StoryStatus"]: StoryStatus;
/** Формат содержимого требования (истории) в CAPITAL */
["CapitalStoryContentFormat"]: CapitalStoryContentFormat;
["SubmitVoteInput"]: {
/** Имя аккаунта кооператива */
coopname: string,
@@ -30226,7 +30425,9 @@ export type GraphQLTypes = {
createdAt: GraphQLTypes["DateTime"],
endOffset: number,
id: string,
/** Канонический Matrix user id (@localpart:server) */
speakerIdentity: string,
/** Отображаемое имя из Synapse (displayname) */
speakerName: string,
startOffset: number,
text: string
@@ -30282,6 +30483,14 @@ export type GraphQLTypes = {
method_id: string,
/** Имя аккаунта пользователя */
username: string
};
["UpdateChatCoopCalendarEventInput"]: {
description?: string | undefined | null,
endsAt?: GraphQLTypes["DateTime"] | undefined | null,
id: string,
matrixRoomId: string,
startsAt: GraphQLTypes["DateTime"],
title: string
};
["UpdateEntrepreneurDataInput"]: {
/** Дата рождения */
@@ -30416,6 +30625,8 @@ export type GraphQLTypes = {
provider_name?: string | undefined | null
};
["UpdateStoryInput"]: {
/** Формат тела требования (MARKDOWN, BPMN, DRAWIO, MERMAID) */
content_format?: GraphQLTypes["CapitalStoryContentFormat"] | undefined | null,
/** Описание истории */
description?: string | undefined | null,
/** Хеш задачи (если история привязана к задаче) */
@@ -30628,6 +30839,13 @@ export enum CapitalOnboardingStep {
generator_offer_template = "generator_offer_template",
generator_program_template = "generator_program_template"
}
/** Формат содержимого требования (истории) в CAPITAL: MARKDOWN, BPMN (XML), DRAWIO (draw.io / diagrams.net XML) или MERMAID (текст диаграммы) */
export enum CapitalStoryContentFormat {
BPMN = "BPMN",
DRAWIO = "DRAWIO",
MARKDOWN = "MARKDOWN",
MERMAID = "MERMAID"
}
export enum ChairmanOnboardingAgendaStep {
participant_application = "participant_application",
privacy_agreement = "privacy_agreement",
@@ -30909,13 +31127,6 @@ export enum StoryStatus {
COMPLETED = "COMPLETED",
PENDING = "PENDING"
}
/** Формат содержимого требования (истории) в CAPITAL */
export enum CapitalStoryContentFormat {
BPMN = "BPMN",
DRAWIO = "DRAWIO",
MARKDOWN = "MARKDOWN",
MERMAID = "MERMAID"
}
/** Состояние контроллера кооператива */
export enum SystemStatus {
active = "active",
@@ -30995,6 +31206,7 @@ type ZEUS_VARIABLES = {
["CapitalOnboardingStepInput"]: ValueTypes["CapitalOnboardingStepInput"];
["CapitalProjectFilter"]: ValueTypes["CapitalProjectFilter"];
["CapitalSegmentFilter"]: ValueTypes["CapitalSegmentFilter"];
["CapitalStoryContentFormat"]: ValueTypes["CapitalStoryContentFormat"];
["CapitalStoryFilter"]: ValueTypes["CapitalStoryFilter"];
["CapitalTimeEntriesFilter"]: ValueTypes["CapitalTimeEntriesFilter"];
["CapitalTimeStatsInput"]: ValueTypes["CapitalTimeStatsInput"];
@@ -31025,6 +31237,7 @@ type ZEUS_VARIABLES = {
["Country"]: ValueTypes["Country"];
["CreateAnnualGeneralMeetInput"]: ValueTypes["CreateAnnualGeneralMeetInput"];
["CreateBranchInput"]: ValueTypes["CreateBranchInput"];
["CreateChatCoopCalendarEventInput"]: ValueTypes["CreateChatCoopCalendarEventInput"];
["CreateChildOrderInput"]: ValueTypes["CreateChildOrderInput"];
["CreateCommitInput"]: ValueTypes["CreateCommitInput"];
["CreateCycleInput"]: ValueTypes["CreateCycleInput"];
@@ -31230,7 +31443,6 @@ type ZEUS_VARIABLES = {
["StartVotingInput"]: ValueTypes["StartVotingInput"];
["StopProjectInput"]: ValueTypes["StopProjectInput"];
["StoryStatus"]: ValueTypes["StoryStatus"];
["CapitalStoryContentFormat"]: ValueTypes["CapitalStoryContentFormat"];
["SubmitVoteInput"]: ValueTypes["SubmitVoteInput"];
["SupplyOnRequestInput"]: ValueTypes["SupplyOnRequestInput"];
["SystemStatus"]: ValueTypes["SystemStatus"];
@@ -31241,6 +31453,7 @@ type ZEUS_VARIABLES = {
["Update"]: ValueTypes["Update"];
["UpdateAccountInput"]: ValueTypes["UpdateAccountInput"];
["UpdateBankAccountInput"]: ValueTypes["UpdateBankAccountInput"];
["UpdateChatCoopCalendarEventInput"]: ValueTypes["UpdateChatCoopCalendarEventInput"];
["UpdateEntrepreneurDataInput"]: ValueTypes["UpdateEntrepreneurDataInput"];
["UpdateIndividualDataInput"]: ValueTypes["UpdateIndividualDataInput"];
["UpdateIssueInput"]: ValueTypes["UpdateIssueInput"];