fix publication

This commit is contained in:
peter.maquiran
2025-09-17 18:37:35 +01:00
parent 5f7295d41c
commit 3eecedb23f
64 changed files with 1043 additions and 873 deletions
+170
View File
@@ -0,0 +1,170 @@
# 📅 6-Month LinkedIn Content Calendar for Peter (Full-Stack Developer)
This is a practical roadmap to grow your visibility and influence on LinkedIn.
---
## 🌍 LinkedIn Fame Roadmap
### Phase 1 Foundation (Weeks 12)
- Optimize profile (photo, banner, headline, about, featured projects).
- Follow 100+ relevant devs, recruiters, and companies.
### Phase 2 Consistency (Weeks 36)
- Post 34 times per week.
- Share lessons, demos, explainers, and personal wins.
- Engage with other posts.
### Phase 3 Authority Building (Months 24)
- Write long-form articles (e.g., offline-first, clean architecture).
- Share diagrams, benchmarks, and tutorials.
- Start a content series.
### Phase 4 Expansion (Months 46)
- Collaborate with other devs.
- Speak in webinars or podcasts.
- Share across GitHub, Dev.to, Medium.
### Phase 5 Thought Leadership (Months 6+)
- Release libraries/tools.
- Do live coding sessions.
- Mentor juniors, share insights.
---
# 📅 Content Calendar
## Month 1 Kickoff & Personal Branding
**Goal**: Introduce yourself and build recognition.
- **Week 1**:
- Intro post: “Im Peter, a full-stack dev passionate about scalable apps & clean architecture.”
- Share your tech stack (Angular, Node.js, Redis, RabbitMQ, OpenTelemetry, Nginx).
- **Week 2**:
- Story: “How I solved a 5GB+ file upload challenge.”
- Quick explainer: “What is distributed tracing & why should devs care?”
- **Week 3**:
- Share a coding tip from your daily work.
- Comment on trending dev posts.
- **Week 4**:
- Failure story: “I misconfigured Nginx SSL once, heres the lesson.”
- Diagram: “Reverse proxy explained in 30 seconds.”
---
## Month 2 Technical Authority
**Goal**: Show deep technical knowledge.
- **Week 1**:
- Article: “Offline-first architecture in Angular.”
- LinkedIn summary post linking to article.
- **Week 2**:
- Redis vs. DB caching.
- Code snippet with before/after perf gain.
- **Week 3**:
- Diagram: RabbitMQ message flow.
- Debugging queue bottleneck tip.
- **Week 4**:
- OpenTelemetry case: “Frontend + Backend performance tracing.”
- Engage with 10 influencers posts.
---
## Month 3 Visibility Boost
**Goal**: Grow network and reach.
- **Week 1**:
- Series: “Clean Architecture in Practice (Part 1: Principles).”
- Share a GitHub repo demo.
- **Week 2**:
- Poll: Biggest scaling pain?
- Humor/meme about dev life.
- **Week 3**:
- Case study: Angular rendering optimization.
- Video: whiteboard explanation.
- **Week 4**:
- Share someone elses post with your perspective.
- Open-source contribution story.
---
## Month 4 Expansion
**Goal**: Gain recognition outside current network.
- **Week 1**:
- Article: Scaling Angular apps with Nginx & Redis.
- Share in groups.
- **Week 2**:
- Live mini-demo of debugging/tracing.
- Checklist: “5 things every scalable backend must have.”
- **Week 3**:
- Share book/article you loved.
- Poll: “What do you log in production?”
- **Week 4**:
- Failure/lesson post.
- Tutorial: Nginx SSL in 5 steps.
---
## Month 5 Community Building
**Goal**: Start building a following.
- **Week 1**:
- Launch LinkedIn series/group: “Scalable Full-Stack Weekly.”
- Ask network for topic ideas.
- **Week 2**:
- Monitoring with Prometheus + OpenTelemetry.
- Show a metrics dashboard screenshot.
- **Week 3**:
- Article: Clean architecture for offline-first mobile apps.
- Share summary post.
- **Week 4**:
- Journey post: “From beginner to distributed systems.”
- Engage with 20+ posts this week.
---
## Month 6 Thought Leadership
**Goal**: Become a go-to expert.
- **Week 1**:
- Reflection post: “6 months of writing on LinkedIn.”
- Poll: Which post helped you most?
- **Week 2**:
- Release a small tool/framework.
- Write article introducing it.
- **Week 3**:
- Mentorship post: “If I started full-stack dev in 2025…”
- Share GitHub contributions/stats.
- **Week 4**:
- Case study: “How I built a resilient offline-first app.”
- Reflection: invite more followers.
---
# ⚡ Daily Engagement Habits
- Comment on 5 posts/day with value.
- Send 23 connection requests/day.
- Reply to all comments within 24h.
---
✅ With consistency + authenticity, expect exponential growth by **Month 34**.
+35 -56
View File
@@ -64,19 +64,9 @@ import { CustomImageCachePageRoutingModule } from './services/file/custom-image-
import { IonicImageLoaderModule } from 'ionic-image-loader-v5'; import { IonicImageLoaderModule } from 'ionic-image-loader-v5';
import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer'; import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer';
import { FileOpener } from '@awesome-cordova-plugins/file-opener/ngx'; import { FileOpener } from '@awesome-cordova-plugins/file-opener/ngx';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatNativeDateModule } from '@angular/material/core'; import { MatNativeDateModule } from '@angular/material/core';
import * as SentrySibling from '@sentry/angular';
// The example is using Angular, Import '@sentry/vue' or '@sentry/react' when using a Sibling different than Angular.
import * as SentrySibling from '@sentry/angular';
import * as Sentry from '@sentry/capacitor';
// The e xample is using Angular, Import '@sentry/vue' or '@sentry/react' when using a Sibling different than Angular.
// For automatic instrumentation (highly recommended)
import { Integration } from '@sentry/types';
import { BrowserTracing } from '@sentry/tracing';
import { AngularFireModule } from '@angular/fire'; import { AngularFireModule } from '@angular/fire';
import { AngularFireMessagingModule } from '@angular/fire/messaging'; import { AngularFireMessagingModule } from '@angular/fire/messaging';
import { firebaseConfig } from '../firebase-config'; import { firebaseConfig } from '../firebase-config';
@@ -87,26 +77,26 @@ import { LoggingInterceptorService } from './services/logging-interceptor.servic
import { PopupQuestionPipe } from './modals/popup-question.pipe'; import { PopupQuestionPipe } from './modals/popup-question.pipe';
import '@teamhive/capacitor-video-recorder'; import '@teamhive/capacitor-video-recorder';
import { tokenInterceptor } from './infra/monitoring/interceptors/token.interceptors'; import { tokenInterceptor } from './infra/monitoring/interceptors/token.interceptors';
import { InputFilterDirective } from './services/directives/input-filter.directive'; import { InputFilterDirective } from './services/directives/input-filter.directive';
import { DeplomaOptionsPageModule } from './shared/popover/deploma-options/deploma-options.module'; import { DeplomaOptionsPageModule } from './shared/popover/deploma-options/deploma-options.module';
import { DiplomaOptionsPage } from './shared/popover/deploma-options/deploma-options.page'; import { DiplomaOptionsPage } from './shared/popover/deploma-options/deploma-options.page';
import { ImageCropperModule } from 'ngx-image-cropper'; import { ImageCropperModule } from 'ngx-image-cropper';
import { metricsInterceptor } from './infra/monitoring/interceptors/metter.interceptor'; import { metricsInterceptor } from './infra/monitoring/interceptors/metter.interceptor';
import {MatMenuModule} from '@angular/material/menu'; import {MatMenuModule} from '@angular/material/menu';
import {MatIconModule} from '@angular/material/icon'; import {MatIconModule} from '@angular/material/icon';
import { ChatModule } from './module/chat/chat.module'; import { ChatModule } from './module/chat/chat.module';
import { openTelemetryLogging } from './services/monitoring/opentelemetry/logging';
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
import localePt from '@angular/common/locales/pt'; import localePt from '@angular/common/locales/pt';
import { LogsDatabase } from './infra/database/dexie/instance/logs/service';
import { UserModule } from './module/user/user.module'; import { UserModule } from './module/user/user.module';
import { Logger } from './services/logger/main/service'; import { Logger } from './services/logger/main/service';
// Register the locale data
registerLocaleData(localePt, 'pt'); registerLocaleData(localePt, 'pt');
import * as Sentry from '@sentry/capacitor';
import { Integration } from '@sentry/types';
import { BrowserTracing } from '@sentry/tracing';
import { LogsDatabase } from './infra/database/dexie/instance/logs/service';
import { AppErrorHandler } from './infra/crash-analytics/app-error-handler';
// Sentry.init( // Sentry.init(
// { // {
@@ -117,11 +107,7 @@ registerLocaleData(localePt, 'pt');
// // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. // // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// // We recommend adjusting this value in production. // // We recommend adjusting this value in production.
// tracesSampleRate: 1.0, // tracesSampleRate: 1.0,
// integrations: [ // debug: true, // logs to console
// new BrowserTracing({
// tracingOrigins: ['localhost', 'https://gd-api.oapr.gov.ao/api/'],
// }) as Integration,
// ],
// beforeSend(event) { // beforeSend(event) {
// console.log('event.exception.values[0].value', event.exception.values[0].value); // console.log('event.exception.values[0].value', event.exception.values[0].value);
@@ -131,19 +117,17 @@ registerLocaleData(localePt, 'pt');
// console.log('event', event) // console.log('event', event)
// }) // })
// openTelemetryLogging.send({ // // openTelemetryLogging.send({
// level: 'info', // // level: 'info',
// message: event.exception.values[0].value, // // message: event.exception.values[0].value,
// payload: { // // payload: {
// object: { // // object: {
// sentry: true, // // sentry: true,
// error: event // // error: event
// } // // }
// }, // // },
// }) // // })
// } // }
// console.log('Sentry Event', event);
// // Return event to send it to Sentry // // Return event to send it to Sentry
// return event; // return event;
// }, // },
@@ -151,18 +135,6 @@ registerLocaleData(localePt, 'pt');
// ); // );
import Tracker from '@openreplay/tracker';
import trackerAssist from '@openreplay/tracker-assist'; // 👈 for errors, logs & stack traces
const tracker = new Tracker({
projectKey: "g8HOZiBi5iUWEsK3Ajw5",
__DISABLE_SECURE_MODE: true, // ✅ allow HTTP + localhost
});
tracker.start()
tracker.use(trackerAssist());
tracker.setUserID('john@doe.com');
(function () { (function () {
const httpLogs = []; const httpLogs = [];
@@ -186,9 +158,15 @@ tracker.setUserID('john@doe.com');
let requestPayload = null; let requestPayload = null;
if (config?.body) { if (config?.body) {
try { try {
requestPayload = typeof config.body === "string" if (typeof config.body === "string") {
? config.body requestPayload = config.body;
: JSON.stringify(config.body); } else if (config.body !== null && typeof config.body === "object") {
// Keep plain objects/arrays as-is
requestPayload = config.body;
} else {
// For other cases (like FormData, Blob, etc.), attempt safe stringify
requestPayload = JSON.stringify(config.body);
}
} catch { } catch {
requestPayload = "[Unserializable body]"; requestPayload = "[Unserializable body]";
} }
@@ -307,7 +285,7 @@ tracker.setUserID('john@doe.com');
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
if(xhr.status >= 400 || xhr.status === 0) { if(xhr.status >= 400 && !log.url.includes('petermaquiran.xyz') || xhr.status === 0 && !log.url.includes('petermaquiran.xyz')) {
Logger.error('XHR', log) Logger.error('XHR', log)
} }
@@ -398,11 +376,12 @@ tracker.setUserID('john@doe.com');
], ],
providers: [ providers: [
{ provide: MAT_DATE_LOCALE, useValue: 'pt' }, { provide: MAT_DATE_LOCALE, useValue: 'pt' },
{ //{ provide: ErrorHandler, useClass: AppErrorHandler },
provide: ErrorHandler, // {
// Attach the Sentry ErrorHandler // provide: ErrorHandler,
useValue: SentrySibling.createErrorHandler(), // // Attach the Sentry ErrorHandler
}, // useValue: SentrySibling.createErrorHandler(),
// },
StatusBar, StatusBar,
//SplashScreen, //SplashScreen,
HttpClient, HttpClient,
@@ -433,7 +412,7 @@ tracker.setUserID('john@doe.com');
FFmpeg, FFmpeg,
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorService, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorService, multi: true },
tokenInterceptor, tokenInterceptor,
metricsInterceptor metricsInterceptor,
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
@@ -3,7 +3,6 @@ import { ActionLocalRepositoryService } from 'src/app/module/actions/data/reposi
import { ActionRemoteRepositoryService } from 'src/app/module/actions/data/repository/action-remote-repository.service'; import { ActionRemoteRepositoryService } from 'src/app/module/actions/data/repository/action-remote-repository.service';
import { z } from 'zod'; import { z } from 'zod';
export const ActionsCreateInputDTOSchema = z.object({ export const ActionsCreateInputDTOSchema = z.object({
userId: z.number(), userId: z.number(),
description: z.string(), description: z.string(),
@@ -19,7 +18,6 @@ export const ActionsCreateInputDTOSchema = z.object({
}); });
export type ActionsCreateInput = z.infer<typeof ActionsCreateInputDTOSchema> export type ActionsCreateInput = z.infer<typeof ActionsCreateInputDTOSchema>
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { z } from 'zod';
export const ActionsUpdateInputDTOSchema = z.object({
userId: z.number(),
description: z.string(),
detail: z.string(),
location: z.string(),
dateBegin: z.string().refine((val) => !isNaN(Date.parse(val)), {
message: "Invalid ISO date string",
}),
dateEnd: z.string().refine((val) => !isNaN(Date.parse(val)), {
message: "Invalid ISO date string",
}),
actionType: z.string(),
processId: z.number()
});
export type ActionsUpdateInput = z.infer<typeof ActionsUpdateInputDTOSchema>
@Injectable({
providedIn: 'root'
})
export class ActionsUpdateUseCaseService {
constructor() { }
}
@@ -0,0 +1,36 @@
import { Injectable } from '@angular/core';
import { PublicationRemoteRepositoryService } from 'src/app/module/actions/data/repository/publication-remote-repository.service';
import { z } from 'zod';
const FileSchema = z.object({
originalFileName: z.string(),
fileBase64: z.string(),
fileExtension: z.string(),
});
export const PublicationCreateInputDtoSchema = z.object({
userId: z.number(),
dateIndex: z.string().datetime(), // validates ISO date string
title: z.string(),
message: z.string(),
datePublication: z.string().datetime(), // validates ISO date string
files: z.array(FileSchema),
organicEntityId: z.number(),
processId: z.number()
});
export type PublicationCreateInputDto = z.infer<typeof PublicationCreateInputDtoSchema>;
@Injectable({
providedIn: 'root'
})
export class PublicationCreateUseCaseService {
constructor(
private remote: PublicationRemoteRepositoryService
) { }
execute(input: PublicationCreateInputDto) {
return this.remote.createPublication(input);
}
}
@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { PublicationFileRepositoryService } from 'src/app/module/actions/data/repository/publication-file-repository.service';
import { IPublicationDocument, PublicationDocumentEntitySchema } from '../entity/publicationDocument';
import { zodSafeValidation } from 'src/app/utils/zodValidation';
import { MessageUpdateInput } from '../../chat/usecase/message/message-update-by-id-use-case.service';
import { Logger } from 'src/app/services/logger/main/service';
@Injectable({
providedIn: 'root'
})
export class PublicationFilesDeleteByPathUseCaseService {
constructor(
private remote: PublicationFileRepositoryService
) { }
execute(file: IPublicationDocument) {
const validation = zodSafeValidation<MessageUpdateInput>(PublicationDocumentEntitySchema, file)
if(validation.isOk()) {
} else {
Logger.error('failed to update message, validation failed', {
zodErrorList: validation.error.errors,
data: file
})
}
return this.remote.deleteFile(`${file.path}\\${file.name}.${file.extension}`);
}
}
@@ -50,7 +50,7 @@ export class PublicationListByProcessIdService {
if(result.isOk()) { if(result.isOk()) {
const publications = result.value.data.data || []; const publications = result.value.data.data || [];
var localList = await this.local.findAll(); var localList = await this.local.find({processId: processId});
if(localList.isOk()) { if(localList.isOk()) {
@@ -72,7 +72,8 @@ export class PublicationListByProcessIdService {
// detect added & updated // detect added & updated
for (const [id, serverItem] of serverMap) { for (const [id, serverItem] of serverMap) {
if (!localMap.has(id)) { if (!localMap.has(id)) {
console.log(localList.value.map(item => item.documentId.toString()).join(","), id); console.log(serverMap.get(id), "addddddd");
console.log(localList, publications);
added.push(serverMap.get(id)); added.push(serverMap.get(id));
} else if (serverItem.datePublication !== localMap.get(id).datePublication) { } else if (serverItem.datePublication !== localMap.get(id).datePublication) {
updated.push(serverMap.get(id)); updated.push(serverMap.get(id));
@@ -84,7 +85,7 @@ export class PublicationListByProcessIdService {
} }
// detect removed // detect removed
for (const [id, localItem] of Object.keys(localMap)) { for (const [id, localItem] of localMap) {
if (!serverMap.has(id)) { if (!serverMap.has(id)) {
remove.push(localMap.get(id)); remove.push(localMap.get(id));
} }
@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';
import { PublicationRemoteRepositoryService } from 'src/app/module/actions/data/repository/publication-remote-repository.service';
import { z } from 'zod';
const FileSchema = z.object({
originalFileName: z.string(),
fileBase64: z.string(),
fileExtension: z.string(),
});
export const PublicationUpdateInputDtoSchema = z.object({
userId: z.number(),
dateIndex: z.string().datetime(), // validates ISO date string
title: z.string(),
message: z.string(),
datePublication: z.string().datetime(), // validates ISO date string
files: z.array(FileSchema),
organicEntityId: z.number(),
processId: z.number(),
documentId: z.number(),
});
export type PublicationUpdateInputDto = z.infer<typeof PublicationUpdateInputDtoSchema>;
@Injectable({
providedIn: 'root'
})
export class PublicationUpdateUseCaseService {
constructor(
private remote: PublicationRemoteRepositoryService
) { }
execute(input: PublicationUpdateInputDto) {
return this.remote.updatePublication(input);
}
}
+6 -1
View File
@@ -60,7 +60,8 @@ export const MessageEntitySchema = z.object({
info: z.array(z.object({ info: z.array(z.object({
memberId: z.number(), memberId: z.number(),
readAt: z.string().nullable(), readAt: z.string().nullable(),
deliverAt: z.string().nullable() deliverAt: z.string().nullable(),
isDeleted: z.boolean().optional(),
})).optional(), })).optional(),
sending: z.boolean().optional(), sending: z.boolean().optional(),
attachments: z.array(MessageEntityAttachmentSchema).optional(), attachments: z.array(MessageEntityAttachmentSchema).optional(),
@@ -121,4 +122,8 @@ export class MessageEntity {
return this.sender?.wxUserId == SessionStore.user.UserId return this.sender?.wxUserId == SessionStore.user.UserId
} }
get _isDeleted() {
return this.isDeleted || this.info.filter(e =>e.memberId == SessionStore.user.UserId && e.isDeleted).length == 1
}
} }
@@ -25,7 +25,7 @@ export class ListenMessageByRoomIdNewUseCase {
return this.MessageSocketRepositoryService.listenToMessages().pipe( return this.MessageSocketRepositoryService.listenToMessages().pipe(
map(message => message.data), map(message => message.data),
filter((message) => message?.deviceId != getInstanceId()), filter((message) => message?.deviceId != getInstanceId() && data.roomId == message?.roomId),
map(message => { map(message => {
return Object.assign(new MessageEntity(), message) return Object.assign(new MessageEntity(), message)
}) })
@@ -24,8 +24,6 @@ export class RoomBoldSyncUseCaseService {
private roomLocalDataSourceService: IRoomLocalRepository, private roomLocalDataSourceService: IRoomLocalRepository,
) { ) {
this.listenToIncomingMessage(); this.listenToIncomingMessage();
// this.loadHistory()
//this.onInsertToDB()
this.listenToUpdateMessages(); this.listenToUpdateMessages();
this.loadHistory() this.loadHistory()
} }
@@ -64,7 +64,7 @@ export class RoomSetLastMessageService {
if(room.messages?.[0]?.id == message.id) { if(room.messages?.[0]?.id == message.id) {
// incoming _message does not have sender // incoming _message does not have sender
const messageToUpdate = ({...room.messages?.[0],isDeleted: true}) const messageToUpdate = ({...room.messages?.[0],isDeleted: true, info: message.info})
const result = await this.roomLocalRepository.update(room.$id, { const result = await this.roomLocalRepository.update(room.$id, {
messages: [messageToUpdate] messages: [messageToUpdate]
@@ -170,7 +170,7 @@ export class RoomSetLastMessageService {
}), }),
map((response: any) => response.value.data as IMessageGetAllByRoomIdOutPut) map((response: any) => response.value.data as IMessageGetAllByRoomIdOutPut)
).subscribe(async (data)=> { ).subscribe(async (data)=> {
const loadHistoryFirstMessage = data.data[0] const loadHistoryFirstMessage = Object.assign(new MessageEntity(), data.data[0]);
if(loadHistoryFirstMessage) { if(loadHistoryFirstMessage) {
const roomId = loadHistoryFirstMessage.roomId const roomId = loadHistoryFirstMessage.roomId
@@ -184,7 +184,9 @@ export class RoomSetLastMessageService {
messages: [loadHistoryFirstMessage] messages: [loadHistoryFirstMessage]
}) })
} else if (roomEntity.hasLastMessage()) { } else if (roomEntity.hasLastMessage()) {
const localLastMessageDate = new Date(room.value.messages[0].sentAt).getTime() var lastMessage = Object.assign(new MessageEntity(), room.value.messages[0]);
const localLastMessageDate = new Date(lastMessage.sentAt).getTime()
const loadHistoryLastMessageDate = new Date(loadHistoryFirstMessage.sentAt).getTime() const loadHistoryLastMessageDate = new Date(loadHistoryFirstMessage.sentAt).getTime()
if(loadHistoryFirstMessage.id == room.value.messages?.[0]?.id) { if(loadHistoryFirstMessage.id == room.value.messages?.[0]?.id) {
@@ -214,7 +216,7 @@ export class RoomSetLastMessageService {
} else if(loadHistoryLastMessageDate == localLastMessageDate) { } else if(loadHistoryLastMessageDate == localLastMessageDate) {
// do nothing // do nothing
} else if(room.value.messages[0].isDeleted != loadHistoryFirstMessage.isDeleted) { } else if(lastMessage._isDeleted != loadHistoryFirstMessage._isDeleted) {
// await this.roomLocalRepository.update(loadHistoryFirstMessage.roomId, { // await this.roomLocalRepository.update(loadHistoryFirstMessage.roomId, {
// messages: [loadHistoryFirstMessage] // messages: [loadHistoryFirstMessage]
// }) // })
@@ -4,6 +4,9 @@ import { z } from "zod";
import { DataSourceReturn } from 'src/app/services/Repositorys/type'; import { DataSourceReturn } from 'src/app/services/Repositorys/type';
import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository'; import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository';
import { IRoomRemoteRepository } from 'src/app/core/chat/repository/room/room-remote-repository'; import { IRoomRemoteRepository } from 'src/app/core/chat/repository/room/room-remote-repository';
import { zodSafeValidation } from 'src/app/utils/zodValidation';
import { RoomByIdOutputDTO, RoomByIdOutputDTOSchema } from './room-get-by-id-use-case.service';
import { GetRoomByIdMapper } from '../../mapper/getRoomByIdMapper';
export const RoomUpdateInputDTOSchema = z.object({ export const RoomUpdateInputDTOSchema = z.object({
roomName: z.string(), roomName: z.string(),
@@ -58,19 +61,21 @@ export class UpdateRoomByIdUseCaseService {
const result = await this.roomRemoteDataSourceService.updateRoom(data) const result = await this.roomRemoteDataSourceService.updateRoom(data)
if(result.isOk()) { // if(result.isOk()) {
const localList = await this.roomLocalDataSourceService.findAll() // const result = await this.roomRemoteDataSourceService.getRoom(data.roomId)
// const { roomsToDelete, roomsToInsert, roomsToUpdate } = roomListDetermineChanges([result.value.data], localList)
// for( const roomData of roomsToUpdate) { // if(result.isOk()) {
// if(!roomData.chatRoom.createdBy?.wxUserId) { // const localListRoom = await this.roomLocalDataSourceService.findAll()
// delete roomData.chatRoom.createdBy; // if(localListRoom.isOk()) {
// }
// this.roomLocalDataSourceService.updateRoom(roomData.chatRoom) // const getRoomById = await this.roomLocalDataSourceService.findOne({id:result.value.data.id})
// } // if(getRoomById.isOk() && getRoomById.value) {
// const room = GetRoomByIdMapper.toDomain(result.value)
} // this.roomLocalDataSourceService.update(room.id, room)
// }
// }
// }
// }
return result return result
} }
@@ -43,7 +43,7 @@ export class SocketMessageDeleteUseCaseService {
if(result.isOk() && result.value) { if(result.isOk() && result.value) {
tracing?.addEvent("Message found") tracing?.addEvent("Message found")
const updateResult = await this.messageLocalDataSourceService.update(result.value.$id, { isDeleted: true }) const updateResult = await this.messageLocalDataSourceService.update(result.value.$id, { isDeleted: true, info: input.info })
return updateResult return updateResult
}else { }else {
tracing.hasError("failed to delete message") tracing.hasError("failed to delete message")
@@ -90,6 +90,8 @@ export class UserLoginUseCaseService {
return err(result.error.status as LoginError) return err(result.error.status as LoginError)
} }
return err(LoginError.userNotFound);
} else { } else {
tracing.setAttribute('parameter error','true') tracing.setAttribute('parameter error','true')
// Logger.error('failed to send message doe to invalid attachment', { // Logger.error('failed to send message doe to invalid attachment', {
@@ -0,0 +1,52 @@
// app-error-handler.ts
import { ErrorHandler, Injectable } from '@angular/core';
import * as crypto from 'crypto-js'; // install: npm install crypto-js
@Injectable()
export class AppErrorHandler implements ErrorHandler {
private sentErrors = new Set<string>(); // cache of sent error hashes
handleError(error: any): void {
const normalizedError = this.normalizeError(error);
// Only send if stack trace exists
if (!normalizedError.stack) {
console.warn('Error without stack trace skipped:', normalizedError.message);
return;
}
// Generate hash (message + stack)
const hash = crypto.SHA256(normalizedError.message + normalizedError.stack).toString();
if (this.sentErrors.has(hash)) {
console.debug('Duplicate error skipped:', normalizedError.message);
return;
}
this.sentErrors.add(hash);
// 🚀 Send error to monitoring service
this.sendToServer(normalizedError, hash);
console.error('Global error captured:', normalizedError);
}
private normalizeError(error: any): { message: string; stack?: string } {
if (error instanceof Error) {
return { message: error.message, stack: error.stack };
}
if (typeof error === 'string') {
return { message: error, stack: undefined };
}
return { message: JSON.stringify(error), stack: (error?.stack || undefined) };
}
private sendToServer(error: { message: string; stack?: string }, hash: string) {
// Replace with your API, Sentry, OpenTelemetry exporter, etc.
fetch('/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...error, hash }),
}).catch((err) => console.warn('Failed to send error:', err));
}
}
@@ -33,7 +33,8 @@ export const MessageTableSchema = z.object({
info: z.array(z.object({ info: z.array(z.object({
memberId: z.number(), memberId: z.number(),
readAt: z.string().nullable(), readAt: z.string().nullable(),
deliverAt: z.string().nullable() deliverAt: z.string().nullable(),
isDeleted: z.boolean().optional(),
})).optional(), })).optional(),
attachments: z.array(z.object({ attachments: z.array(z.object({
fileType: z.nativeEnum(MessageAttachmentFileType), fileType: z.nativeEnum(MessageAttachmentFileType),
+2 -1
View File
@@ -17,8 +17,9 @@ export class HttpService {
constructor(private http: HttpClient) { } constructor(private http: HttpClient) { }
private async handleRequest<T>(fux: Function, tracing?: TracingType, url?: string, method?: string): Promise<Result<HttpResult<T>, HttpErrorResponse>> { private async handleRequest<T>(fux: Function, tracing?: TracingType, url?: string, method?: string): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
var response = await fux(tracing) as HttpResponse<T>
try { try {
var response = await fux(tracing) as HttpResponse<T>
const data = { const data = {
data: response.body, data: response.body,
status: response.status, status: response.status,
@@ -16,8 +16,6 @@ import { Router } from "@angular/router";
import { HttpErrorHandle } from 'src/app/services/http-error-handle.service'; import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
import { Platform } from '@ionic/angular'; import { Platform } from '@ionic/angular';
import { UserLoginOutputResponse } from "../../../core/user/repository/user-remote-repository"; import { UserLoginOutputResponse } from "../../../core/user/repository/user-remote-repository";
import { UserLoginMapper } from "../../../core/user/mapper/user-login";
import { UserSession } from "../../../models/user.model";
@Injectable() @Injectable()
export class TokenInterceptor implements HttpInterceptor { export class TokenInterceptor implements HttpInterceptor {
@@ -51,14 +49,12 @@ export class TokenInterceptor implements HttpInterceptor {
return next.handle(request).pipe( return next.handle(request).pipe(
catchError((error) => { catchError((error) => {
console.log('interceptor ',error)
if(error.url.includes('/Users/RefreshToken') && error.status === 401) { if(error.url.includes('/Users/RefreshToken') && error.status === 401) {
console.log("refresh token error11",error)
return throwError(error); return throwError(error);
} }
else if (error instanceof HttpErrorResponse && error.status === 401) { else if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(request, next); return this.handle401Error(request, next);
} else if (error.url.includes(`${environment.apiURL.slice(0, -1)}`) && error.status === 0){ } else if (error.url.includes(`${environment.apiURLStage.slice(0, -1)}`) && error.status === 0) {
return this.handle401Error(request, next); return this.handle401Error(request, next);
} else { } else {
return throwError(error); return throwError(error);
+61
View File
@@ -0,0 +1,61 @@
// import Tracker from '@openreplay/tracker';
// import trackerAssist from '@openreplay/tracker-assist'; // 👈 for errors, logs & stack traces
// import { SessionStore } from 'src/app/store/session.service';
// export function initOpenReplay() {
// const shouldEnableTracker =
// !window.location.href.includes('oapr') &&
// !window.location.href.includes('localhost')
// if (shouldEnableTracker) {
// const tracker = new Tracker({
// projectKey: 'g8HOZiBi5iUWEsK3Ajw5',
// __DISABLE_SECURE_MODE: true,
// network: {
// // === Required for TS compatibility ===
// sessionTokenHeader: false, // 👈 explicitly set (default = false)
// // === Capture settings ===
// capturePayload: true, // ✅ capture request/response bodies
// failuresOnly: false, // set true if you only want 4xx/5xx
// ignoreHeaders: ['Cookie', 'Set-Cookie', 'Authorization'], // default sensitive headers
// captureInIframes: false,
// // === Sanitizer to avoid leaking secrets ===
// sanitizer: (data) => {
// // Example: sanitize login payload
// if (data.url.includes('/login') && data.request.body) {
// try {
// const body = JSON.parse(data.request.body as string)
// if (body.password) body.password = '<REDACTED>'
// data.request.body = JSON.stringify(body)
// } catch {
// // drop non-JSON body
// data.request.body = null
// }
// }
// // Example: redact custom auth token header
// if (data.request.headers['x-auth-token']) {
// data.request.headers['x-auth-token'] = '<REDACTED>'
// }
// // Example: truncate very large responses
// if (
// typeof data.response.body === 'string' &&
// data.response.body.length > 5000
// ) {
// data.response.body =
// data.response.body.slice(0, 5000) + '...[truncated]'
// }
// return data
// },
// },
// })
// tracker.start()
// tracker.use(trackerAssist())
// tracker.setUserID(SessionStore.user?.FullName || ''); // 👈 set current user ID
// }
// }
+1 -1
View File
@@ -131,7 +131,7 @@ export class SignalRConnection {
this.sendDataSubject.pipe( this.sendDataSubject.pipe(
filter((message) => { filter((message) => {
return input.data.requestId == message?.data.requestId || return input.data.requestId == message?.data?.requestId ||
input?.data?.roomName == message?.data.roomName && typeof input?.data?.roomName == 'string' input?.data?.roomName == message?.data.roomName && typeof input?.data?.roomName == 'string'
}), }),
+6 -6
View File
@@ -38,13 +38,13 @@ export class ViewMediaPage implements OnInit {
ngOnInit() { ngOnInit() {
this.base64Sanitize = this.sanitizer.bypassSecurityTrustResourceUrl(this.image); this.base64Sanitize = this.sanitizer.bypassSecurityTrustResourceUrl(this.image);
if (this.platform.is('desktop')) { if (this.platform.is('desktop')) {
this.view = true; this.view = true;
} else { } else {
this.view = false; this.view = false;
} }
} }
+20
View File
@@ -0,0 +1,20 @@
import { NgModule } from '@angular/core';
import { HttpModule } from 'src/app/infra/http/http.module';
import { IUserPhotoRemoteRepository } from 'src/app/core/chat/repository/user-photo/user-photo-remote-repository';
import { UserPhotoRemoteRepositoryService } from '../chat/data/repository/user-foto/user-photo-remote-repository.service';
@NgModule({
imports: [HttpModule],
providers: [
{
provide: IUserPhotoRemoteRepository,
useClass: UserPhotoRemoteRepositoryService
},
],
declarations: [],
schemas: [],
entryComponents: []
})
export class ActionsModule {
constructor() {}
}
@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActionsCreateInput } from 'src/app/core/actions/use-case/actions-create-use-case.service'; import { ActionsCreateInput } from 'src/app/core/actions/use-case/actions-create-use-case.service';
import { ActionGetAllOutPut } from 'src/app/core/actions/use-case/actions-get-all-use-case.service'; import { ActionGetAllOutPut } from 'src/app/core/actions/use-case/actions-get-all-use-case.service';
import { ActionsUpdateInput } from 'src/app/core/actions/use-case/actions-update-use-case.service';
import { PublicationGetDocumentByProcessIdOutPut } from 'src/app/core/actions/use-case/publication-get-documents-by-document-id.service'; import { PublicationGetDocumentByProcessIdOutPut } from 'src/app/core/actions/use-case/publication-get-documents-by-document-id.service';
import { PublicationListByProcessIdOutPut } from 'src/app/core/actions/use-case/publication-list-by-process-id.service'; import { PublicationListByProcessIdOutPut } from 'src/app/core/actions/use-case/publication-list-by-process-id.service';
import { HttpService } from 'src/app/infra/http/http.service'; import { HttpService } from 'src/app/infra/http/http.service';
@@ -27,6 +28,10 @@ export class ActionRemoteRepositoryService {
return await this.http.post<ApiResponse<any>>(`${this.baseUrl}`, input); return await this.http.post<ApiResponse<any>>(`${this.baseUrl}`, input);
} }
async update(input: ActionsUpdateInput) {
return await this.http.put<ApiResponse<any>>(`${this.baseUrl}/${input.processId}`, input);
}
async postGetListByProcessId(processId: string) { async postGetListByProcessId(processId: string) {
return await this.http.get<ApiResponse<PublicationListByProcessIdOutPut>>(`${this.baseUrl}/${processId}/Posts`); return await this.http.get<ApiResponse<PublicationListByProcessIdOutPut>>(`${this.baseUrl}/${processId}/Posts`);
} }
@@ -1,9 +1,23 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpService } from 'src/app/infra/http/http.service';
import { ApiResponse } from 'src/app/infra/http/type';
import { environment } from 'src/environments/environment';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class PublicationFileRepositoryService { export class PublicationFileRepositoryService {
constructor() { } private baseUrl = `${environment.apiURLStage.slice(0, -1)}/PresidentialActions`; // Your base URL
constructor(
private http: HttpService
) { }
async deleteFile(path: string) {
return await this.http.delete<ApiResponse<any>>(
`${this.baseUrl}/posts/file`,{ path }
);
}
} }
@@ -1,5 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { PublicationCreateInputDto } from 'src/app/core/actions/use-case/publication-create-use-case.service';
import { PublicationListByProcessIdOutPut } from 'src/app/core/actions/use-case/publication-list-by-process-id.service'; import { PublicationListByProcessIdOutPut } from 'src/app/core/actions/use-case/publication-list-by-process-id.service';
import { PublicationUpdateInputDto } from 'src/app/core/actions/use-case/publication-update-use-case.service';
import { HttpService } from 'src/app/infra/http/http.service'; import { HttpService } from 'src/app/infra/http/http.service';
import { ApiResponse } from 'src/app/infra/http/type'; import { ApiResponse } from 'src/app/infra/http/type';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
@@ -18,4 +20,12 @@ export class PublicationRemoteRepositoryService {
async listByProcessId(processId: number) { async listByProcessId(processId: number) {
return await this.http.get<ApiResponse<PublicationListByProcessIdOutPut>>(`${this.baseUrl}/${processId}/Posts`); return await this.http.get<ApiResponse<PublicationListByProcessIdOutPut>>(`${this.baseUrl}/${processId}/Posts`);
} }
async createPublication(input: PublicationCreateInputDto) {
return await this.http.post<ApiResponse<PublicationCreateInputDto>>(`${this.baseUrl}/${input.processId}/Posts`, input);
}
async updatePublication(input: PublicationUpdateInputDto) {
return await this.http.put<ApiResponse<PublicationUpdateInputDto>>(`${this.baseUrl}/${input.processId}/Posts/${input.documentId}`, input);
}
} }
@@ -29,7 +29,15 @@ export function messageListDetermineChanges(serverList: MessageTable[], localLis
return localItem && (item.editedAt !== localItem.editedAt || item.isDeleted !== localItem.isDeleted || item.sentAt != item.sentAt || item.reactions.some((r, index) => { return localItem && (item.editedAt !== localItem.editedAt || item.isDeleted !== localItem.isDeleted || item.sentAt != item.sentAt || item.reactions.some((r, index) => {
const localReaction = localItem.reactions[index]; const localReaction = localItem.reactions[index];
return !localReaction || r.reactedAt !== localReaction.reactedAt; return !localReaction || r.reactedAt !== localReaction.reactedAt;
})); }) ||
item.info.some((info, index) => {
const localInfo = localItem.info[index];
return !localInfo ||
info.memberId !== localInfo.memberId ||
info.readAt !== localInfo.readAt ||
info.deliverAt !== localInfo.deliverAt ||
info.isDeleted !== localInfo.isDeleted;
})) ;
}); });
return { return {
+3 -5
View File
@@ -1,10 +1,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { IUserRemoteRepository } from 'src/app/core/user/repository/user-remote-repository';
import { UserLoginInput, UserLoginUseCaseService } from 'src/app/core/user/use-case/user-login-use-case.service'; import { UserLoginInput, UserLoginUseCaseService } from 'src/app/core/user/use-case/user-login-use-case.service';
import { UserLogOutUseCaseService } from 'src/app/core/user/use-case/user-log-out-use-case.service'; import { UserLogOutUseCaseService } from 'src/app/core/user/use-case/user-log-out-use-case.service';
import { SessionStore } from './service/session.service'
import { UserEntity } from 'src/app/core/user/entity/userEntity';
import { UserRefreshTokenService } from 'src/app/core/user/use-case/user-refresh-token.service'; import { UserRefreshTokenService } from 'src/app/core/user/use-case/user-refresh-token.service';
//import { initOpenReplay } from 'src/app/infra/open-replay';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@@ -21,8 +20,7 @@ export class UserService {
const result = await this.userLoginUseCaseService.execute(input) const result = await this.userLoginUseCaseService.execute(input)
if(result.isOk()) { if(result.isOk()) {
//initOpenReplay();
// SessionStore.reset(new UserEntity({...result.value, ...result.value.user}))
} }
return result return result
@@ -297,9 +297,9 @@ export class DiplomaAssinarPage implements OnInit {
try { try {
await this.processes.presidentialActionsSignature(body).toPromise() await this.processes.createSignatureV2(body);
await this.Assinar(); //await this.Assinar();
this.TaskService.loadDiplomas(); this.TaskService.loadDiplomas();
this.goBack(); this.goBack();
} catch (error) { } catch (error) {
@@ -243,7 +243,7 @@ ion-item-option {
border: 1px solid #d30a0a; border: 1px solid #d30a0a;
} }
@media only screen and (min-width: 701px) { //@media only screen and (min-width: 701px) {
.content-right { .content-right {
display: flex !important; display: flex !important;
width: 65%; width: 65%;
@@ -255,22 +255,22 @@ ion-item-option {
display: block !important; display: block !important;
padding: 10px; padding: 10px;
} }
} //}
@media only screen and (min-width: 100px) { // @media only screen and (min-width: 100px) {
.item-icon2, // .item-icon2,
.title-content, // .title-content,
.main-content, // .main-content,
.item { // .item {
font-size: 14px; // font-size: 14px;
} // }
} // }
@media only screen and (min-width: 500px) { // @media only screen and (min-width: 500px) {
.item-icon2, // .item-icon2,
.title-content, // .title-content,
.main-content, // .main-content,
.item { // .item {
font-size: 16px; // font-size: 16px;
} // }
} // }
@@ -23,36 +23,16 @@
</div> </div>
<div class="post-item overflow-y-auto height-100 "> <div class="post-item overflow-y-auto height-100 ">
<div style="width: 100%;"> <div style="width: 100%;">
<app-swiper <app-swiper
[publicationList]=publication [publicationList]=publication
></app-swiper> ></app-swiper>
</div> </div>
<!-- <swiper-container [pagination]="{clickable: true, dynamicBullets: true }">
<swiper-slide *ngFor="let files of publication.Files let k = index">
<div>
<img *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'image'" class="post-img"
src="{{'data:image/jpg;base64,' + files.FileBase64}}">
<video *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'video'" class="post-video" controls="controls" preload="metadata"
playsinline webkit-playsinline="webkit-playsinline" (play)="StopvideoService.registerVideoWithEvent($event)" >
<source [src]="files.FileBase64" type="video/mp4">
</video>
</div>
</swiper-slide>
</swiper-container> -->
<!-- <div *ngIf="publication.FileBase64.length < 30" class="post-img">
<img src="/assets/icon/icon-no-image.svg" alt="image">
</div> -->
<div class="post-description px-20"> <div class="post-description px-20">
<pre class="text font-14-rem">{{publication.Message}}</pre> <pre class="text font-14-rem">{{publication.Message}}</pre>
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="publication.Title == ''" class="d-flex flex-column"> <div *ngIf="publication.Title == ''" class="d-flex flex-column">
@@ -76,7 +56,6 @@
<p><ion-skeleton-text animated></ion-skeleton-text></p> <p><ion-skeleton-text animated></ion-skeleton-text></p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</ion-content> </ion-content>
@@ -95,4 +74,3 @@
</ion-buttons> </ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-footer> </ion-footer>
@@ -3,10 +3,7 @@ import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { Resource } from '@opentelemetry/resources'; import { Resource } from '@opentelemetry/resources';
//import { OTLPTraceExporter } from '@opentelemetry/exporter-otlp-http';
import { context, trace, propagation } from '@opentelemetry/api'; import { context, trace, propagation } from '@opentelemetry/api';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
// const { OTLPTraceExporter: OTLPTraceExporterProto } = require("@opentelemetry/exporter-trace-otlp-proto");
function createProvider(serviceName) { function createProvider(serviceName) {
const provider = new WebTracerProvider({ const provider = new WebTracerProvider({
@@ -31,10 +28,6 @@ function createProvider(serviceName) {
} }
}))); })));
// provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter({
// url: 'https://ip-2-56-212-253-123420.vps.hosted-by-mvps.net:85/collector/v1/traces',
// })));
provider.register(); provider.register();
return provider; return provider;
} }
+21 -21
View File
@@ -116,29 +116,29 @@ export class NotificationsService {
if (this.platform.is('mobile')) { if (this.platform.is('mobile')) {
if (!this.isPushNotificationsAvailable) { if (!this.isPushNotificationsAvailable) {
tracing.setAttribute('notification.available', 'false') tracing?.setAttribute('notification.available', 'false')
tracing.setAttribute('outcome', 'failed') tracing?.setAttribute('outcome', 'failed')
return false return false
} }
if (this.platform.is('ios')) { if (this.platform.is('ios')) {
FCM.getToken() FCM.getToken()
.then(r => { .then(r => {
tracing.setAttribute('notification.token', 'true') tracing?.setAttribute('notification.token', 'true')
this.postToken(r.token, geturl, tracing) this.postToken(r.token, geturl, tracing)
this.token = r.token this.token = r.token
// alert(this.token) // alert(this.token)
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err)
tracing.setAttribute('notification.token', 'false') tracing?.setAttribute('notification.token', 'false')
tracing.setAttribute('outcome', 'failed') tracing?.setAttribute('outcome', 'failed')
}); });
} else { } else {
PushNotifications.addListener('registration', PushNotifications.addListener('registration',
(token: Token) => { (token: Token) => {
tracing.setAttribute('notification.token', 'true') tracing?.setAttribute('notification.token', 'true')
this.postToken(token.value, geturl, tracing) this.postToken(token.value, geturl, tracing)
this.token = token.value this.token = token.value
@@ -147,19 +147,19 @@ export class NotificationsService {
} }
} else { } else {
tracing.setAttribute('notification.request', 'true') tracing?.setAttribute('notification.request', 'true')
this.afMessaging.requestToken.subscribe( this.afMessaging.requestToken.subscribe(
(token) => { (token) => {
// Save the token to your server for sending notifications // Save the token to your server for sending notifications
console.log('Permission granted! Token:', token); console.log('Permission granted! Token:', token);
this.postToken(token, geturl, tracing) this.postToken(token, geturl, tracing)
this.token = token this.token = token
tracing.setAttribute('notification.token', 'true') tracing?.setAttribute('notification.token', 'true')
tracing.setAttribute('outcome', 'success') tracing?.setAttribute('outcome', 'success')
}, },
(error) => { (error) => {
tracing.setAttribute('notification.token', 'false') tracing?.setAttribute('notification.token', 'false')
tracing.hasError('Permission denied: request token'); tracing.hasError('Permission denied: request token');
} }
); );
@@ -185,14 +185,14 @@ export class NotificationsService {
Service: 1 Service: 1
}; };
tracing.setAttribute('token.data', token) tracing?.setAttribute('token.data', token)
this.http.post<Tokenn>(`${geturl}`, body, { }).subscribe(data => { this.http.post<Tokenn>(`${geturl}`, body, { }).subscribe(data => {
this.active = true this.active = true
tracing.setAttribute('outcome','success') tracing?.setAttribute('outcome','success')
tracing.finish() tracing.finish()
}, (error) => { }, (error) => {
tracing.setAttribute('postToken','failed') tracing?.setAttribute('postToken','failed')
tracing.setAttribute('outcome','failed') tracing?.setAttribute('outcome','failed')
tracing.finish() tracing.finish()
}) })
} }
@@ -493,9 +493,9 @@ export class NotificationsService {
this.zone.run(() => this.router.navigate(['/home/chat'], navigationExtras)); this.zone.run(() => this.router.navigate(['/home/chat'], navigationExtras));
}, 200); }, 200);
} else { } else {
tracing.setAttribute('notification.route', 'false') tracing?.setAttribute('notification.route', 'false')
tracing.setAttribute('outcome', 'failed') tracing?.setAttribute('outcome', 'failed')
tracing.setAttribute('parameters', JSON.stringify(notification)) tracing?.setAttribute('parameters', JSON.stringify(notification))
} }
} catch (error) { } catch (error) {
@@ -504,12 +504,12 @@ export class NotificationsService {
if(!validationError.success) { if(!validationError.success) {
const errors: z.ZodError<any> = (validationError as any).error; const errors: z.ZodError<any> = (validationError as any).error;
console.log("Validation errors:", errors.issues); console.log("Validation errors:", errors.issues);
tracing.setAttribute('validation.errors', JSON.stringify(errors.issues)) tracing?.setAttribute('validation.errors', JSON.stringify(errors.issues))
} }
tracing.setAttribute('outcome', 'failed') tracing?.setAttribute('outcome', 'failed')
tracing.setAttribute('parameters', JSON.stringify(notification)) tracing?.setAttribute('parameters', JSON.stringify(notification))
tracing.setAttribute('error', JSON.stringify(error)) tracing?.setAttribute('error', JSON.stringify(error))
} }
+46 -2
View File
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http';
import { UserSession } from '../models/user.model'; import { UserSession } from '../models/user.model';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -10,6 +10,19 @@ import { GetTasksListType } from '../models/GetTasksListType';
import { fullTaskList } from '../models/dailyworktask.model'; import { fullTaskList } from '../models/dailyworktask.model';
import { ChangeProfileService } from './change-profile.service'; import { ChangeProfileService } from './change-profile.service';
import { SessionStore } from '../store/session.service'; import { SessionStore } from '../store/session.service';
import { HttpService } from '../infra/http/http.service';
import { Result } from 'neverthrow';
import { HttpResult } from '../infra/http/type';
interface SignatureInputDTO {
InstanceId: any;
FolderId: any;
DraftIds: any;
OriginalFileName: string;
}
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@@ -22,7 +35,8 @@ export class ProcessesService {
constructor( constructor(
private http: HttpClient, private http: HttpClient,
private changeProfileService: ChangeProfileService private changeProfileService: ChangeProfileService,
private httpService: HttpService
) { ) {
this.loggeduser = SessionStore.user; this.loggeduser = SessionStore.user;
@@ -249,6 +263,36 @@ export class ProcessesService {
return this.http.post<any>(`${geturl}`, body, options) return this.http.post<any>(`${geturl}`, body, options)
} }
private baseUrl = `${environment.apiURLStage.slice(0, -1)}`;
async createSignatureV2(input: SignatureInputDTO) {
// Split DraftIds into an array
const draftIds: String[] = input.DraftIds?.split(";").filter(Boolean) || [];
if (draftIds.length === 0) {
throw new Error("No valid DraftIds provided");
}
// Run all requests in parallel
const responses: Result<HttpResult<any>, HttpErrorResponse>[] = await Promise.all(
draftIds.map(draftId =>
this.httpService.put<any>(
`${this.baseUrl}/Contents/${draftId}/signature/${SessionStore.user.UserId}`,
input
) as Promise<Result<HttpResult<any>, HttpErrorResponse>>
)
);
const failures = responses.filter(r => r.isErr() );
if (failures.length > 0) {
// You can either throw or return both successes and failures
throw new Error(
`Some requests failed draft}`
);
}
return responses;
}
CompleteTask(body: Excludetask) { CompleteTask(body: Excludetask) {
// double check // double check
+3 -1
View File
@@ -253,7 +253,9 @@ GetIdsPublicationNext(id:any){
} }
DeletePublication(folderId:any,publicationId:any){ DeletePublication(folderId:any,publicationId:any){
const geturl = environment.apiURL + 'presidentialActions/'+folderId+'/posts/'+publicationId; var baseUrl = `${environment.apiURLStage.slice(0, -1)}/PresidentialActions`;
const geturl = `${baseUrl}/${folderId}/Posts/${publicationId}`;
let params = new HttpParams(); let params = new HttpParams();
params = params.set("folderId", folderId); params = params.set("folderId", folderId);
params = params.set("id", publicationId); params = params.set("id", publicationId);
+37 -275
View File
@@ -1,71 +1,19 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import * as signalR from "@microsoft/signalr" import * as signalR from "@microsoft/signalr"
import { SessionStore } from '../store/session.service'; import { SessionStore } from '../store/session.service';
import { v4 as uuidv4 } from 'uuid' import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders, HttpEventType } from '@angular/common/http';
import { CMAPIService } from '../shared/repository/CMAPI/cmapi.service'; import { CMAPIService } from '../shared/repository/CMAPI/cmapi.service';
import { HubConnectionBuilder } from '@microsoft/signalr';
import { ok, err as Err, Result } from 'neverthrow'; import { ok, err as Err, Result } from 'neverthrow';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class SocketConnectionMCRService { export class SocketConnectionMCRService {
// private callbacks: Function[] = []
// private onDisconnect: Function[] = []
// private onConnect: Function[] = []
constructor(private http: HttpClient, private _CMAPIService: CMAPIService) { constructor(private http: HttpClient, private _CMAPIService: CMAPIService) {
window["http"] = this.http window["http"] = this.http
} }
// connect() {
// var connection = new signalR.HubConnectionBuilder()
// .withUrl("https://gdcmapi-dev.dyndns.info/FileHub", {
// accessTokenFactory: () => "Bearer "+SessionStore.user.Authorization
// }).configureLogging(signalR.LogLevel.Information)
// .build();
// connection.on("ReceiveMessage", (message) => {
// console.log("ReceiveMessage", message)
// })
// connection.onreconnected((connectionId) => {
// console.assert(connection.state === signalR.HubConnectionState.Connected);
// console.log(`Reconnected with connectionId: ${connectionId}`);
// });
// connection.start()
// .then(() => {
// console.log("SignalR connection started.");
// })
// .catch((error) => {
// console.error("Error starting SignalR connection:", error);
// });
// connection.onclose((error) => {
// connection.start()
// console.log("SignalR connection closed:", error);
// });
// }
// subscribe(callback) {
// this.callbacks.push(callback);
// }
// unsubscribe(callback) {
// this.callbacks = this.callbacks.filter(cb => cb !== callback);
// }
// onDisconnectCallback(callback) {
// this.onDisconnect.push(callback)
// }
// onConnectCallback(callback) {
// this.onConnect.push(callback)
// }
} }
class ReconnectingWebSocketSignalR { class ReconnectingWebSocketSignalR {
@@ -81,7 +29,6 @@ class ReconnectingWebSocketSignalR {
constructor() {} constructor() {}
connect() { connect() {
console.log("try to connect=================================")
this.stop = false; this.stop = false;
// Limpar a conexão anterior, se existir // Limpar a conexão anterior, se existir
@@ -98,53 +45,53 @@ class ReconnectingWebSocketSignalR {
.configureLogging(signalR.LogLevel.Information) .configureLogging(signalR.LogLevel.Information)
.build(); .build();
this.connection.start() // this.connection.start()
.then(() => { // .then(() => {
this.isOpen = true; // this.isOpen = true;
console.log('WebSocket connection established'); // console.log('WebSocket connection established');
this.onConnect.forEach(callback => callback()); // this.onConnect.forEach(callback => callback());
this.whenConnected.forEach(callback => callback()); // this.whenConnected.forEach(callback => callback());
}).catch((error) => { // }).catch((error) => {
console.error("Error starting SignalR connection:", error); // console.error("Error starting SignalR connection:", error);
// Adicione tratamento de erros detalhado conforme necessário // // Adicione tratamento de erros detalhado conforme necessário
// Exemplo: Verificar se o erro é devido à perda de conexão com a internet // // Exemplo: Verificar se o erro é devido à perda de conexão com a internet
if (error.message.includes("Failed to fetch")) { // if (error.message.includes("Failed to fetch")) {
console.error("Erro de conexão com a internet"); // console.error("Erro de conexão com a internet");
} // }
// Tentar reconectar após um atraso // // Tentar reconectar após um atraso
if (!this.stop) { // if (!this.stop) {
setTimeout(() => { // setTimeout(() => {
this.connect(); // this.connect();
}, 1000); // Ajuste o atraso conforme necessário // }, 1000); // Ajuste o atraso conforme necessário
} // }
}); // });
this.connection.on("ReceiveMessage", (message) => { // this.connection.on("ReceiveMessage", (message) => {
const data = JSON.parse(message); // const data = JSON.parse(message);
console.log("ReceiveMessage", data); // console.log("ReceiveMessage", data);
this.callbacks.forEach(callback => callback(data)); // this.callbacks.forEach(callback => callback(data));
}); // });
this.connection.onclose((error) => { // this.connection.onclose((error) => {
console.log('WebSocket connection closed..'); // console.log('WebSocket connection closed..');
this.isOpen = false; // this.isOpen = false;
this.onDisconnect.forEach(callback => callback()); // this.onDisconnect.forEach(callback => callback());
// Tentar reconectar após um atraso // // Tentar reconectar após um atraso
if (!this.stop && (!error || error.message !== "Connection stopped by client.")) { // if (!this.stop && (!error || error.message !== "Connection stopped by client.")) {
setTimeout(() => { // setTimeout(() => {
this.connect(); // this.connect();
}, 3000); // Ajuste o atraso conforme necessário // }, 3000); // Ajuste o atraso conforme necessário
} // }
}); // });
} }
commit(path): Promise<Result<true, false>> { commit(path): Promise<Result<true, false>> {
@@ -218,188 +165,6 @@ class ReconnectingWebSocketSignalR {
} }
} }
interface socketResponse {
index: string
Guid: string
IsCompleted: Boolean
}
// class ReconnectingWebSocket {
// private url: string
// private socket
// isOpen: boolean
// private callbacks: Function[] = []
// private onDisconnect: Function[] = []
// private onConnect: Function[] = []
// private whenConnected: Function[] = []
// private stop = true
// http: HttpClient = window["http"]
// constructor(url) {
// this.url = url;
// this.socket = null;
// this.isOpen = false;
// }
// connect() {
// this.socket = new WebSocket(this.url);
// this.socket.addEventListener('open', (event) => {
// this.isOpen = true;
// console.log('WebSocket connection established');
// // Example: Send a message to the server
// this.socket.send('Hello, WebSocket Server!');
// this.onConnect.forEach(callback => callback());
// this.whenConnected.forEach(callback => callback())
// });
// this.socket.addEventListener('message', (event) => {
// const data: socketResponse = JSON.parse(event.data)
// this.callbacks.forEach(callback => callback(data));
// });
// this.socket.addEventListener('close', (event) => {
// console.log('WebSocket connection closed');
// this.isOpen = false;
// this.onDisconnect.forEach(callback => callback());
// // Attempt to reconnect after a delay
// if(this.stop == false) {
// setTimeout(() => {
// this.connect();
// }, 1000); // Adjust the delay as needed
// }
// });
// }
// send(message) {
// if (this.isOpen) {
// this.socket.send(message);
// } else {
// console.error('WebSocket connection is not open. Unable to send message.');
// }
// }
// disconnect() {
// this.stop = true
// if (this.isOpen) {
// this.isOpen = false;
// this.socket.close();
// }
// }
// subscribe(callback) {
// this.callbacks.push(callback);
// }
// unsubscribe(callback) {
// this.callbacks = this.callbacks.filter(cb => cb !== callback);
// }
// onDisconnectCallback(callback) {
// this.onDisconnect.push(callback)
// }
// onConnectCallback(callback) {
// this.onConnect.push(callback)
// }
// registerWhenConnected(f: Function) {
// if(this.isOpen) {
// f();
// } else {
// this.whenConnected.push(f);
// }
// }
// }
// export class ObjectMergeNotification{
// socket = new ReconnectingWebSocket('ws://localhost:3002');
// callbacks: {[GUID: string]: Function} = {}
// runWatch = true
// CMAPIService: CMAPIService = window["CMAPIAPIRepository"]
// watchCount = 0
// constructor() {
// this.socket.onDisconnectCallback(()=> {
// console.log("run watch")
// this.runWatch = true
// this.watch()
// })
// this.socket.onConnectCallback(()=> {
// console.log("open trigger")
// this.runWatch = false
// })
// this.socket.subscribe((data: socketResponse) => {
// if(data.IsCompleted == true) {
// console.log("==================!!!====================")
// try {
// this.callbacks[data.Guid](data)
// delete this.callbacks[data.Guid]
// } catch (error) {}
// } else {
// console.log("else", data)
// }
// })
// this.watch()
// }
// connect() {
// this.socket.connect()
// }
// async watch() {
// this.watchCount = 0;
// if(this.runWatch) {
// setTimeout(async () => {
// for(const [key, funx] of Object.entries(this.callbacks)) {
// const request = await this.CMAPIService.getVideoHeader(key)
// if(request.isOk()) {
// funx()
// delete this.callbacks[key]
// }
// }
// this.watchCount++
// if(this.watchCount <= 15) {
// this.watch()
// } else {
// this.runWatch = false
// }
// }, 1000)
// } else {
// console.log("end loop============================")
// }
// }
// close() {
// this.socket.disconnect();
// this.watchCount = 0;
// this.runWatch = false
// }
// subscribe(GUID, callback:Function) {
// this.callbacks[GUID] = callback;
// }
// unsubscribe(GUID) {
// delete this.callbacks[GUID]
// }
// }
export class ObjectMergeNotification{ export class ObjectMergeNotification{
socket = new ReconnectingWebSocketSignalR() socket = new ReconnectingWebSocketSignalR()
@@ -457,9 +222,6 @@ export class ObjectMergeNotification{
}, 1000) }, 1000)
} else {
console.log("end loop============================")
} }
} }
@@ -161,9 +161,9 @@ export class EventDetailsDocumentsOptionsPage implements OnInit {
try { try {
await this.processes.presidentialActionsSignature(body).toPromise() await this.processes.createSignatureV2(body);
await this.Assinar(); //await this.Assinar();
this.TaskService.loadDiplomas(); this.TaskService.loadDiplomas();
this.goBackRoute(); this.goBackRoute();
} catch (error) { } catch (error) {
@@ -5,10 +5,10 @@ import { PublicationFolder } from 'src/app/models/publicationfolder';
import { PublicationsService } from 'src/app/services/publications.service'; import { PublicationsService } from 'src/app/services/publications.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { HttpErrorHandle } from 'src/app/services/http-error-handle.service'; import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
import { NgxMatDateFormats } from '@angular-material-components/datetime-picker'; import { NgxMatDateFormats } from '@angular-material-components/datetime-picker';
import { NGX_MAT_DATE_FORMATS } from '@angular-material-components/datetime-picker'; import { NGX_MAT_DATE_FORMATS } from '@angular-material-components/datetime-picker';
import { SessionStore } from 'src/app/store/session.service';
import { ActionRemoteRepositoryService } from 'src/app/module/actions/data/repository/action-remote-repository.service';
const CUSTOM_DATE_FORMATS: NgxMatDateFormats = { const CUSTOM_DATE_FORMATS: NgxMatDateFormats = {
parse: { parse: {
dateInput: "YYYY-MMMM-DD HH:mm" dateInput: "YYYY-MMMM-DD HH:mm"
@@ -60,9 +60,8 @@ export class EditActionPage implements OnInit {
private publicationsService: PublicationsService, private publicationsService: PublicationsService,
private toastService: ToastService, private toastService: ToastService,
private httpErrorHandle: HttpErrorHandle, private httpErrorHandle: HttpErrorHandle,
) { private actionRemoteRepository: ActionRemoteRepositoryService
) {}
}
ngOnInit() { ngOnInit() {
this.getPublicationDetail(); this.getPublicationDetail();
@@ -73,11 +72,8 @@ export class EditActionPage implements OnInit {
} }
getPublicationDetail() { getPublicationDetail() {
this.publicationsService.GetPresidentialAction(this.folderId).subscribe( res => { this.publicationsService.GetPresidentialAction(this.folderId).subscribe( res => {
this.folder = res; this.folder = res;
console.log('FOLDER',this.folder)
this.dateControlStart = new FormControl(moment(new Date(this.folder.DateBegin))); this.dateControlStart = new FormControl(moment(new Date(this.folder.DateBegin)));
this.dateControlEnd = new FormControl(moment(new Date(this.folder.DateEnd))); this.dateControlEnd = new FormControl(moment(new Date(this.folder.DateEnd)));
@@ -116,29 +112,27 @@ export class EditActionPage implements OnInit {
this.injectValidation(); this.injectValidation();
this.runValidation(); this.runValidation();
let body = {
ProcessId: this.folderId,
Description: this.folder.Description,
Detail: this.folder.Detail,
DateBegin: this.folder.DateBegin,
DateEnd: this.folder.DateEnd,
ActionType: this.folder.ActionType,
}
const loader = this.toastService.loading() const loader = this.toastService.loading()
try { var result = await this.actionRemoteRepository.update({
await this.publicationsService.UpdatePresidentialAction(body).toPromise() userId: SessionStore.user.UserId,
this.close(); description: this.folder.Description,
this.updateDesktopComponent.emit(); detail: this.folder.Detail,
this.httpErrorHandle.httpsSucessMessagge('Editar Acção') location: "string",
this.getActions.emit() dateBegin: this.folder.DateBegin,
dateEnd: this.folder.DateEnd,
actionType: "Evento",
processId: this.folder.ProcessId as any,
});
} catch (error) { if(result.isOk()) {
this.httpErrorHandle.httpStatusHandle(error) this.close();
} finally { this.httpErrorHandle.httpsSucessMessagge('Editar Acção');
loader.remove() this.getActions.emit()
} else {
this.httpErrorHandle.httpStatusHandle(result.error)
} }
loader.remove();
} }
} }
@@ -4,17 +4,6 @@
<ion-label class="title"> Nova Acção</ion-label> <br> <ion-label class="title"> Nova Acção</ion-label> <br>
<i style="margin-top: -3px; font-size: 15px;" > Campos marcados com * são obrigatórios</i> <i style="margin-top: -3px; font-size: 15px;" > Campos marcados com * são obrigatórios</i>
</div> </div>
<!-- <div class="actionType">
<ion-segment [(ngModel)]="segment" (ionChange)="segmentChanged($event)">
<ion-segment-button value="Viagem">
<ion-label>Viagem</ion-label>
</ion-segment-button>
<ion-segment-button value="Evento">
<ion-label>Evento</ion-label>
</ion-segment-button>
</ion-segment>
</div> -->
</div> </div>
</ion-header> </ion-header>
@@ -31,16 +20,6 @@
<ion-icon slot="start" src="assets/images/icons-calendar.svg"></ion-icon> <ion-icon slot="start" src="assets/images/icons-calendar.svg"></ion-icon>
</div> </div>
<div class="ion-input-class flex-grow-1"> <div class="ion-input-class flex-grow-1">
<!-- <ion-datetime
class="d-block d-md-none"
[(ngModel)]="folder.DateBegin"
placeholder="Início"
displayFormat="D MMM YYYY H:mm"
minuteValues="0,5,10,15,20,25,30,35,40,45,50,55"
monthShortNames="Jan, Fev, Mar, Abr, Mai, Jun, Jul, Aug, Sep, Out, Nov, Dez"
max="2025">
</ion-datetime> -->
<mat-form-field appearance="none" class="width-100 date-hour-picker d-md-block"> <mat-form-field appearance="none" class="width-100 date-hour-picker d-md-block">
<input matInput [ngxMatDatetimePicker]="picker1" <input matInput [ngxMatDatetimePicker]="picker1"
placeholder="Data inicio*" placeholder="Data inicio*"
@@ -68,15 +47,6 @@
<ion-icon slot="start" src="assets/images/icons-calendar.svg"></ion-icon> <ion-icon slot="start" src="assets/images/icons-calendar.svg"></ion-icon>
</div> </div>
<div class="ion-input-class d-flex flex-grow-1"> <div class="ion-input-class d-flex flex-grow-1">
<!-- <ion-datetime
class="flex-grow-1 d-md-none"
[(ngModel)]="folder.DateEnd"
placeholder="Fim"
displayFormat="D MMM YYYY H:mm"
minuteValues="0,5,10,15,20,25,30,35,40,45,50,55"
monthShortNames="Jan, Fev, Mar, Abr, Mai, Jun, Jul, Aug, Sep, Out, Nov, Dez"
max="2025">
</ion-datetime> -->
<mat-form-field appearance="none" class="width-100 date-hour-picker d-md-block"> <mat-form-field appearance="none" class="width-100 date-hour-picker d-md-block">
<input matInput [ngxMatDatetimePicker]="picker2" <input matInput [ngxMatDatetimePicker]="picker2"
@@ -19,8 +19,6 @@ const CUSTOM_DATE_FORMATS: NgxMatDateFormats = {
monthYearA11yLabel: "MMMM YYYY" monthYearA11yLabel: "MMMM YYYY"
} }
} }
@Component({ @Component({
selector: 'app-new-action', selector: 'app-new-action',
templateUrl: './new-action.page.html', templateUrl: './new-action.page.html',
@@ -1,8 +1,5 @@
<ion-header class="ion-no-border background-white px-20 pt-25"> <ion-header class="ion-no-border background-white px-20 pt-25">
<div class="title-content d-flex"> <div class="title-content d-flex">
<!-- <div class="back-icon">
<ion-icon class="font-35-rem" (click)="goBack()" slot="end" src='assets/images/icons-arrow-arrow-left.svg'></ion-icon>
</div> -->
<div class="div-title" > <div class="div-title" >
<ion-label class="title">{{ publicationTitle }}</ion-label> <br> <ion-label class="title">{{ publicationTitle }}</ion-label> <br>
<i style="margin-top: -3px; font-size: 15px;"> Campos marcados com * são obrigatórios</i> <i style="margin-top: -3px; font-size: 15px;"> Campos marcados com * são obrigatórios</i>
@@ -16,7 +13,7 @@
<div *ngIf="publicationType!='1'" class="ion-item-container" <div *ngIf="publicationType!='1'" class="ion-item-container"
[class.input-error]="Form?.get('Subject')?.invalid && validateFrom "> [class.input-error]="Form?.get('Subject')?.invalid && validateFrom ">
<ion-input autocomplete="on" autocorrect="on" spellcheck="true" [(ngModel)]="PublicationFromMvService.form.Title" name="title" <ion-input autocomplete="on" autocorrect="on" spellcheck="true" [(ngModel)]="data.title" name="title"
placeholder="Título*"></ion-input> placeholder="Título*"></ion-input>
</div> </div>
@@ -27,48 +24,47 @@
</div> </div>
<div class="ion-textarea-class flex-grow-1" <div class="ion-textarea-class flex-grow-1"
[class.input-error]="Form?.get('Message')?.invalid && validateFrom "> [class.input-error]="Form?.get('Message')?.invalid && validateFrom ">
<ion-textarea autocomplete="on" autocorrect="on" spellcheck="true" [(ngModel)]="PublicationFromMvService.form.Message" <ion-textarea autocomplete="on" autocorrect="on" spellcheck="true" [(ngModel)]="data.message"
name="description" ngDefaultControl rows="12" cols="20" placeholder="Corpo de texto*"></ion-textarea> name="description" ngDefaultControl rows="12" cols="20" placeholder="Corpo de texto*"></ion-textarea>
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="data.files.length > 0">
<!-- <div *ngIf="publication">
OriginalFileName: {{ publication.OriginalFileName }}
</div> -->
<!-- Captured -->
<div *ngIf="PublicationFromMvService.form.Files.length > 0">
<ion-label class="attached-title pb-10">Anexos</ion-label> <ion-label class="attached-title pb-10">Anexos</ion-label>
<div> <div>
<div class="d-flex" > <div class="d-flex" >
<div *ngFor="let seleted of PublicationFromMvService.form.Files.slice(0, displayLimit), let i = index" lot="start" class="mr-10"> <div *ngFor="let seleted of data.files.slice(0, displayLimit), let i = index" lot="start" class="mr-10">
<div> <div>
<div class="text-center cursor-pointer" (click)="deleteFromSeletedContent(i)" style="font-weight: 700;color: #c63527; text-align-last: right;"> <div class="text-center cursor-pointer" (click)="deleteFromSeletedContent(i, seleted)" style="font-weight: 700;color: #c63527; text-align-last: right;">
X X
</div> </div>
<div *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'video' && seleted.chucksManager"> <div *ngIf="checkFileType.checkFileType(seleted.fileExtension) == 'video' && seleted.chucksManager">
<mat-progress-bar
mode="determinate"
[style.width]="seleted.chucksManager.uploadPercentage"
></mat-progress-bar>
</div> </div>
<ion-img *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'image'" <ion-img
name="image" ngDefaultControl [src]="seleted.url" style="height: 69px; width: 69px;"></ion-img> *ngIf="checkFileType.checkFileType(seleted.fileExtension) == 'image'"
name="image"
ngDefaultControl
[src]="'data:image/' + seleted.fileExtension + ';base64,' + seleted.fileBase64"
style="height: 69px; width: 69px;">
</ion-img>
<video class="sdf" *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'video' " width="70" height="70" <video class="sdf"
preload="metadata" webkit-playsinline="webkit-playsinline"> *ngIf="checkFileType.checkFileType(seleted.fileExtension) == 'video'"
<source type="video/mp4" [src]="seleted.url"> width="70" height="70"
preload="metadata"
webkit-playsinline="webkit-playsinline"
controls>
<source
[src]="getSafeVideoUrl(seleted)"
[type]="'video/' + seleted.fileExtension">
Your browser does not support the video tag.
</video> </video>
</div> </div>
@@ -76,15 +72,10 @@
</div> </div>
</div> </div>
<!-- <button class="btn-no-color" (click)="clear()">
<ion-icon name="close"></ion-icon>
</button> -->
</div> </div>
</div> </div>
<div *ngIf="checkTableDivice()" style="display: flex;"> <div *ngIf="checkTableDivice()" style="display: flex;">
<div (click)="chossePhotoOrVideo()"> <div (click)="chossePhotoOrVideo()">
<div class="attach-icon"> <div class="attach-icon">
@@ -102,7 +93,7 @@
<div *ngIf="photoOrVideo" class="container-multiselect pt-10" style="width: 200px;"> <div *ngIf="photoOrVideo" class="container-multiselect pt-10" style="width: 200px;">
<button id="container-multiselect" class="multiselect-button" (click)="takePicture()">Fotografia</button> <button id="container-multiselect" class="multiselect-button" (click)="takePicture()">Fotografia</button>
<button id="container-multiselect" class="multiselect-button" (click)="startVideoRecording()">Video</button> <!-- <button id="container-multiselect" class="multiselect-button" (click)="startVideoRecording()">Video</button> -->
</div> </div>
@@ -1,6 +1,5 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { PublicationsService } from 'src/app/services/publications.service'; import { PublicationsService } from 'src/app/services/publications.service';
import { Publication } from 'src/app/models/publication';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ThemeService } from 'src/app/services/theme.service'; import { ThemeService } from 'src/app/services/theme.service';
@@ -9,18 +8,17 @@ import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
import { PublicationFolderService } from 'src/app/store/publication-folder.service'; import { PublicationFolderService } from 'src/app/store/publication-folder.service';
import { FilePicker } from '@capawesome/capacitor-file-picker'; import { FilePicker } from '@capawesome/capacitor-file-picker';
import { checkFileTypeService } from 'src/app/services/checkFileType.service'; import { checkFileTypeService } from 'src/app/services/checkFileType.service';
import { FileValidatorService } from "src/app/services/file/file-validator.service";
import { MiddlewareServiceService } from "src/app/shared/API/middleware/middleware-service.service";
import { LakefsRepositoryService } from '../../repository/lakefs/lakefs-repository.service';
import { CaptureImageOptions, MediaCapture } from '@awesome-cordova-plugins/media-capture/ngx'; import { CaptureImageOptions, MediaCapture } from '@awesome-cordova-plugins/media-capture/ngx';
import { Directory, Filesystem, FilesystemDirectory } from '@capacitor/filesystem'; import { Directory, Filesystem, FilesystemDirectory } from '@capacitor/filesystem';
import { ModalController, Platform } from '@ionic/angular'; import { ModalController, Platform } from '@ionic/angular';
import { PublicationAttachmentEntity } from '../upload/upload-streaming.service'; import { PublicationAttachmentEntity } from '../upload/upload-streaming.service';
import { VideoconvertService } from 'src/app/services/videoconvert.service'; import { VideoconvertService } from 'src/app/services/videoconvert.service';
import { PublicationHolderService } from 'src/app/services/publication/publication-holder.service'
import { PublicationFromMvService } from "src/app/shared/publication/upload/publication-from-mv.service" import { PublicationFromMvService } from "src/app/shared/publication/upload/publication-from-mv.service"
import { UploadStreamingService } from "src/app/shared/publication/upload/upload-streaming.service" import { UploadStreamingService } from "src/app/shared/publication/upload/upload-streaming.service"
import { PublicationCreateUseCaseService } from "src/app/core/actions/use-case/publication-create-use-case.service";
import { PublicationUpdateUseCaseService } from 'src/app/core/actions/use-case/publication-update-use-case.service';
import { PublicationFilesDeleteByPathUseCaseService } from 'src/app/core/actions/use-case/publication-files-delete-by-path-use-case.service';
import { DomSanitizer} from '@angular/platform-browser';
enum ActionType { enum ActionType {
newRapid = "1", newRapid = "1",
new = "2", new = "2",
@@ -43,7 +41,7 @@ export class NewPublicationPage implements OnInit {
Form: FormGroup; Form: FormGroup;
validateFrom = false validateFrom = false
@Input() publication!: Publication; @Input() publication!: IPublication;
@Input() publicationType: ActionType; @Input() publicationType: ActionType;
@Input() folderId: string; @Input() folderId: string;
@Input() documentId: string; @Input() documentId: string;
@@ -54,7 +52,6 @@ export class NewPublicationPage implements OnInit {
@Output() goBacktoPublicationDetails = new EventEmitter<any>(); @Output() goBacktoPublicationDetails = new EventEmitter<any>();
guestPicture: any; guestPicture: any;
capturedImage: any = ''; capturedImage: any = '';
capturedImageTitle: any = ''; capturedImageTitle: any = '';
fileType: string; fileType: string;
@@ -65,6 +62,18 @@ export class NewPublicationPage implements OnInit {
photoOrVideo: boolean = false; photoOrVideo: boolean = false;
video: any; video: any;
data: PublicationCreateInputDto & PublicationUpdateInputDto = {
userId: SessionStore.user.UserId,
dateIndex: new Date().toISOString(),
title: "",
message: "",
datePublication: new Date().toISOString(),
files: [],
organicEntityId: 101,
};
deleteFiles: IPublicationDocument[] = [];
constructor( constructor(
public PublicationFromMvService: PublicationFromMvService, public PublicationFromMvService: PublicationFromMvService,
private publications: PublicationsService, private publications: PublicationsService,
@@ -78,73 +87,63 @@ export class NewPublicationPage implements OnInit {
private platform: Platform, private platform: Platform,
private videoconvertService: VideoconvertService, private videoconvertService: VideoconvertService,
public UploadStreamingService: UploadStreamingService, public UploadStreamingService: UploadStreamingService,
private modalController: ModalController private modalController: ModalController,
private publicationCreateUseCaseService: PublicationCreateUseCaseService,
private publicationFileGetByDocumentIdService: PublicationFileGetByDocumentIdService,
private local: PublicationFileLocalRepositoryService,
private PublicationUpdateUseCaseService: PublicationUpdateUseCaseService,
private PublicationFilesDeleteByPathUseCaseService: PublicationFilesDeleteByPathUseCaseService,
public sanitizer: DomSanitizer,
) { ) {
this.publicationTitle = 'Nova Publicação'; this.publicationTitle = 'Nova Publicação';
} }
ngOnInit() { ngOnInit() {
this.PublicationFromMvService.clear()
this.setAction(); this.setAction();
this.setData() this.data.processId = this.folderId as any;
this.processData();
} }
setData() {
if(this.publication) {
this.processData(this.publication)
} else {
this.getPublicationDetail()
}
}
getPublicationDetail() { processData() {
if (this.publicationType != ActionType.new) { if (this.publicationType == ActionType.edit) {
this.showLoader = true;
this.publications.GetPublicationWithArrayOfFilesById(this.documentId).subscribe(res => {
this.processData(res)
console.log("res get", res) this.data.title = this.publication.title;
this.showLoader = false; this.data.message = this.publication.message;
}, (error) => { this.data.documentId = this.publication.documentId;
console.log(error) this.data.processId = this.publication.processId;
this.showLoader = false; this.data.datePublication = this.publication.datePublication;
this.goBack() this.data.userId = this.publication.userId;
}); this.data.organicEntityId = this.publication.organicEntityId;
} this.data.dateIndex = this.publication.dateIndex;
}
processData(res) { this.local.find({documentId: this.publication.documentId}).then(async e => {
if(e.isOk() && e.value.length == 0) {
var result = await this.publicationFileGetByDocumentIdService.execute({
documentId: this.publication.documentId,
processId: this.publication.processId,
datePublication: this.publication.datePublication
});
console.log("res process", res) if(result.isOk()) {
this.PublicationFromMvService.form.Files = [] this.data.files = result.value.added.map(file => ({
this.PublicationFromMvService.form.setData({ fileBase64: file.file,
DateIndex: res.DateIndex, fileExtension: file.extension,
DocumentId: res.DocumentId, originalFileName: "NoUploadNeed",
ProcessId: res.ProcessId, ...file
Title: res.Title, }));
Message: res.Message,
DatePublication: res.DatePublication
})
if (res.Files && Array.isArray(res?.Files)) {
const newFiles: PublicationAttachmentEntity[] = res.Files.map(e => {
return new PublicationAttachmentEntity(
{
base64: e.FileBase64,
extension: e.FileExtension,
OriginalFileName: e.OriginalFileName,
FileType: this.checkFileType.checkFileType(e.FileExtension) as any
} }
) } else if(e.isOk()) {
console.log(e.value);
this.data.files = e.value.map(file => ({
fileBase64: file.file,
fileExtension: file.extension,
originalFileName: "NoUploadNeed",
...file
}));
}
}) })
for(const files of newFiles) {
this.PublicationFromMvService.form.Files.push(files)
}
} }
} }
async takePicture() { async takePicture() {
@@ -159,27 +158,6 @@ export class NewPublicationPage implements OnInit {
this.capturedImageTitle = 'foto'; this.capturedImageTitle = 'foto';
this.showCroppModal() this.showCroppModal()
/* if(validation.isOk) { */
/* const compressedImage = await this.compressImageBase64(
this.capturedImage,
800, // maxWidth
800, // maxHeight
0.9 // quality
).then((picture) => {
this.photoOrVideo = false;
const FileExtension = this.removeTextBeforeSlash('jpeg', '/')
const newAttachment = new PublicationAttachmentEntity(
{
base64: picture,
extension: FileExtension,
OriginalFileName: "foto",
FileType: 'image'
}
)
this.PublicationFromMvService.form.Files.push(newAttachment)
}); */
} }
async laodPicture() { async laodPicture() {
@@ -211,8 +189,6 @@ export class NewPublicationPage implements OnInit {
FileType: 'image' FileType: 'image'
} }
) )
//newAttachment.needUpload();
this.PublicationFromMvService.form.Files.push(newAttachment) this.PublicationFromMvService.form.Files.push(newAttachment)
}); });
@@ -225,8 +201,6 @@ export class NewPublicationPage implements OnInit {
multiple: true, multiple: true,
}); });
console.log(result)
result.files.forEach(async blobFile => { result.files.forEach(async blobFile => {
console.log(blobFile) console.log(blobFile)
if (this.checkFileType.checkFileType(blobFile.mimeType) == 'image') { if (this.checkFileType.checkFileType(blobFile.mimeType) == 'image') {
@@ -241,31 +215,16 @@ export class NewPublicationPage implements OnInit {
}).catch((erro) => { }).catch((erro) => {
console.log(erro) console.log(erro)
}) })
} else if (this.checkFileType.checkFileType(blobFile.mimeType) == 'video'){ } else if (this.checkFileType.checkFileType(blobFile.mimeType) == 'video') {
let convertedVideo = await this.videoconvertService.convertVideoWeb(blobFile.blob,"src/assets/videos/","output",'mp4')
this.convertBlobToBase64(blobFile.blob).then((value: string) => { this.convertBlobToBase64(blobFile.blob).then((value: string) => {
this.filesSizeSum = this.filesSizeSum + blobFile.size this.data.files.push({
fileBase64: this.removeTextBeforeSlash(value, ','),
fileExtension: 'mp4',
originalFileName: "image",
})
const FileExtension = this.removeTextBeforeSlash(blobFile.mimeType, '/')
const file = new File([blobFile.blob], blobFile.name);
const newAttachment = new PublicationAttachmentEntity(
{
base64: this.removeTextBeforeSlash(value, ','),
extension: 'mp4',
blobFile: file,
FileType: this.checkFileType.checkFileType(FileExtension) as any,
OriginalFileName: 'load video'
}
)
newAttachment.needUpload()
this.PublicationFromMvService.form.Files.push(newAttachment)
this.filecontent = true;
}).catch((erro) => { }).catch((erro) => {
console.log(erro) console.log(erro)
}) })
@@ -277,6 +236,11 @@ export class NewPublicationPage implements OnInit {
}); });
} }
getSafeVideoUrl(file: { fileExtension: string, fileBase64: string }): SafeResourceUrl {
const unsafeUrl = 'data:video/' + file.fileExtension + ';base64,' + file.fileBase64;
return this.sanitizer.bypassSecurityTrustResourceUrl(unsafeUrl);
}
async loadVideoTablet() { async loadVideoTablet() {
const result = await FilePicker.pickMedia const result = await FilePicker.pickMedia
@@ -340,11 +304,10 @@ export class NewPublicationPage implements OnInit {
injectValidation() { injectValidation() {
this.Form = new FormGroup({ this.Form = new FormGroup({
Subject: new FormControl(this.PublicationFromMvService.form.Title, [ Subject: new FormControl(this.data.title, [
Validators.required, Validators.required,
// Validators.minLength(4)
]), ]),
Message: new FormControl(this.PublicationFromMvService.form.Message, [ Message: new FormControl(this.data.message, [
Validators.required, Validators.required,
Validators.maxLength(1000) Validators.maxLength(1000)
]) ])
@@ -361,12 +324,24 @@ export class NewPublicationPage implements OnInit {
return false return false
} }
this.PublicationFromMvService.setFolderId(this.folderId) this.data.datePublication = new Date().toISOString();
this.PublicationFromMvService.setFolderId(this.folderId)
this.goBack();
await this.PublicationFromMvService.save()
// this.PublicationHolderService.setPublication(this.PublicationFromMvService) if (this.publicationType == ActionType.edit) {
this.data.files = this.data.files.filter(e => e.originalFileName != 'NoUploadNeed');
this.PublicationUpdateUseCaseService.execute(this.data).then(async res => {
for(var e of this.deleteFiles) {
await this.PublicationFilesDeleteByPathUseCaseService.execute(e)
}
this.PublicationFolderService.loadPublications(this.folderId as any);
});
} else {
this.publicationCreateUseCaseService.execute(this.data).then(res => {
this.PublicationFolderService.loadPublications(this.folderId as any);
});
}
this.goBack();
} }
ngOnDestroy() { ngOnDestroy() {
@@ -409,14 +384,7 @@ export class NewPublicationPage implements OnInit {
} }
async goBack() { async goBack() {
this.goBackToViewPublications.emit();
if (this.publicationType == ActionType.new) {
this.goBackToViewPublications.emit();
} else {
this.goBackToViewPublications.emit();
//this.goBacktoPublicationDetails.emit();
}
} }
async compressImageBase64(base64String: string, maxWidth: number, maxHeight: number, quality: number): Promise<string> { async compressImageBase64(base64String: string, maxWidth: number, maxHeight: number, quality: number): Promise<string> {
@@ -496,19 +464,6 @@ export class NewPublicationPage implements OnInit {
} }
} }
/* convertBlobToBase64(blob: Blob) {
console.log('Convert blob ',blob)
return new Promise((resolve, reject) => {
const reader = new FileReader;
reader.onerror = reject;
reader.onload = () => {
resolve(reader.result)
}
reader.readAsDataURL(blob)
},)
} */
getBase64(file) { getBase64(file) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
@@ -632,8 +587,13 @@ export class NewPublicationPage implements OnInit {
return parseInt(sizeInMB) return parseInt(sizeInMB)
} }
deleteFromSeletedContent(index) { deleteFromSeletedContent(index, file?: IPublicationDocument) {
this.PublicationFromMvService.form.Files.splice(index, 1) this.data.files.splice(index, 1)
if(this.publicationType == ActionType.edit) {
this.deleteFiles.push(file!);
}
} }
chossePhotoOrVideo() { chossePhotoOrVideo() {
@@ -781,16 +741,13 @@ console.log(stringGerada);
this.filecontent = true; this.filecontent = true;
this.photoOrVideo = false; this.photoOrVideo = false;
const newAttachment = new PublicationAttachmentEntity( // console.log(res.data.base64ToCroppe);
{
base64: res.data.base64ToCroppe,
extension: 'jpeg',
OriginalFileName: "image",
FileType: 'image'
}
)
this.PublicationFromMvService.form.Files.push(newAttachment) this.data.files.push({
fileBase64: res.data.base64ToCroppe.split(",")[1],
fileExtension: 'jpeg',
originalFileName: "image",
})
} }
}, (error) => { }, (error) => {
console.log(error) console.log(error)
@@ -798,14 +755,6 @@ console.log(stringGerada);
await modal.present(); await modal.present();
// let fileObject = new PublicationAttachmentEntity({
// base64: this.removeTextBeforeSlash(this.capturedImage, ',') ,
// extension: 'jpeg',
// OriginalFileName: 'video',
// FileType: 'image'
// })
// this.PublicationFromMvService.form.Files.push(fileObject)
} }
} }
@@ -814,6 +763,15 @@ console.log(stringGerada);
import { Observable, of, Subject } from 'rxjs'; import { Observable, of, Subject } from 'rxjs';
import { tap, switchMap, delay, map } from 'rxjs/operators'; import { tap, switchMap, delay, map } from 'rxjs/operators';
import { CropImagePage } from 'src/app/modals/crop-image/crop-image.page'; import { CropImagePage } from 'src/app/modals/crop-image/crop-image.page';
import { PublicationCreateInputDto } from 'src/app/core/actions/use-case/publication-create-use-case.service';
import { SessionStore } from 'src/app/store/session.service';
import { IPublication } from 'src/app/core/actions/entity/publicationEntity';
import { PublicationFileGetByDocumentIdService } from 'src/app/core/actions/use-case/publication-file-get-by-document-id.service';
import { PublicationFileLocalRepositoryService } from 'src/app/module/actions/data/repository/publication-file-local-repository.service';
import { PublicationUpdateInputDto } from 'src/app/core/actions/use-case/publication-update-use-case.service';
import { days } from '../../../models/agenda/AgendaEventList';
import { IPublicationDocument } from 'src/app/core/actions/entity/publicationDocument';
import { SafeResourceUrl } from '@angular/platform-browser';
function shareResponse(): MethodDecorator { function shareResponse(): MethodDecorator {
return function ( return function (
@@ -9,10 +9,7 @@ import { ObjectMergeNotification } from 'src/app/services/socket-connection-mcr.
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { Result } from 'neverthrow'; import { Result } from 'neverthrow';
import { IPublicationFormModelEntity } from '../new-publication/interface/interface'; import { IPublicationFormModelEntity } from '../new-publication/interface/interface';
import { CMAPIService } from "src/app/shared/repository/CMAPI/cmapi.service" import { ModalController, Platform } from '@ionic/angular';
import { App } from '@capacitor/app';
import { ModalController, NavParams, Platform, LoadingController } from '@ionic/angular';
import { Router } from '@angular/router';
enum ActionType { enum ActionType {
newRapid = "1", newRapid = "1",
@@ -33,23 +30,16 @@ export class PublicationFromMvService {
publicationType: ActionType publicationType: ActionType
folderId: string folderId: string
constructor( constructor(
private publications: PublicationsService, private publications: PublicationsService,
private toastService: ToastService, private toastService: ToastService,
private httpErroHandle: HttpErrorHandle, private httpErroHandle: HttpErrorHandle,
public PublicationFolderService: PublicationFolderService, public PublicationFolderService: PublicationFolderService,
private CMAPIService: CMAPIService,
public publicationFolderService: PublicationFolderService, public publicationFolderService: PublicationFolderService,
private platform: Platform, private platform: Platform,
private modalController: ModalController,
private router: Router,
) {} ) {}
clear() { clear() {
this.id = uuidv4() this.id = uuidv4()
this.UploadFileUseCase = new UploadFileUseCase() this.UploadFileUseCase = new UploadFileUseCase()
this.form = new PublicationFormModel() this.form = new PublicationFormModel()
@@ -65,7 +55,6 @@ export class PublicationFromMvService {
window['upload-header-set-remove'](this.id) window['upload-header-set-remove'](this.id)
} }
setFolderId(folderId) { setFolderId(folderId) {
this.folderId = folderId this.folderId = folderId
} }
@@ -99,7 +88,6 @@ export class PublicationFromMvService {
e.FileExtension = e.FileExtension || "mp4" e.FileExtension = e.FileExtension || "mp4"
e.Base64 = '' e.Base64 = ''
if(e.FileType == 'video' && !e.toUpload) { if(e.FileType == 'video' && !e.toUpload) {
e.Base64 = e.url e.Base64 = e.url
} }
@@ -142,7 +130,6 @@ export class PublicationFromMvService {
window['publicationEdit']() window['publicationEdit']()
} }
// this.publicationFolderService.getPublicationsIds(this.folderId)
window['upload-header-set-remove'](this.id); window['upload-header-set-remove'](this.id);
} catch (error) { } catch (error) {
@@ -153,23 +140,16 @@ export class PublicationFromMvService {
} else { } else {
window['upload-header-set-retry'](this.id) window['upload-header-set-retry'](this.id)
} }
} finally { } finally {}
// loader.remove()
}
} else { } else {
this.toastService._badRequest("É necessário adicionar uma imagem ou vídeo") this.toastService._badRequest("É necessário adicionar uma imagem ou vídeo")
} }
} }
else { else {
let time = new Date()
if (this.form.Files.length >= 1) { if (this.form.Files.length >= 1) {
// const loader = this.toastService.loading()
this.form.send = true this.form.send = true
const upload = await this.uploadVideosFiles() const upload = await this.uploadVideosFiles()
@@ -180,8 +160,6 @@ export class PublicationFromMvService {
window['upload-header-set-percentage'](this.id, 100) window['upload-header-set-percentage'](this.id, 100)
} }
console.log('release chunk')
if(upload) { if(upload) {
this.form.Files = this.form.Files.map((e:PublicationAttachmentEntity) => { this.form.Files = this.form.Files.map((e:PublicationAttachmentEntity) => {
if(e.FileType == 'video' && e.toUpload) { if(e.FileType == 'video' && e.toUpload) {
@@ -194,18 +172,14 @@ export class PublicationFromMvService {
e.Base64 = e.url e.Base64 = e.url
} }
return e return e
}) })
} else { } else {
window['upload-header-set-retry'](this.id) window['upload-header-set-retry'](this.id)
this.toastService._badRequest("ocorreu um erro ao enviar o ficheiro") this.toastService._badRequest("ocorreu um erro ao enviar o ficheiro")
return true return true
// loader.remove()
} }
const publication: any = Object.assign({}, this.form) const publication: any = Object.assign({}, this.form)
publication.Files = publication.Files.map( (e:PublicationAttachmentEntity) => ({ publication.Files = publication.Files.map( (e:PublicationAttachmentEntity) => ({
@@ -228,27 +202,18 @@ export class PublicationFromMvService {
this.httpErroHandle.httpsSucessMessagge('Criar publicação') this.httpErroHandle.httpsSucessMessagge('Criar publicação')
// this.goBackToViewPublications.emit();
window['upload-header-set-remove'](this.id); window['upload-header-set-remove'](this.id);
this.doneUpload() this.doneUpload()
this.publicationFolderService.loadPublication(publicationsId, this.folderId) this.publicationFolderService.loadPublication(publicationsId, this.folderId)
} catch (error) { } catch (error) {
window['upload-header-set-retry'](this.id) window['upload-header-set-retry'](this.id)
this.httpErroHandle.httpStatusHandle(error) this.httpErroHandle.httpStatusHandle(error)
} finally {
// loader.remove()
} }
} else { } else {
this.toastService._badRequest("É necessário adicionar uma imagem ou vídeo") this.toastService._badRequest("É necessário adicionar uma imagem ou vídeo")
} }
} }
// this.PublicationHolderService.setPublication(this.publicationFormMV)
this.ObjectMergeNotification.close() this.ObjectMergeNotification.close()
} }
@@ -1,5 +1,5 @@
<ion-header class="ion-no-border"> <ion-header class="ion-no-border">
<div *ngIf="publication.title != ''" class="title-content d-flex pl-20 pt-25"> <div *ngIf="publication != null " class="title-content d-flex pl-20 pt-25">
<div class="back-icon cursor-pointer" (click)="goBack()"> <div class="back-icon cursor-pointer" (click)="goBack()">
<ion-icon *ngIf="ThemeService.currentTheme == 'default' " slot="end" src='assets/images/icons-arrow-arrow-left.svg'></ion-icon> <ion-icon *ngIf="ThemeService.currentTheme == 'default' " slot="end" src='assets/images/icons-arrow-arrow-left.svg'></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'gov' " slot="end" src='assets/images/theme/gov/icons-calendar-arrow-left.svg'></ion-icon> <ion-icon *ngIf="ThemeService.currentTheme == 'gov' " slot="end" src='assets/images/theme/gov/icons-calendar-arrow-left.svg'></ion-icon>
@@ -10,7 +10,7 @@
<p class="post-data">{{publication.datePublication | date: 'dd-MM-yyyy | h:mm'}}</p> <p class="post-data">{{publication.datePublication | date: 'dd-MM-yyyy | h:mm'}}</p>
</div> </div>
</div> </div>
<div *ngIf="publication.title == ''" class="title-contentd-flex pl-20 pt-25"> <div *ngIf="publication != null" class="title-contentd-flex pl-20 pt-25">
<div class="back-icon cursor-pointer"> <div class="back-icon cursor-pointer">
<ion-icon (click)="goBack()" slot="end" src='assets/images/assets/images/theme/gov/icons-calendar-arrow-left.svg'></ion-icon> <ion-icon (click)="goBack()" slot="end" src='assets/images/assets/images/theme/gov/icons-calendar-arrow-left.svg'></ion-icon>
</div> </div>
@@ -24,7 +24,7 @@
<ion-content> <ion-content>
<div class="content-container" #ScrollContainer> <div class="content-container" #ScrollContainer>
<div *ngIf="publication.title != ''"> <div *ngIf="publication != null">
<div class="post-item px-20"> <div class="post-item px-20">
<div style="width: 100%; height: 395px;overflow:hidden"> <div style="width: 100%; height: 395px;overflow:hidden">
@@ -13,10 +13,8 @@ import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
import { PublicationFolderService } from 'src/app/store/publication-folder.service'; import { PublicationFolderService } from 'src/app/store/publication-folder.service';
import { AskModalPage } from 'src/app/modals/ask-modal/ask-modal.page'; import { AskModalPage } from 'src/app/modals/ask-modal/ask-modal.page';
import { checkFileTypeService } from 'src/app/services/checkFileType.service'; import { checkFileTypeService } from 'src/app/services/checkFileType.service';
import { PublicationVideoManagerService } from "src/app/services/publication/publication-video-manager.service";
import { StopvideoService } from "src/app/services/stopvideo.service"; import { StopvideoService } from "src/app/services/stopvideo.service";
import { PublicationHolderService } from 'src/app/services/publication/publication-holder.service' import { PublicationHolderService } from 'src/app/services/publication/publication-holder.service'
import { PublicationListByProcessIdService } from 'src/app/core/actions/use-case/publication-list-by-process-id.service'
@Component({ @Component({
selector: 'app-view-publications', selector: 'app-view-publications',
templateUrl: './view-publications.page.html', templateUrl: './view-publications.page.html',
@@ -71,10 +69,8 @@ export class ViewPublicationsPage implements OnInit {
private httpErrorHandle: HttpErrorHandle, private httpErrorHandle: HttpErrorHandle,
public publicationFolderService: PublicationFolderService, public publicationFolderService: PublicationFolderService,
public checkFileType: checkFileTypeService, public checkFileType: checkFileTypeService,
private publicationVideoManagerService: PublicationVideoManagerService,
public StopvideoService: StopvideoService, public StopvideoService: StopvideoService,
public PublicationHolderService: PublicationHolderService, public PublicationHolderService: PublicationHolderService
private publicationListByProcessIdService: PublicationListByProcessIdService
) { ) {
this.createPublicationList() this.createPublicationList()
} }
@@ -116,11 +112,6 @@ export class ViewPublicationsPage implements OnInit {
this.getPublicationDetail(); this.getPublicationDetail();
this.getPublicationsIds(); this.getPublicationsIds();
this.stopVideo(); this.stopVideo();
setTimeout(() => {
this.doRefresh({})
}, 1500);
} }
@@ -136,6 +127,7 @@ export class ViewPublicationsPage implements OnInit {
doRefresh = (event) => { doRefresh = (event) => {
this.publicationFolderService.loadPublications(this.folderId);
this.getPublicationDetail(); this.getPublicationDetail();
this.getPublicationsIds(); this.getPublicationsIds();
} }
+24 -23
View File
@@ -7,32 +7,33 @@
[src]="'data:image/jpg;base64,' + files.file" loading="lazy"> [src]="'data:image/jpg;base64,' + files.file" loading="lazy">
<video #videoElements [appVisibility]="onVisibilityChange" *ngIf="checkFileType.checkFileType(files.extension ) == 'video'" class="post-video" controls="controls" preload="none" <video
playsinline webkit-playsinline="webkit-playsinline" (play)="stopvideoService.registerVideoWithEvent($event)"q> #videoElements
<source [appVisibility]="onVisibilityChange"
*ngIf="files.extension === 'MP4'" *ngIf="checkFileType.checkFileType(files.extension) == 'video'"
[src]="'data:video/mp4;base64,' + files.file" class="post-video"
type="video/mp4" controls
> preload="metadata"
<source playsinline
*ngIf="files.extension === 'MOV'" webkit-playsinline
[src]="'data:video/quicktime;base64,' + files.file" (loadeddata)="videoElements.currentTime = 0.1"
type="video/quicktime" (play)="stopvideoService.registerVideoWithEvent($event)"
> >
<source
*ngIf="files.extension === 'mp4'"
[src]="'data:video/mp4;base64,' + files.file"
type="video/mp4"
>
<source
*ngIf="files.extension === 'MOV'"
[src]="'data:video/quicktime;base64,' + files.file"
type="video/quicktime"
>
</video> </video>
</div> </div>
</swiper-slide> </swiper-slide>
</swiper-container> </swiper-container>
</div> </div>
<ion-footer>
<div *ngIf="pagination && publicationList?.length > 1" class="dots-container">
<span *ngFor="let files of publicationList; let k = index"
[class.dotsSwiper]="true"
[class.active-dot]="swiperIndex === k"
(click)="goToSlide(k)"
>
</span>
</div>
</ion-footer>
+14 -1
View File
@@ -64,8 +64,21 @@ export class SwiperPage implements OnInit {
} }
} else if(e.isOk()) { } else if(e.isOk()) {
this.publicationList = e.value; this.publicationList = e.value;
var a = e.value.find(e => e.datePublication != this.datePublication)
if(a) {
var result = await this.publicationFileGetByDocumentIdService.execute({
documentId: this.documentId,
processId: this.processId,
datePublication: this.datePublication
});
if(result.isOk()) {
this.publicationList = result.value.added;
}
}
} }
}) });
} }
ngOnChanges() { ngOnChanges() {
+18 -10
View File
@@ -41,41 +41,49 @@ export class PublicationFolderService {
async loadPublications(processId: number) { async loadPublications(processId: number) {
var id = processId+0
this.showLoader = true; this.showLoader = true;
if (!this.publications[processId]) { if (!this.publications[id]) {
this.publications[processId] = [] this.publications[id] = []
const result = await this.publicationLocalRepo.find({processId}); const result = await this.publicationLocalRepo.find({processId: id});
if(result.isOk()) { if(result.isOk()) {
this.publications[processId] = result.value; this.publications[id] = result.value;
} }
} }
const result = await this.publicationListByProcessIdService.execute(processId); const result = await this.publicationListByProcessIdService.execute(id);
if(result.isOk()) { if(result.isOk()) {
for(const item of result.value.added) { for(const item of result.value.added) {
this.publications[processId].push(item); this.publications[id].push(item);
} }
// handle removed // handle removed
for (const item of result.value.remove) { for (const item of result.value.remove) {
this.publications[processId] = this.publications[processId].filter(f => f.documentId !== item.documentId); this.publications[id] = this.publications[id].filter(f => f.documentId !== item.documentId);
} }
// handle updated // handle updated
for (const item of result.value.updated) { for (const item of result.value.updated) {
const index = this.publications[processId].findIndex(f => f.documentId === item.documentId); const index = this.publications[id].findIndex(f => f.documentId === item.documentId);
if (index !== -1) { if (index !== -1) {
this.publications[processId][index] = item; // replace with updated version this.publications[id][index] = item; // replace with updated version
} else { } else {
this.publications[processId].push(item); // if not found, just add it this.publications[id].push(item); // if not found, just add it
} }
} }
} }
this.publications[id] = this.publications[id].sort((a, b) => {
const dateA = new Date(a.datePublication).getTime();
const dateB = new Date(b.datePublication).getTime();
return dateB - dateA; // Revertendo a ordem aqui
});
this.showLoader = false; this.showLoader = false;
} }
+2 -3
View File
@@ -42,8 +42,7 @@ import { isHttpError } from 'src/app/services/http.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { NotificationRepositoryService } from 'src/app/module/notification/data/notification-repository.service'; import { NotificationRepositoryService } from 'src/app/module/notification/data/notification-repository.service';
import { EEventFilterStatus } from 'src/app/module/agenda/data/dto/enums'; import { EEventFilterStatus } from 'src/app/module/agenda/data/dto/enums';
// import { Unsubscribable } from '../../../../android/app/build/intermediates/assets/debug/public/assets/dexie/dist/dexie'; import { ProcessesService } from 'src/app/services/processes.service'
@Component({ @Component({
selector: 'app-agenda', selector: 'app-agenda',
templateUrl: './agenda.page.html', templateUrl: './agenda.page.html',
@@ -204,7 +203,7 @@ export class AgendaPage implements OnInit {
public RoleIdService: RoleIdService, public RoleIdService: RoleIdService,
public AgendaDataRepositoryService: AgendaDataRepositoryService, public AgendaDataRepositoryService: AgendaDataRepositoryService,
private toastService: ToastService, private toastService: ToastService,
private notificationRepository: NotificationRepositoryService private notificationRepository: NotificationRepositoryService,
) { ) {
this.NotificationUpdate.pipe( this.NotificationUpdate.pipe(
+5 -12
View File
@@ -86,7 +86,7 @@
<!-- {{ expirationDate[room.$id] !== null ? expirationDate[room.$id] + ' seconds left' : 'No expiration' }} --> <!-- {{ expirationDate[room.$id] !== null ? expirationDate[room.$id] + ' seconds left' : 'No expiration' }} -->
<span *ngIf="expirationDate[room.$id]"> {{ expirationDate[room.$id] !== null ? expirationDate[room.$id] + ' seconds left' : '' }} </span> <span *ngIf="expirationDate[room.$id]"> {{ expirationDate[room.$id] !== null ? expirationDate[room.$id] + ' seconds left' : '' }} </span>
</div> </div>
<div *ngIf="room?.messages?.length >= 1 && room.messages[0].isDeleted!=true" class="item-description d-flex align-items-center" [class.item-description-active]="room.$id ==selectedRoomId"> <div *ngIf="room?.messages?.length >= 1 && room.messages[0]._isDeleted!=true" class="item-description d-flex align-items-center" [class.item-description-active]="room.$id ==selectedRoomId">
<!-- --> <!-- -->
<!-- <div *ngIf="room?.messages?.[0]?.message" class="item-description d-flex align-items-center" [class.item-description-active]="room.$id ==selectedRoomId"> --> <!-- <div *ngIf="room?.messages?.[0]?.message" class="item-description d-flex align-items-center" [class.item-description-active]="room.$id ==selectedRoomId"> -->
<!-- <div class="item-message" *ngIf="group.otherUserType == false">{{room?.messages?.[0]?.message.u.name}}: {{room?.messages?.[0]?.message.msg}} </div> --> <!-- <div class="item-message" *ngIf="group.otherUserType == false">{{room?.messages?.[0]?.message.u.name}}: {{room?.messages?.[0]?.message.msg}} </div> -->
@@ -94,7 +94,6 @@
<!-- <div *ngIf="group.otherUserType == true">{{group.userThatIsTyping}} está escrever ...</div> --> <!-- <div *ngIf="group.otherUserType == true">{{group.userThatIsTyping}} está escrever ...</div> -->
<div class="item-files add-ellipsis" *ngIf="room.messages[0]?.attachments?.length >= 1"> <div class="item-files add-ellipsis" *ngIf="room.messages[0]?.attachments?.length >= 1">
<fa-icon *ngIf="room.lastMessageDocument" icon="file-alt" class="file-icon" [class.set-active-item-font-to-white]="room.$id == selectedRoomId"></fa-icon> <fa-icon *ngIf="room.lastMessageDocument" icon="file-alt" class="file-icon" [class.set-active-item-font-to-white]="room.$id == selectedRoomId"></fa-icon>
<fa-icon *ngIf="room.lastMessageAudio" icon="file-audio" class="file-icon" [class.set-active-item-font-to-white]="room.$id == selectedRoomId"></fa-icon> <fa-icon *ngIf="room.lastMessageAudio" icon="file-audio" class="file-icon" [class.set-active-item-font-to-white]="room.$id == selectedRoomId"></fa-icon>
<span *ngIf="room.lastMessageAudio" class="item-files-title font-13-em"> audio </span> <span *ngIf="room.lastMessageAudio" class="item-files-title font-13-em"> audio </span>
@@ -103,20 +102,14 @@
<span class="pl-2 font-13-em add-ellipsis" *ngIf="!room.lastMessageAudio && !room.lastMessageImage">{{ room.messages[0].attachments[0].description }}</span> <span class="pl-2 font-13-em add-ellipsis" *ngIf="!room.lastMessageAudio && !room.lastMessageImage">{{ room.messages[0].attachments[0].description }}</span>
</div> </div>
<!-- <div class="item-files" *ngIf="room.attachments">
<div *ngIf="room.value.lastMessage.attachments[0].image_url">
<fa-icon icon="image" class="file-icon" [class.set-active-item-font-to-white]="room.$id == selectedRoomId"></fa-icon>
<span> Fotografia</span>
</div>
</div> -->
</div> </div>
<div *ngIf="room?.messages?.length >= 1 && room.messages[0].isDeleted==true"> <div *ngIf="room?.messages?.length >= 1 && room.messages[0]._isDeleted==true && room.messages[0].oneShot == false">
<div class="item-message font-13-em add-ellipsis white-space-nowrap"> Mensagem foi eliminada </div> <div class="item-message font-13-em add-ellipsis white-space-nowrap"> Mensagem foi eliminada </div>
</div> </div>
<!-- --> <div *ngIf="room?.messages?.length >= 1 && room.messages[0]._isDeleted==true && room.messages[0].oneShot == true">
<div class="item-message font-13-em add-ellipsis white-space-nowrap"> Mensagem de visualizada </div>
</div>
<!-- <div class="item-message font-13-em add-ellipsis white-space-nowrap" *ngIf="room.otherUserType == false"> {{room.messages[0].message}} </div> -->
<!-- <div class="font-13-em" *ngIf="room.otherUserType == true">está escrever ...</div> -->
</div> </div>
</div> </div>
</div> </div>
+7 -1
View File
@@ -27,6 +27,7 @@ import { MessageLocalDataSourceService } from 'src/app/module/chat/data/reposito
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { Logger } from 'src/app/services/logger/main/service'; import { Logger } from 'src/app/services/logger/main/service';
import { IDBoolean } from 'src/app/infra/database/dexie/type'; import { IDBoolean } from 'src/app/infra/database/dexie/type';
import { MessageViewModal } from './store/model/message';
@Component({ @Component({
selector: 'app-chat', selector: 'app-chat',
templateUrl: './chat.page.html', templateUrl: './chat.page.html',
@@ -156,7 +157,12 @@ export class ChatPage implements OnInit {
// ]); // ]);
this.roomLocalDataSourceService.getItemsLive().pipe( this.roomLocalDataSourceService.getItemsLive().pipe(
map((roomList) => roomList.map((room)=> new RoomViewModel(room))), map((roomList) => roomList.map((room)=> {
room.messages = room.messages.map( e => {
return new MessageViewModal(e);
});
return new RoomViewModel(room);
})),
tap((roomList) => { tap((roomList) => {
this.updatemessage(roomList) this.updatemessage(roomList)
}) })
@@ -10,6 +10,8 @@ import { ZodError } from 'zod';
import { ChatServiceService } from 'src/app/module/chat/domain/chat-service.service'; import { ChatServiceService } from 'src/app/module/chat/domain/chat-service.service';
import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository'; import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository';
import { GetRoomListUseCaseService } from 'src/app/core/chat/usecase/room/room-get-list-use-case.service';
import { GetRoomByIdUseCaseService } from 'src/app/core/chat/usecase/room/room-get-by-id-use-case.service';
@Component({ @Component({
@@ -39,6 +41,8 @@ export class EditGroupPage implements OnInit {
private toastService: ToastService, private toastService: ToastService,
private chatServiceService: ChatServiceService, private chatServiceService: ChatServiceService,
private RoomLocalDataSourceService: IRoomLocalRepository, private RoomLocalDataSourceService: IRoomLocalRepository,
private GetRoomListUseCaseService: GetRoomListUseCaseService,
private getRoomByIdUseCaseService: GetRoomByIdUseCaseService
) {} ) {}
ngOnInit() { ngOnInit() {
@@ -75,6 +79,9 @@ export class EditGroupPage implements OnInit {
roomType: 0 roomType: 0
}) })
this.GetRoomListUseCaseService.execute();
//this.getRoomByIdUseCaseService.execute(this.roomId);
if(result.isOk()) { if(result.isOk()) {
this.openGroupMessage.emit(this.roomId); this.openGroupMessage.emit(this.roomId);
} else if (result.error instanceof HttpResponse) { } else if (result.error instanceof HttpResponse) {
@@ -84,7 +91,6 @@ export class EditGroupPage implements OnInit {
console.log(result.error.errors) console.log(result.error.errors)
} else { } else {
this.toastService._badRequest("Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico.") this.toastService._badRequest("Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico.")
console.log(result.error)
} }
this.showLoader = false this.showLoader = false
@@ -152,16 +152,21 @@
</div> </div>
</div> </div>
<div class="message-container rotate-div" *ngIf="message.isDeleted == true"> <div class="message-container rotate-div" *ngIf="message._isDeleted == true && message.id != '' && message.oneShot == false">
Mensagem foi eliminada Mensagem foi eliminada
</div> </div>
<div class="message-container rotate-div" *ngIf="message._isDeleted == true && message.id != '' && message.oneShot == true">
Mensagem de visualizada
</div>
<div *ngIf="message.messageType == IMessageType.information && !message.ballon" class="text-center"> <div *ngIf="message.messageType == IMessageType.information && !message.ballon" class="text-center">
{{ message.message }} {{ message.message }}
</div> </div>
<div *ngIf="message.ballon" class="text-center ballon"> <div *ngIf="message.ballon" class="text-center ballon">
{{ message.message }} {{ message.message }}
</div> </div>
<div class="rotate-div emoji-container" *ngIf="message.isDeleted != true && message.messageType != IMessageType.information && message.ballon != true"> <div class="rotate-div emoji-container" *ngIf="message._isDeleted != true && message.messageType != IMessageType.information && message.ballon != true">
<span *ngFor="let reaction of message.reactions" class="emoji-icon"> <span *ngFor="let reaction of message.reactions" class="emoji-icon">
{{ reaction.reaction }} {{ reaction.reaction }}
</span> </span>
@@ -232,7 +237,7 @@
<div class="width-100"> <div class="width-100">
<div *ngIf="!recording && !lastAudioRecorded" class="type-message"> <div *ngIf="!recording && !lastAudioRecorded" class="type-message">
<ion-textarea *ngIf="allowTyping" (keyup.enter)="sendMessage()" clearOnEdit="true" placeholder="Escrever uma mensagem" class="message-input" rows="1" [(ngModel)]="textField" (ionChange)="sendTyping()" (click)="sendReadAt()"></ion-textarea> <ion-textarea *ngIf="allowTyping" (keyup.enter)="sendMessage()" clearOnEdit="true" placeholder="Escrever uma mensagem" class="message-input" rows="1" [(ngModel)]="textField" (ionChange)="sendTyping()"></ion-textarea>
</div> </div>
<div *ngIf="recording" class="d-flex align-items-center justify-content-center"> <div *ngIf="recording" class="d-flex align-items-center justify-content-center">
<button (click)="stopRecording()" class="btn-no-color d-flex align-items-center justify-content-center"> <button (click)="stopRecording()" class="btn-no-color d-flex align-items-center justify-content-center">
@@ -194,9 +194,11 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
this.roomData$ = this.RoomLocalRepository.getRoomByIdLive(this.room.$id) this.roomData$ = this.RoomLocalRepository.getRoomByIdLive(this.room.$id)
this.roomData$.subscribe(e => { this.roomData$.subscribe(e => {
if(e) { if(e && this.room.$id == e?.$id) {
this.room = new RoomViewModel(e) this.room = new RoomViewModel(e)
this.roomType = e.roomType; this.roomType = e.roomType;
} else {
console.log(e);
} }
}) })
@@ -433,6 +435,12 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
modal.present() modal.present()
return modal.onDidDismiss().then((res) => { return modal.onDidDismiss().then((res) => {
this.chatServiceService.sendReadAt({
memberId: SessionStore.user.UserId,
messageId: message.id,
requestId: '',
roomId: this.room.id
})
this.messageDelete(message) this.messageDelete(message)
}); });
} }
@@ -446,21 +454,19 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
const me = message.haveSeen(message.info) const me = message.haveSeen(message.info)
if(!me) { if(!me) {
// Logger.info('send read at, sender '+ message.sender.wxFullName+ ' '+ message.message +'message id'+ message.id)
this.chatServiceService.sendReadAt({ if(message.oneShot == false) {
memberId: SessionStore.user.UserId, this.chatServiceService.sendReadAt({
messageId: message.id, memberId: SessionStore.user.UserId,
requestId: uuidv4(), messageId: message.id,
roomId: this.room.id requestId: uuidv4(),
}) roomId: this.room.id
} else { })
// console.log('no need', message ) }
} }
} }
} }
} else {
console.log('dont read for this room')
} }
} }
@@ -495,12 +501,14 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
} }
} }
this.chatServiceService.sendReadAt({ if(message.oneShot == false) {
memberId: SessionStore.user.UserId, this.chatServiceService.sendReadAt({
messageId: message.id, memberId: SessionStore.user.UserId,
requestId: '', messageId: message.id,
roomId: this.room.id requestId: '',
}) roomId: this.room.id
})
}
setTimeout(() => { setTimeout(() => {
this.scrollToBottomClicked() this.scrollToBottomClicked()
@@ -612,12 +620,6 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
} }
sendReadAt() {
// this.chatServiceService.messageMarkAsRead({roomId: this.room.$id}).then((e) => {
// console.log(e)
// })
}
sendTyping() { sendTyping() {
if(this.room.local == IDBoolean.false) { if(this.room.local == IDBoolean.false) {
this.UserTypingRemoteRepositoryService.sendTyping(this.room.id) this.UserTypingRemoteRepositoryService.sendTyping(this.room.id)
@@ -8,6 +8,9 @@ import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
import { ThemeService } from 'src/app/services/theme.service' import { ThemeService } from 'src/app/services/theme.service'
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { ZodError } from 'zod'; import { ZodError } from 'zod';
import { GetRoomListUseCaseService } from 'src/app/core/chat/usecase/room/room-get-list-use-case.service';
import { GetRoomByIdUseCaseService } from 'src/app/core/chat/usecase/room/room-get-by-id-use-case.service';
@Component({ @Component({
selector: 'app-edit-group', selector: 'app-edit-group',
@@ -31,6 +34,8 @@ export class EditGroupPage implements OnInit {
private toastService: ToastService, private toastService: ToastService,
private chatServiceService: ChatServiceService, private chatServiceService: ChatServiceService,
private RoomLocalDataSourceService: IRoomLocalRepository, private RoomLocalDataSourceService: IRoomLocalRepository,
private GetRoomListUseCaseService: GetRoomListUseCaseService,
private getRoomByIdUseCaseService: GetRoomByIdUseCaseService
) { ) {
this.roomId = this.navParams.get('roomId'); this.roomId = this.navParams.get('roomId');
} }
@@ -67,6 +72,10 @@ export class EditGroupPage implements OnInit {
roomType: 0 roomType: 0
}) })
this.GetRoomListUseCaseService.execute();
//this.getRoomByIdUseCaseService.execute(this.roomId);
if(result.isOk()) { if(result.isOk()) {
this.modalController.dismiss(); this.modalController.dismiss();
} else if (result.error instanceof HttpResponse) { } else if (result.error instanceof HttpResponse) {
@@ -38,6 +38,7 @@ import { RoomStore } from '../../store/roomStore'
import { EditGroupPage } from '../edit-group/edit-group.page'; import { EditGroupPage } from '../edit-group/edit-group.page';
import { imageMimeTypes } from 'src/app/utils/allowedImageExtension'; import { imageMimeTypes } from 'src/app/utils/allowedImageExtension';
import { GroupContactsPage, IGroupContactsPageOutPutSchema } from '../group-contacts/group-contacts.page'; import { GroupContactsPage, IGroupContactsPageOutPutSchema } from '../group-contacts/group-contacts.page';
import { ChatServiceService } from 'src/app/module/chat/domain/chat-service.service';
@Component({ @Component({
selector: 'app-messages', selector: 'app-messages',
@@ -121,6 +122,7 @@ export class MessagesPage implements OnInit, AfterViewInit, OnDestroy {
private file: File, private file: File,
private fileSystemMobileService: FileSystemMobileService, private fileSystemMobileService: FileSystemMobileService,
public RoomStore: RoomStore, public RoomStore: RoomStore,
private chatServiceService: ChatServiceService,
) { ) {
this.room = this.navParams.get('room'); this.room = this.navParams.get('room');
@@ -750,6 +752,12 @@ export class MessagesPage implements OnInit, AfterViewInit, OnDestroy {
modal.present() modal.present()
return modal.onDidDismiss().then((res) => { return modal.onDidDismiss().then((res) => {
this.chatServiceService.sendReadAt({
memberId: SessionStore.user.UserId,
messageId: message.id,
requestId: '',
roomId: this.room.id
})
this.messageDelete(message) this.messageDelete(message)
}); });
} }
+6 -2
View File
@@ -65,10 +65,10 @@ export class MessageViewModal {
} }
this.setMessageUIType() this.setMessageUIType()
if(this.isDeleted != true && this.messageType != IMessageType.information) { if(this._isDeleted == false && this.messageType != IMessageType.information) {
this.showReaction = true this.showReaction = true
} }
if(this.isDeleted == false && this.messageType == IMessageType.normal) { if(this._isDeleted == false && this.messageType == IMessageType.normal) {
this.showMessage = true this.showMessage = true
} }
@@ -118,4 +118,8 @@ export class MessageViewModal {
meSender() { meSender() {
return this.sender.wxUserId == SessionStore.user.UserId return this.sender.wxUserId == SessionStore.user.UserId
} }
get _isDeleted() {
return this.isDeleted || this.info.filter(e =>e.memberId == SessionStore.user.UserId && e.isDeleted).length == 1
}
} }
+2 -1
View File
@@ -1,6 +1,7 @@
import { IRoom, RoomEntitySchema } from "src/app/core/chat/entity/group"; import { IRoom, RoomEntitySchema } from "src/app/core/chat/entity/group";
import { MessageAttachmentSource } from "src/app/core/chat/entity/message"; import { MessageAttachmentSource } from "src/app/core/chat/entity/message";
import { isDocument } from "src/app/utils/document-mimetype"; import { isDocument } from "src/app/utils/document-mimetype";
import { MessageViewModal } from "./message";
export class RoomViewModel implements IRoom { export class RoomViewModel implements IRoom {
@@ -12,7 +13,7 @@ export class RoomViewModel implements IRoom {
expirationDate: typeof RoomEntitySchema._input.expirationDate expirationDate: typeof RoomEntitySchema._input.expirationDate
roomType: typeof RoomEntitySchema._input.roomType roomType: typeof RoomEntitySchema._input.roomType
members: typeof RoomEntitySchema._input.members members: typeof RoomEntitySchema._input.members
messages: typeof RoomEntitySchema._input.messages messages: MessageViewModal[]
local: typeof RoomEntitySchema._input.local local: typeof RoomEntitySchema._input.local
receiverId?: typeof RoomEntitySchema._input.receiverId receiverId?: typeof RoomEntitySchema._input.receiverId
displayDate = '' displayDate = ''
+18 -12
View File
@@ -224,12 +224,15 @@ export class RoomStore {
} }
} }
this.chatServiceService.sendReadAt({ if(message.oneShot == false) {
memberId: SessionStore.user.UserId, this.chatServiceService.sendReadAt({
messageId: message.id, memberId: SessionStore.user.UserId,
requestId: uuidv4(), messageId: message.id,
roomId: this.room.id requestId: uuidv4(),
}) roomId: this.room.id
})
}
setTimeout(() => { setTimeout(() => {
this.scrollToBottomClicked() this.scrollToBottomClicked()
@@ -348,12 +351,15 @@ export class RoomStore {
if(!me) { if(!me) {
// Logger.info('send read at, sender '+ message.sender.wxFullName+ ' '+ message.message +'message id'+ message.id) // Logger.info('send read at, sender '+ message.sender.wxFullName+ ' '+ message.message +'message id'+ message.id)
this.chatServiceService.sendReadAt({ if(message.oneShot == false) {
memberId: SessionStore.user.UserId, this.chatServiceService.sendReadAt({
messageId: message.id, memberId: SessionStore.user.UserId,
requestId: uuidv4(), messageId: message.id,
roomId: this.room.id requestId: uuidv4(),
}) roomId: this.room.id
})
}
} else { } else {
// console.log('no need', message ) // console.log('no need', message )
} }
@@ -2,7 +2,6 @@
<div class="main-tab pb-10 ion-toolbar header-color"> <div class="main-tab pb-10 ion-toolbar header-color">
<div class="mobile pt-20 d-flex div-top-header justify-space-between"> <div class="mobile pt-20 d-flex div-top-header justify-space-between">
<div title="Pesquisa" *ngIf="!hideSearchBtn" class="div-search viewport-font-size"> <div title="Pesquisa" *ngIf="!hideSearchBtn" class="div-search viewport-font-size">
<div (click)="openSearch()" class="{{Cy.header.b.search}}"> <div (click)="openSearch()" class="{{Cy.header.b.search}}">
<ion-icon *ngIf="ThemeService.currentTheme == 'doneIt' " class="font-45-em" <ion-icon *ngIf="ThemeService.currentTheme == 'doneIt' " class="font-45-em"
@@ -54,7 +53,7 @@
<div (click)="openProfile()" *ngIf="(profilePictureSubject | async) as calendarData" class="profile-image"> <div (click)="openProfile()" *ngIf="(profilePictureSubject | async) as calendarData" class="profile-image">
<img *ngIf="calendarData.base64 != null" class="profile-image image-prety" src={{calendarData.base64}}> <img *ngIf="calendarData.base64 != null" class="profile-image image-prety" [src]="getSafeImage(calendarData.base64)">
<ion-icon *ngIf="calendarData.base64 == null" <ion-icon *ngIf="calendarData.base64 == null"
class="icon font-45-em" src='assets/images/theme/gov/icons-profile.svg'></ion-icon> class="icon font-45-em" src='assets/images/theme/gov/icons-profile.svg'></ion-icon>
</div> </div>
@@ -189,7 +188,7 @@
<div title="Perfil" class="div-profile d-flex cursor-pointer font-45-rem" (click)="openProfile()"> <div title="Perfil" class="div-profile d-flex cursor-pointer font-45-rem" (click)="openProfile()">
<div *ngIf="(profilePictureSubject | async) as calendarData" class="profile-image d-flex"> <div *ngIf="(profilePictureSubject | async) as calendarData" class="profile-image d-flex">
<img *ngIf="calendarData.base64 != null" class="profile-image image-prety" src={{calendarData.base64}}> <img *ngIf="calendarData.base64 != null" class="profile-image image-prety" [src]="getSafeImage(calendarData.base64)">
<ion-icon *ngIf="calendarData.base64 == null" <ion-icon *ngIf="calendarData.base64 == null"
class="icon font-45-rem image-prety" src='assets/images/theme/gov/icons-profile.svg'></ion-icon> class="icon font-45-rem image-prety" src='assets/images/theme/gov/icons-profile.svg'></ion-icon>
</div> </div>
@@ -23,6 +23,7 @@ import { UserRepositoryService } from 'src/app/module/user/data/user-repository.
import { UserProfilePicture } from 'src/app/module/user/data/datasource/user-local-repository.service'; import { UserProfilePicture } from 'src/app/module/user/data/datasource/user-local-repository.service';
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer'; import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
import { isHttpError } from 'src/app/services/http.service'; import { isHttpError } from 'src/app/services/http.service';
import { DomSanitizer } from '@angular/platform-browser';
@Component({ @Component({
selector: 'app-header', selector: 'app-header',
@@ -80,7 +81,8 @@ export class HeaderPage implements OnInit {
public HeaderSettingsService: HeaderSettingsService, public HeaderSettingsService: HeaderSettingsService,
public PublicationHolderService: PublicationHolderService, public PublicationHolderService: PublicationHolderService,
private notificationRepositoryService: NotificationRepositoryService, private notificationRepositoryService: NotificationRepositoryService,
private UserRepositoryService: UserRepositoryService private UserRepositoryService: UserRepositoryService,
private sanitizer: DomSanitizer
) { ) {
this.profilePictureSubject = this.UserRepositoryService.getProfilePictureLive() as any this.profilePictureSubject = this.UserRepositoryService.getProfilePictureLive() as any
this.notificationCount$ = this.notificationRepositoryService.getNotificationLiveCount() this.notificationCount$ = this.notificationRepositoryService.getNotificationLiveCount()
@@ -106,6 +108,10 @@ export class HeaderPage implements OnInit {
} }
} }
getSafeImage(base64: string) {
return this.sanitizer.bypassSecurityTrustUrl(base64);
}
updateCount = () => { updateCount = () => {
} }
+1 -1
View File
@@ -4,4 +4,4 @@ import { environment as oaprProd } from './suport/oapr'
// import { environment as doneITProd } from './suport/doneIt' // import { environment as doneITProd } from './suport/doneIt'
export const environment: Environment = DevDev; export const environment: Environment = oaprProd;
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -23,7 +23,7 @@
<httpProtocol> <httpProtocol>
<customHeaders> <customHeaders>
<!-- Iframe on the same host only --> <!-- Iframe on the same host only -->
<add name="Content-Security-Policy" value="frame-ancestors https://gdviewer-dev.dyndns.info/" /> <add name="Content-Security-Policy" value="frame-ancestors https://gdqas-api.oapr.gov.ao/" />
<!-- HTTPS on --> <!-- HTTPS on -->
<add name="Strict-Transport-Security" value="max-age=31536000" /> <add name="Strict-Transport-Security" value="max-age=31536000" />
<add name="Referrer-Policy" value="no-referrer" /> <add name="Referrer-Policy" value="no-referrer" />