37 Commits

Author SHA1 Message Date
peter.maquiran 0598a11d2e Add .drone.yml
continuous-integration/drone Build is failing
2026-04-13 23:40:39 +01:00
peter 5fb8b67599 dont call session expired screen when already in the login page 2026-04-07 15:21:30 +01:00
peter e147164f07 fit button color 2026-04-06 15:18:41 +01:00
peter 3c326eeb2a fix: reset expiration date when toggling duration option 2026-04-06 13:39:47 +01:00
peter 2ee634b354 clean room list on session expire 2026-04-06 13:22:04 +01:00
peter ec57a046ee remove modals on logout 2026-04-06 13:21:30 +01:00
peter 54411bc20c force to close the user modal profile 2026-04-06 13:21:13 +01:00
peter f6ddea8586 dont show empty message when the list is not empty 2026-04-06 13:20:53 +01:00
peter 154de1f991 call sesstion exparation model 2026-04-06 10:24:35 +01:00
peter d9984981d1 dont remove session exparation model on route chage 2026-04-06 10:23:57 +01:00
peter 4a7a0934de session expiration module 2026-04-06 10:23:05 +01:00
peter c00e1b888c session exparation model 2026-04-06 10:22:48 +01:00
peter cb9fb7aab5 list all images 2026-04-06 10:21:32 +01:00
peter 7ca666518d add uuidv4 2026-04-06 10:21:16 +01:00
peter e006d7ed96 fix erro on read message 2026-04-06 10:19:58 +01:00
peter c4689bb103 remove logs 2026-04-01 13:09:03 +01:00
peter 671e2e9e38 prevent calling notification when not login 2026-04-01 13:08:46 +01:00
peter 796212cba0 add auth to logs 2026-03-26 10:31:23 +01:00
peter 52095e37ec Add loader to publication post 2026-03-18 14:37:45 +01:00
peter 7911a73066 remove alert 2026-03-16 11:40:17 +01:00
peter e622f305e5 fix image one shot 2026-03-16 11:39:56 +01:00
Peter Maquiran 3ae36a33cd fix publication slow to reflect 2026-03-05 10:04:57 +01:00
Peter Maquiran ee61f066e4 change function defaul value 2026-03-02 11:35:35 +01:00
Peter Maquiran a47e7f0c5c send attachment of creating defeimente 2026-03-02 11:34:06 +01:00
peter.maquiran 2f054bc781 save 2026-01-19 15:14:30 +01:00
peter.maquiran 2673f8a17a set expediente as default and fix assinar diploma 2026-01-06 11:58:59 +01:00
peter.maquiran c14431034c remove duplicate calls 2025-10-17 16:20:48 +01:00
peter.maquiran a9cd4843d5 pedido 2025-10-17 13:58:05 +01:00
peter.maquiran 9e3c6db93a fix chat and gabinete 2025-10-16 16:09:35 +01:00
peter.maquiran f79df96d48 fix message 2025-09-29 11:34:11 +01:00
peter.maquiran 4870a0cd69 fix messsage 2025-09-24 16:21:52 +01:00
peter.maquiran 3eecedb23f fix publication 2025-09-17 18:37:35 +01:00
peter.maquiran 5f7295d41c fix publication details 2025-09-11 11:20:44 +01:00
peter.maquiran 69e4334ebf fix chat send attchment 2025-09-07 10:12:05 +01:00
peter.maquiran 0d10ee98fa fix profile picture 2025-09-05 12:00:01 +01:00
peter.maquiran d1e5387d11 update image 2025-09-05 11:42:36 +01:00
peter.maquiran 430adf394d fix chage duplicate message 2025-09-04 15:40:45 +01:00
197 changed files with 4576 additions and 2995 deletions
+25
View File
@@ -0,0 +1,25 @@
kind: pipeline
type: docker
name: swarm-deploy
steps:
# 1. Build and push Next.js tvone Docker image
- name: build
image: plugins/docker
settings:
registry: registry.petermaquiran.xyz
repo: registry.petermaquiran.xyz/tvone
tags:
- latest
dockerfile: Dockerfile
context: .
# 2. Trigger service update in Docker Swarm
- name: update-service
image: curlimages/curl:latest
commands:
- >
curl -X POST https://docker-socket.petermaquiran.xyz/update-service
-H "Authorization: Bearer 123"
-H "Content-Type: application/json"
-d "{\"service\":\"tvone_tvone\",\"image\":\"registry.petermaquiran.xyz/tvone:latest\"}"
+1
View File
@@ -0,0 +1 @@
{}
+43
View File
@@ -0,0 +1,43 @@
# TestFlight Build & Upload Guide (Flutter + iOS)
Follow these steps to generate and release a TestFlight build.
## 1. Update App Version
Edit `pubspec.yaml`:
version: 1.2.3+45
- `1.2.3` → visible version
- `45` → build number (must increase every release)
## 2. Install Dependencies
```bash
fvm flutter clean
fvm flutter pub get
cd ios && pod install && cd ..
```
## 3. Open Project in Xcode
```bash
open ios/Runner.xcworkspace
```
## 5. Archive in Xcode
1. Product → Destination → Any iOS Device
2. Product → Archive
## 6. Upload to TestFlight
After archive: - Click _Distribute App_ - Choose _App Store Connect_
Upload
## 9. Enable TestFlight Testing
- Go to https://appstoreconnect.apple.com
- Select the app
- Open _TestFlight_
- Add internal testers or submit for external review
+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**.
+788 -332
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -118,6 +118,7 @@
"@ngrx/store": "^10.1.2",
"@ngx-translate/core": "^13.0.0",
"@ngxs/store": "^3.8.2",
"@openreplay/tracker": "^16.4.8",
"@opentelemetry/exporter-metrics-otlp-http": "^0.52.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
"@opentelemetry/exporter-zipkin": "^1.25.1",
@@ -225,6 +226,7 @@
"uuidv4": "^6.2.11",
"videogular2": "^7.0.2",
"webpack": "^5.88.2",
"winston": "^3.17.0",
"wordcloud": "^1.1.2",
"ws": "^7.5.9",
"zod": "^3.23.8",
@@ -259,7 +261,7 @@
"cordova-plugin-media-capture": "^3.0.3",
"cordova-plugin-network-information": "^3.0.0",
"cordova-plugin-statusbar": "^2.4.2",
"cypress": "^12.13.0",
"cypress": "^15.1.0",
"es6-promise-plugin": "^4.2.2",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
+223 -61
View File
@@ -64,19 +64,9 @@ import { CustomImageCachePageRoutingModule } from './services/file/custom-image-
import { IonicImageLoaderModule } from 'ionic-image-loader-v5';
import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer';
import { FileOpener } from '@awesome-cordova-plugins/file-opener/ngx';
import { MatInputModule } from '@angular/material/input';
import { MatNativeDateModule } from '@angular/material/core';
// 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 * as SentrySibling from '@sentry/angular';
import { AngularFireModule } from '@angular/fire';
import { AngularFireMessagingModule } from '@angular/fire/messaging';
import { firebaseConfig } from '../firebase-config';
@@ -87,67 +77,237 @@ import { LoggingInterceptorService } from './services/logging-interceptor.servic
import { PopupQuestionPipe } from './modals/popup-question.pipe';
import '@teamhive/capacitor-video-recorder';
import { tokenInterceptor } from './infra/monitoring/interceptors/token.interceptors';
import { InputFilterDirective } from './services/directives/input-filter.directive';
import { VisibilityDirective } from './services/directives/visibility.directive';
import { DeplomaOptionsPageModule } from './shared/popover/deploma-options/deploma-options.module';
import { DiplomaOptionsPage } from './shared/popover/deploma-options/deploma-options.page';
import { ImageCropperModule } from 'ngx-image-cropper';
import { metricsInterceptor } from './infra/monitoring/interceptors/metter.interceptor';
import {MatMenuModule} from '@angular/material/menu';
import {MatIconModule} from '@angular/material/icon';
import { ChatModule } from './module/chat/chat.module';
import { openTelemetryLogging } from './services/monitoring/opentelemetry/logging';
import { registerLocaleData } from '@angular/common';
import localePt from '@angular/common/locales/pt';
import { LogsDatabase } from './infra/database/dexie/instance/logs/service';
import { UserModule } from './module/user/user.module';
import { SessionExpiredModalPageModule } from './modals/session-expired-modal/session-expired-modal.module';
import { Logger } from './services/logger/main/service';
// Register the locale data
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(
{
dsn: 'https://5b345a3ae70b4e4da463da65881b4aaa@o4504340905525248.ingest.sentry.io/4504345615794176',
// To set your release and dist versions
release: 'gabinetedigital@1.0.0',
dist: '1',
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
tracesSampleRate: 1.0,
integrations: [
new BrowserTracing({
tracingOrigins: ['localhost', 'https://gd-api.oapr.gov.ao/api/'],
}) as Integration,
],
beforeSend(event) {
console.log('event.exception.values[0].value', event.exception.values[0].value);
if (event.level === 'error') {
LogsDatabase.sentryError.add(event as any).then(() => {
console.log('event', event)
})
// Sentry.init(
// {
// dsn: 'https://5b345a3ae70b4e4da463da65881b4aaa@o4504340905525248.ingest.sentry.io/4504345615794176',
// // To set your release and dist versions
// release: 'gabinetedigital@1.0.0',
// dist: '1',
// // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// // We recommend adjusting this value in production.
// tracesSampleRate: 1.0,
// debug: true, // logs to console
// beforeSend(event) {
// console.log('event.exception.values[0].value', event.exception.values[0].value);
openTelemetryLogging.send({
type: 'graylog',
payload: {
message: event.exception.values[0].value,
object: {
sentry: true,
error: event
}
},
spanContext: null
})
// if (event.level === 'error') {
// LogsDatabase.sentryError.add(event as any).then(() => {
// console.log('event', event)
// })
// // openTelemetryLogging.send({
// // level: 'info',
// // message: event.exception.values[0].value,
// // payload: {
// // object: {
// // sentry: true,
// // error: event
// // }
// // },
// // })
// }
// // Return event to send it to Sentry
// return event;
// },
// }
// );
(function () {
const httpLogs = [];
// --- Patch fetch ---
const originalFetch = window.fetch;
window.fetch = async (input, config = {}) => {
const url = typeof input === "string" ? input : (input as any).url;
const method = config?.method || "GET";
// Capture tracer header
let tracerHeader = null;
if (config?.headers) {
if (config.headers instanceof Headers) {
tracerHeader = config.headers.get("x-tracer");
} else if (typeof config.headers === "object") {
tracerHeader = config.headers["x-tracer"] || config.headers["X-Tracer"];
}
}
// Return event to send it to Sentry
return event;
},
}
);
// Capture request body (payload)
let requestPayload = null;
if (config?.body) {
try {
if (typeof config.body === "string") {
requestPayload = 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 {
requestPayload = "[Unserializable body]";
}
}
const start = Date.now();
try {
const response = await originalFetch(input, config);
// Clone response so we dont consume it
const clone = response.clone();
let responsePayload = null;
try {
const contentType = clone.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
responsePayload = await clone.json();
} else if (contentType.includes("text")) {
responsePayload = await clone.text();
} else {
responsePayload = "[Non-textual body]";
}
} catch {
responsePayload = "[Unserializable response]";
}
const log = {
type: "fetch",
url,
method,
status: response.status,
xTracer: tracerHeader,
requestPayload,
responsePayload,
duration: Date.now() - start,
timestamp: new Date().toISOString(),
};
if(response.status >= 400 || response.status === 0) {
Logger.error('XHR', log)
}
httpLogs.push(log);
return response;
} catch (error) {
const log = {
type: "fetch",
url,
method,
status: "NETWORK_ERROR",
xTracer: tracerHeader,
requestPayload,
responsePayload: null,
duration: Date.now() - start,
timestamp: new Date().toISOString(),
};
if(!url.includes('petermaquiran.xyz') && url != "") {
Logger.error('XHR', log);
}
httpLogs.push(log);
throw error;
}
};
// --- Patch XMLHttpRequest ---
const OriginalXHR = window.XMLHttpRequest;
function CustomXHR() {
const xhr: any = new OriginalXHR();
const start = Date.now();
xhr._xTracer = null;
xhr._requestPayload = null;
const originalSetRequestHeader = xhr.setRequestHeader;
xhr.setRequestHeader = function (key, value) {
if (key.toLowerCase() === "x-tracer") {
xhr._xTracer = value;
}
return originalSetRequestHeader.call(xhr, key, value);
};
const originalSend = xhr.send;
xhr.send = function (body) {
if (body) {
try {
xhr._requestPayload = typeof body === "string" ? body : JSON.stringify(body);
} catch {
xhr._requestPayload = "[Unserializable body]";
}
}
return originalSend.call(xhr, body);
};
xhr.addEventListener("loadend", function () {
let responsePayload = null;
try {
const contentType = xhr.getResponseHeader("content-type") || "";
if (contentType.includes("application/json")) {
responsePayload = JSON.parse(xhr.responseText);
} else if (contentType.includes("text")) {
responsePayload = xhr.responseText;
} else {
responsePayload = "[Non-textual body]";
}
} catch {
responsePayload = "[Unserializable response]";
}
const log = {
type: "xhr",
url: xhr.responseURL,
method: xhr._method || "GET",
status: xhr.status,
xTracer: xhr._xTracer,
requestPayload: xhr._requestPayload,
responsePayload,
duration: Date.now() - start,
timestamp: new Date().toISOString(),
};
if(xhr.status >= 400 && !log.url.includes('petermaquiran.xyz') || xhr.status === 0 && !log.url.includes('petermaquiran.xyz')) {
Logger.error('XHR', log)
}
httpLogs.push(log);
});
const originalOpen = xhr.open;
xhr.open = function (method, url, ...rest) {
xhr._method = method;
return originalOpen.call(xhr, method, url, ...rest);
};
return xhr;
}
(window as any).XMLHttpRequest = CustomXHR;
// Expose logs
(window as any).getHttpLogs = () => httpLogs;
})();
@NgModule({
declarations: [AppComponent, PopupQuestionPipe, InputFilterDirective],
@@ -210,7 +370,8 @@ registerLocaleData(localePt, 'pt');
MatIconModule,
// module
ChatModule,
UserModule
UserModule,
SessionExpiredModalPageModule
],
entryComponents: [
DiplomaOptionsPage,
@@ -218,11 +379,12 @@ registerLocaleData(localePt, 'pt');
],
providers: [
{ provide: MAT_DATE_LOCALE, useValue: 'pt' },
{
provide: ErrorHandler,
// Attach the Sentry ErrorHandler
useValue: SentrySibling.createErrorHandler(),
},
//{ provide: ErrorHandler, useClass: AppErrorHandler },
// {
// provide: ErrorHandler,
// // Attach the Sentry ErrorHandler
// useValue: SentrySibling.createErrorHandler(),
// },
StatusBar,
//SplashScreen,
HttpClient,
@@ -253,7 +415,7 @@ registerLocaleData(localePt, 'pt');
FFmpeg,
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorService, multi: true },
tokenInterceptor,
metricsInterceptor
metricsInterceptor,
],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
+47
View File
@@ -0,0 +1,47 @@
import { BaseEntity } from "src/app/utils/entity";
import { z } from "zod"
export const ActionEntitySchema = z.object({
processId: z.number(),
applicationId: z.number(),
organicEntityId: z.number(),
securityId: z.number(),
serieId: z.number(),
userId: z.number(),
dateIndex: z.string().datetime(), // ISO 8601 datetime
description: z.string(),
detail: z.string(),
dateBegin: z.string().datetime(),
dateEnd: z.string().datetime(),
actionType: z.number(),
location: z.string(),
isExported: z.boolean(),
sourceLocation: z.number(),
sourceProcessId: z.number(),
})
export type IAction = z.infer<typeof ActionEntitySchema>
export class ActionEntity extends BaseEntity<ActionEntity>(ActionEntitySchema) implements IAction{
processId: typeof ActionEntitySchema._input.processId
applicationId: typeof ActionEntitySchema._input.processId
organicEntityId: typeof ActionEntitySchema._input.processId
securityId: typeof ActionEntitySchema._input.processId
serieId: typeof ActionEntitySchema._input.processId
userId: typeof ActionEntitySchema._input.processId
dateIndex: typeof ActionEntitySchema._input.dateIndex
description: typeof ActionEntitySchema._input.description
detail: typeof ActionEntitySchema._input.detail
dateBegin: typeof ActionEntitySchema._input.dateBegin
dateEnd: typeof ActionEntitySchema._input.dateEnd
actionType: typeof ActionEntitySchema._input.processId
location: typeof ActionEntitySchema._input.location
isExported: typeof ActionEntitySchema._input.isExported
sourceLocation:typeof ActionEntitySchema._input.processId
sourceProcessId: typeof ActionEntitySchema._input.processId
constructor(data: IAction) {
super();
Object.assign(this, data)
}
}
@@ -0,0 +1,27 @@
import { z } from "zod"
export const PublicationDocumentEntitySchema = z.object({
id: z.number().optional(),
file: z.string(),
extension: z.string(),
name: z.string(),
path: z.string(),
documentId: z.number(),
datePublication: z.string(),
})
export type IPublicationDocument = z.infer<typeof PublicationDocumentEntitySchema>
export class PublicationDocumentEntity implements IPublicationDocument{
file: typeof PublicationDocumentEntitySchema._input.file
extension: typeof PublicationDocumentEntitySchema._input.extension
name: typeof PublicationDocumentEntitySchema._input.name
path: typeof PublicationDocumentEntitySchema._input.path
documentId: typeof PublicationDocumentEntitySchema._input.documentId
datePublication: typeof PublicationDocumentEntitySchema._input.datePublication
id: typeof PublicationDocumentEntitySchema._input.id
constructor(data: IPublicationDocument) {
Object.assign(this, data)
}
}
@@ -0,0 +1,43 @@
import { BaseEntity } from "src/app/utils/entity";
import { z } from "zod"
export const PublicationEntitySchema = z.object({
processId: z.number(),
documentId: z.number(),
applicationId: z.number(),
organicEntityId: z.number(),
securityId: z.number(),
userId: z.number(),
dateIndex: z.string(),
phase: z.number(),
title: z.string(),
message: z.string(),
datePublication: z.string(),
isExported: z.boolean(),
sourceLocation: z.number(),
sourceProcessId: z.number(),
sourceDocumentId: z.number(),
})
export type IPublication = z.infer<typeof PublicationEntitySchema>
export class PublicationEntity extends BaseEntity<PublicationEntity>(PublicationEntitySchema) implements IPublication{
processId: typeof PublicationEntitySchema._input.processId
applicationId: typeof PublicationEntitySchema._input.processId
organicEntityId: typeof PublicationEntitySchema._input.processId
securityId: typeof PublicationEntitySchema._input.processId
serieId: typeof PublicationEntitySchema._input.processId
userId: typeof PublicationEntitySchema._input.processId
dateIndex: typeof PublicationEntitySchema._input.dateIndex
isExported: typeof PublicationEntitySchema._input.isExported
sourceLocation:typeof PublicationEntitySchema._input.processId
sourceProcessId: typeof PublicationEntitySchema._input.processId
sourceDocumentId:typeof PublicationEntitySchema._input.sourceDocumentId
documentId: typeof PublicationEntitySchema._input.documentId
datePublication: typeof PublicationEntitySchema._input.datePublication
constructor(data: IPublication) {
super();
Object.assign(this, data)
}
}
@@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
import { ActionLocalRepositoryService } from 'src/app/module/actions/data/repository/action-local-repository.service';
import { ActionRemoteRepositoryService } from 'src/app/module/actions/data/repository/action-remote-repository.service';
import { z } from 'zod';
export const ActionsCreateInputDTOSchema = 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(),
});
export type ActionsCreateInput = z.infer<typeof ActionsCreateInputDTOSchema>
@Injectable({
providedIn: 'root'
})
export class ActionsCreateUseCaseService {
constructor(
private remote: ActionRemoteRepositoryService,
private local: ActionLocalRepositoryService
) { }
execute(input: ActionsCreateInput) {
this.remote.create(input);
}
}
@@ -0,0 +1,53 @@
import { Injectable } from '@angular/core';
import { ActionLocalRepositoryService } from 'src/app/module/actions/data/repository/action-local-repository.service';
import { ActionRemoteRepositoryService } from 'src/app/module/actions/data/repository/action-remote-repository.service';
import { z } from "zod";
export const ActionGetAllOutPutSchema = z.array(z.object({
processId: z.number(),
applicationId: z.number(),
organicEntityId: z.number(),
securityId: z.number(),
serieId: z.number(),
userId: z.number(),
dateIndex: z.string().datetime(), // ISO 8601 datetime
description: z.string(),
detail: z.string(),
dateBegin: z.string().datetime(),
dateEnd: z.string().datetime(),
actionType: z.number(),
location: z.string(),
isExported: z.boolean(),
sourceLocation: z.number(),
sourceProcessId: z.number(),
}));
// Example usage:
export type ActionGetAllOutPut = z.infer<typeof ActionGetAllOutPutSchema>;
@Injectable({
providedIn: 'root'
})
export class ActionsGetAllUseCaseService {
constructor(
private remote: ActionRemoteRepositoryService,
private local: ActionLocalRepositoryService
) { }
async execute() {
var result = await this.remote.actionGetAll();
if(result.isOk()) {
const data = result.value.data.data;
this.local.createTransaction( async (table) => {
// Clear the table before inserting new data
await table.clear();
// Insert the new data
await table.bulkAdd(data);
});
}
}
}
@@ -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,105 @@
import { Injectable } from '@angular/core';
import { Result } from 'neverthrow';
import { PublicationFileLocalRepositoryService } from 'src/app/module/actions/data/repository/publication-file-local-repository.service';
import { PublicationFileRemoteRepositoryService } from 'src/app/module/actions/data/repository/publication-file-remote-repository.service';
import { z } from 'zod';
import { HttpErrorResponse } from '@angular/common/http';
export const PublicationFileGetByDocumentIdInputDTOSchema = z.object({
datePublication: z.string(),
documentId: z.number(),
processId: z.number(),
});
export type PublicationFileGetByDocumentIdInput = z.infer<typeof PublicationFileGetByDocumentIdInputDTOSchema>
export const PublicationFileGetByDocumentIdOutPutDTOSchema = z.object({
id: z.number().optional(),
file: z.string(),
extension: z.string(),
name: z.string(),
path: z.string(),
documentId: z.number(),
datePublication: z.string(),
}).array();
export type PublicationFileGetByDocumentIdOutPut = z.infer<typeof PublicationFileGetByDocumentIdOutPutDTOSchema>
export interface IPublicationFilesSyncResult {
added: PublicationFileGetByDocumentIdOutPut;
updated: PublicationFileGetByDocumentIdOutPut;
remove: PublicationFileGetByDocumentIdOutPut;
}
@Injectable({
providedIn: 'root'
})
export class PublicationFileGetByDocumentIdService {
constructor(
private local: PublicationFileLocalRepositoryService,
private remote: PublicationFileRemoteRepositoryService
) { }
async execute(input: PublicationFileGetByDocumentIdInput): Promise<Result<IPublicationFilesSyncResult, HttpErrorResponse>> {
var httpResult = await this.remote.FileListByDocumentId(input);
if(httpResult.isOk()) {
var localResult = await this.local.find({documentId: input.documentId});
if(localResult.isOk()) {
const serverFiles = httpResult.value.data
const added = [];
const updated = [];
const remove = [];
for (const localItem of localResult.value) {
if (localItem.datePublication !== input.datePublication!) {
localItem.datePublication = input.datePublication;
this.local.update(localItem.id, localItem);
}
}
for (const file of serverFiles.data || []) {
const findLocally = await this.local.findOne({ name: file.name });
if (findLocally.isOk() && findLocally.value == null) {
added.push({
...file,
documentId: input.documentId!,
datePublication: input.datePublication,
});
const start = performance.now();
await this.local.insert({
...file,
documentId: input.documentId!,
datePublication: input.datePublication,
}).then(() => {
const end = performance.now();
//console.log(`Insert duration for file "${file.name}": ${(end - start).toFixed(2)} ms`);
});
}
}
for (const localFile of localResult.value) {
const found = httpResult.value.data.data.filter((e) => e.name === localFile.name);
if (found.length === 0 && localFile.name) {
remove.push(localFile);
await this.local.delete(localFile.id);
}
}
return httpResult.map(() =>({
added,
updated,
remove
}))
}
}
return httpResult.map(() =>({
added: [],
updated: [],
remove: []
}));
}
}
@@ -0,0 +1,38 @@
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';
import { PublicationFileLocalRepositoryService } from 'src/app/module/actions/data/repository/publication-file-local-repository.service';
@Injectable({
providedIn: 'root'
})
export class PublicationFilesDeleteByPathUseCaseService {
constructor(
private remote: PublicationFileRepositoryService,
private local: PublicationFileLocalRepositoryService
) { }
async execute(file: IPublicationDocument) {
const validation = zodSafeValidation<MessageUpdateInput>(PublicationDocumentEntitySchema, file)
if(validation.isOk()) {
const result = await this.local.findOne({ path: file.path })
if(result.isOk()) {
await this.local.delete(result.value.id);
}
} 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}`);
}
}
@@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { ActionLocalRepositoryService } from 'src/app/module/actions/data/repository/action-local-repository.service';
import { ActionRemoteRepositoryService } from 'src/app/module/actions/data/repository/action-remote-repository.service';
import { z } from 'zod';
const PublicationGetDocumentByProcessIdOutPutSchema = z.object({
file: z.string(),
extension: z.string(),
name: z.string(),
path: z.string(),
documentId: z.number()
}).array();
// Example usage:
export type PublicationGetDocumentByProcessIdOutPut = z.infer<typeof PublicationGetDocumentByProcessIdOutPutSchema>;
@Injectable({
providedIn: 'root'
})
export class PublicationGetDocumentByProcessIdService {
constructor(
private remote: ActionRemoteRepositoryService,
private local: ActionLocalRepositoryService
) {}
execute(processId: string) {
this.remote
}
}
@@ -0,0 +1,135 @@
import { Injectable } from '@angular/core';
import { PublicationLocalRepositoryService } from 'src/app/module/actions/data/repository/publication-local-repository.service';
import { PublicationRemoteRepositoryService } from 'src/app/module/actions/data/repository/publication-remote-repository.service';
import { z } from 'zod';
import { IPublication } from '../entity/publicationEntity';
import { Result } from 'neverthrow';
import { HttpErrorResponse } from '@angular/common/http';
const PublicationListByProcessIdOutPutSchema = z.object({
processId: z.number(),
documentId: z.number(),
applicationId: z.number(),
organicEntityId: z.number(),
securityId: z.number(),
userId: z.number(),
dateIndex: z.string().datetime(),
phase: z.number(),
title: z.string(),
message: z.string(),
datePublication: z.string().datetime(),
isExported: z.boolean(),
sourceLocation: z.number(),
sourceProcessId: z.number(),
sourceDocumentId: z.number(),
}).array();
// Example usage:
export type PublicationListByProcessIdOutPut = z.infer<typeof PublicationListByProcessIdOutPutSchema>;
export interface IPublicationSyncResult {
added: IPublication[];
updated: IPublication[];
remove: IPublication[];
}
@Injectable({
providedIn: 'root'
})
export class PublicationListByProcessIdService {
constructor(
private remote: PublicationRemoteRepositoryService,
private local: PublicationLocalRepositoryService,
) {}
async execute(processId: number): Promise<Result<IPublicationSyncResult, HttpErrorResponse>> {
const result = await this.remote.listByProcessId(processId);
if(result.isOk()) {
const publications = result.value.data.data || [];
var localList = await this.local.find({processId: processId});
if(localList.isOk()) {
const localMap = new Map(
localList.value.map(item => [item.documentId.toString(), item])
);
var serverMap = new Map(
publications.map(item => [item.documentId.toString(), item])
);
const added = [];
const updated = [];
const remove = [];
//console.log("local::", localList.value.length);
//console.log("server::", publications.length);
// detect added & updated
for (const [id, serverItem] of serverMap) {
if (!localMap.has(id)) {
//console.log(serverMap.get(id), "addddddd");
//console.log(localList, publications);
added.push(serverMap.get(id));
} else if (serverItem.datePublication !== localMap.get(id).datePublication) {
updated.push(serverMap.get(id));
//console.log('update');
} else {
//console.log('else');
//console.log(localMap, serverMap);
}
}
// detect removed
for (const [id, localItem] of localMap) {
if (!serverMap.has(id)) {
remove.push(localMap.get(id));
}
}
//console.log("update::", Object.keys(updated).length);
//console.log("added::", added.length);
//console.log("remove::", remove.length);
// apply updates
if (updated.length > 0) {
await this.local.updateMany(Object.values(updated));
}
// apply removals
for (const deletedItem of remove) {
const findRequest = await this.local.findOne({
documentId: deletedItem.documentId
});
if (findRequest.isOk()) {
await this.local.delete(findRequest.value.documentId!);
}
}
// apply inserts
if (added.length > 0) {
await this.local.insertMany(added);
}
return result.map(()=>({
added,
updated,
remove
}))
}
}
return result.map(()=>({
added: [],
updated: [],
remove: []
}))
}
}
@@ -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);
}
}
+2 -2
View File
@@ -14,7 +14,7 @@ const UserSchema = z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string().nullable(),
userPhoto: z.string().nullable().optional(),
});
const MemberSchema = z.object({
@@ -27,7 +27,7 @@ const MemberSchema = z.object({
export const RoomEntitySchema = z.object({
$id: z.string(),
id: z.string().uuid().optional(),
roomName: z.string(),
roomName: z.string().nullable(),
createdBy: z.object({
wxUserId: z.number(),
wxFullName: z.string(),
+10 -3
View File
@@ -30,6 +30,7 @@ export const MessageEntityAttachmentSchema = z.object({
mimeType: z.string().nullable().optional(),
safeFile: z.any().optional(),
description: z.string().nullable().optional(),
filePath: z.string().nullable().optional()
})
export const MessageEntitySchema = z.object({
@@ -49,7 +50,7 @@ export const MessageEntitySchema = z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string().nullable(),
userPhoto: z.string().nullable().optional(),
}).nullable(),
reactions: z.object({
id: z.string(),
@@ -60,14 +61,16 @@ export const MessageEntitySchema = z.object({
info: z.array(z.object({
memberId: z.number(),
readAt: z.string().nullable(),
deliverAt: z.string().nullable()
deliverAt: z.string().nullable(),
isDeleted: z.boolean().optional(),
})).optional(),
sending: z.boolean().optional(),
attachments: z.array(MessageEntityAttachmentSchema).optional(),
origin: z.enum(['history', 'local', 'incoming']).optional(),
requestId: z.string().nullable().optional(),
sendAttemp: z.number().optional(),
hasAttachment: z.boolean().optional()
hasAttachment: z.boolean().optional(),
deviceId: z.string().nullable().optional()
})
export type IMessage = z.infer<typeof MessageEntitySchema>;
@@ -120,4 +123,8 @@ export class MessageEntity {
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
}
}
+3 -11
View File
@@ -1,6 +1,7 @@
import { MessageEntity, IMessage } from "../entity/message";
import { MessageOutPutDataDTO } from "src/app/core/chat/repository/dto/messageOutputDTO";
import { MessageInputDTO } from "../usecase/message/message-create-use-case.service";
import { getInstanceId } from "src/app/module/chat/domain/chat-service.service";
export class MessageMapper {
static toDomain(DTO: MessageOutPutDataDTO) : MessageEntity {
@@ -18,17 +19,8 @@ export class MessageMapper {
roomId: entity.roomId,
senderId: entity.sender.wxUserId,
requestId: entity.requestId || requestId,
attachment: entity.attachments.map((e)=>({
fileType:e.fileType,
source: e.source,
file: e.file,
fileName: e.fileName,
applicationId: e.applicationId || 0,
docId: Number(e.docId) || 0,
mimeType: e.mimeType,
description: e.description
}))[0] || {}
attachment: entity.attachments.map((e)=>(e.id)) || [],
deviceId: getInstanceId()
}
}
}
@@ -0,0 +1,12 @@
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
import { Observable as DexieObservable, PromiseExtended } from 'Dexie';
import { AttachmentTable } from "src/app/infra/database/dexie/instance/chat/schema/attachment";
import { Result } from "neverthrow";
import { MemberTable, MemberTableSchema } from "src/app/infra/database/dexie/instance/chat/schema/members";
import { RepositoryResult } from "src/app/infra/repository";
import { z } from "zod";
import { MemberListUPdateStatusInputDTO } from "src/app/core/chat/usecase/socket/member-list-update-status-use-case.service";
import { Observable } from "rxjs";
import { ActionTable } from "src/app/infra/database/dexie/instance/action/schema/action";
export abstract class IActionLocalRepository extends DexieRepository<ActionTable, ActionTable> {}
@@ -47,7 +47,8 @@ export const MessageOutPutDataDTOSchema = z.object({
applicationId: z.number().optional(),
docId: z.number().optional(),
id: z.string().optional()
}))
})),
deviceId: z.string().nullable().optional()
});
export type MessageOutPutDataDTO = z.infer<typeof MessageOutPutDataDTOSchema>
@@ -1,5 +1,8 @@
import { DataSourceReturn } from "src/app/services/Repositorys/type";
import { IMessageGetAllByRoomIdOutPut } from "../../usecase/message/message-get-all-by-room-Id";
import { MessageAttachmentInput } from "../../usecase/message/message-attachment-upload-use-case.service";
import { Result } from "neverthrow";
import { HttpErrorResponse } from "@angular/common/http";
export interface IGetMessagesFromRoomParams {
roomId: string
@@ -8,4 +11,5 @@ export interface IGetMessagesFromRoomParams {
export abstract class IMessageRemoteRepository {
abstract getMessagesFromRoom(input: IGetMessagesFromRoomParams): DataSourceReturn<IMessageGetAllByRoomIdOutPut>
abstract messageAttachmentUpload(input: MessageAttachmentInput): Promise<Result<String, HttpErrorResponse>>
}
@@ -12,8 +12,8 @@ import { SocketMessage } from "src/app/infra/socket/signalR/signalR";
export abstract class IMessageSocketRepository {
abstract connect(): Promise<Result<HubConnection, false>>
abstract sendGroupMessage(data: MessageInputDTO): Promise<Result<MessageCreateOutPutDataDTO, any>>
abstract sendDirectMessage(data: MessageInputDTO): Promise<Result<MessageCreateOutPutDataDTO, any>>
abstract sendGroupMessage(data: MessageInputDTO): Promise<Result<MessageOutPutDataDTO, any>>
abstract sendDirectMessage(data: MessageInputDTO): Promise<Result<MessageOutPutDataDTO, any>>
// abstract sendDeliverAt(): Promise<Result<any, any>>
abstract sendReadAt(data: MessageMarkAsReadInput): Promise<Result<any, any>>
@@ -8,7 +8,7 @@ import { z } from "zod";
const SocketRoomUpdateOutPutSchema = z.object({
id: z.string().uuid(),
roomName: z.string().min(1),
roomName: z.string().nullable(),
createdBy: z.string().nullable(), // Allowing null for createdBy
createdAt: z.string().datetime(),
expirationDate: z.string().nullable().optional(), // Allowing null and making it optional
@@ -7,6 +7,7 @@ import { IMemberRemoteRepository } from 'src/app/core/chat/repository/member/mem
export const AddMemberToRoomInputDTOSchema = z.object({
id: z.string(),
members: z.array(z.number()),
userId: z.number()
});
export type AddMemberToRoomInputDTO = z.infer<typeof AddMemberToRoomInputDTOSchema>
@@ -3,7 +3,7 @@ import { filter, map } from 'rxjs/operators';
import { z } from 'zod';
import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository';
import { MessageEntity } from '../../entity/message';
import { InstanceId } from 'src/app/module/chat/domain/chat-service.service';
import { getInstanceId } from 'src/app/module/chat/domain/chat-service.service';
export const ListenMessageByRoomIdNewInputDTOSchema = z.object({
@@ -25,8 +25,10 @@ export class ListenMessageByRoomIdNewUseCase {
return this.MessageSocketRepositoryService.listenToMessages().pipe(
map(message => message.data),
filter((message) => !message?.requestId?.startsWith(InstanceId) && message?.roomId == data.roomId),
map(message => Object.assign(new MessageEntity(), message))
filter((message) => message?.deviceId != getInstanceId() && data.roomId == message?.roomId),
map(message => {
return Object.assign(new MessageEntity(), message)
})
)
}
@@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import { MessageSocketRepositoryService } from 'src/app/module/chat/data/repository/message/message-live-signalr-data-source.service'
import { InstanceId } from '../../../../module/chat/domain/chat-service.service';
import { filter, map } from 'rxjs/operators';
import { z } from 'zod';
import { IMessageSocketRepository } from '../../repository/message/message-socket-repository';
import { getInstanceId } from 'src/app/module/chat/domain/chat-service.service';
export const ListenSendMessageInputDTOSchema = z.object({
roomId: z.string(),
@@ -25,10 +25,7 @@ export class ListenSendMessageUseCase {
return this.MessageSocketRepositoryService.listenToMessages().pipe(
map(message => message.data),
filter((message) => {
return message?.requestId?.startsWith(InstanceId) && message?.roomId == roomId
}),
filter((message) => message?.deviceId != getInstanceId()),
map(message => message)
)
@@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import { base64Schema } from 'src/app/utils/zod';
import { z } from 'zod';
import { MessageAttachmentFileType, MessageAttachmentSource } from '../../entity/message';
export const MessageAttachmentDTOSchema = z.object({
fileType: z.nativeEnum(MessageAttachmentFileType),
source: z.nativeEnum(MessageAttachmentSource),
file: base64Schema.optional(),
fileName: z.string().optional(),
applicationId: z.number(),
docId: z.number(),
mimeType: z.string().nullable().optional(),
description: z.string().optional(),
senderId: z.number().optional(),
});
export type MessageAttachmentInput = z.infer<typeof MessageAttachmentDTOSchema>
@Injectable({
providedIn: 'root'
})
export class MessageAttachmentUploadUseCaseService {
constructor() { }
execute(input: MessageAttachmentInput) {
}
}
@@ -1,236 +0,0 @@
import { Injectable } from '@angular/core';
import { IMessage, MessageAttachmentSource, MessageEntity, MessageEntitySchema, } from '../../entity/message';
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';
import { InstanceId } from '../../../../module/chat/domain/chat-service.service';
import { createBlobFromBase64, createDataURL } from 'src/app/utils/ToBase64';
import { zodSafeValidation } from 'src/app/utils/zodValidation';
import { Logger } from 'src/app/services/logger/main/service';
import { err, Result } from 'neverthrow';
import { MessageMapper } from '../../mapper/messageMapper';
import { RoomType } from "src/app/core/chat/entity/group";
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
import { MessageTable } from 'src/app/infra/database/dexie/instance/chat/schema/message';
import { MessageAttachmentFileType, MessageOutPutDataDTO } from 'src/app/core/chat/repository/dto/messageOutputDTO';
import { IMessageLocalRepository } from 'src/app/core/chat/repository/message/message-local-repository';
import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository';
import { IMemberLocalRepository } from 'src/app/core/chat/repository/member/member-local-repository';
import { IAttachmentLocalRepository } from 'src/app/core/chat/repository/typing/typing-local-repository';
import { base64Schema } from 'src/app/utils/zod';
export const MessageInputDTOSchema = z.object({
roomId: z.string().uuid().optional(),
receiverId: z.number().optional(),
senderId: z.number(),
message: z.string().nullable().optional(),
messageType: z.number(),
canEdit: z.boolean(),
oneShot: z.boolean(),
requireUnlock: z.boolean(),
requestId: z.string(),
attachment: z.object({
fileType: z.nativeEnum(MessageAttachmentFileType),
source: z.nativeEnum(MessageAttachmentSource),
file: base64Schema.optional(),
fileName: z.string().optional(),
applicationId: z.number().optional(),
docId: z.number().optional(),
mimeType: z.string().nullable().optional(),
description: z.string().optional()
}).optional()
});
export type MessageInputDTO = z.infer<typeof MessageInputDTOSchema>
export const MessageCreatePutDataDTOSchema = z.object({
id: z.string(),
roomId: z.string(),
sender: z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string().optional()
}),
message: z.string().nullable().optional(),
messageType: z.number(),
sentAt: z.string(),
canEdit: z.boolean(),
oneShot: z.boolean(),
requireUnlock: z.boolean(),
requestId: z.string().optional().nullable(),
reactions: z.object({
id: z.string(),
reactedAt: z.string(),
reaction: z.string(),
sender: z.object({}),
}).array(),
info: z.array(z.object({
memberId: z.number(),
readAt: z.string().nullable(),
deliverAt: z.string().nullable()
})),
attachments: z.array(z.object({
fileType: z.nativeEnum(MessageAttachmentFileType),
source: z.nativeEnum(MessageAttachmentSource),
file: z.string().optional(),
fileName: z.string().optional(),
applicationId: z.number().optional(),
docId: z.number().optional(),
id: z.string().optional()
}))
});
export type MessageCreateOutPutDataDTO = z.infer<typeof MessageCreatePutDataDTOSchema>
@Injectable({
providedIn: 'root'
})
export class MessageCreateUseCaseService {
constructor(
private AttachmentLocalRepositoryService: IAttachmentLocalRepository,
private messageLocalDataSourceService: IMessageLocalRepository,
private messageSocketRepositoryService: IMessageSocketRepository,
private MemberListLocalRepository: IMemberLocalRepository
) { }
@XTracerAsync({name:'MessageCreateUseCaseService', module:'chat', bugPrint: true, waitNThrow: 5000})
async execute(message: IMessage, messageEnum: RoomType, tracing?: TracingType) {
const validation = zodSafeValidation<IMessage>(MessageEntitySchema, message)
if(validation.isOk()) {
message.sendAttemp++;
message.requestId = InstanceId +'@'+ uuidv4();
message.sending = true;
const createMessageLocally = this.messageLocalDataSourceService.insert(message)
createMessageLocally.then(async (value) => {
if(value.isOk()) {
const localId = value.value
message.$id = localId
if(message.hasAttachment) {
for (const attachment of message.attachments) {
if(attachment.source != MessageAttachmentSource.Webtrix) {
this.AttachmentLocalRepositoryService.insert({
$messageId: localId,
file: createBlobFromBase64(attachment.file, attachment.mimeType),
fileType: attachment.fileType,
source: attachment.source,
fileName: attachment.fileName,
applicationId: attachment.applicationId,
docId: attachment.docId,
mimeType: attachment.mimeType,
base64: createDataURL(attachment.file, attachment.mimeType)
}).then((e) => {
if(e.isErr()) {
Logger.error('failed to create attachment locally on send message', {
error: e.error,
data: createDataURL(attachment.file, attachment.mimeType).slice(0, 100) +'...'
})
}
})
attachment.safeFile = createDataURL(attachment.file, attachment.mimeType)
}
}
}
} else {
Logger.error('failed to insert locally', {
error: value.error.message
})
}
}).catch((error) => {
Logger.error('failed to insert catch', {
//error: createMessageLocally.error.message
})
})
let sendMessageResult: Result<MessageOutPutDataDTO, any>
if(messageEnum == RoomType.Group) {
const DTO = MessageMapper.fromDomain(message, message.requestId)
message.sending = true
sendMessageResult = await this.messageSocketRepositoryService.sendGroupMessage(DTO)
} else {
const DTO = MessageMapper.fromDomain(message, message.requestId)
delete DTO.roomId
message.sending = true
sendMessageResult = await this.messageSocketRepositoryService.sendDirectMessage(DTO)
}
// return this sendMessageResult
if(sendMessageResult.isOk()) {
message.id = sendMessageResult.value.id
console.log('sendMessageResult', sendMessageResult.value.id)
if(sendMessageResult.value.sender == undefined || sendMessageResult.value.sender == null) {
delete sendMessageResult.value.sender
}
let clone: MessageTable = {
...sendMessageResult.value,
id: sendMessageResult.value.id,
$id : message.$id
}
createMessageLocally.then(() => {
this.messageLocalDataSourceService.update(message.$id, {...clone, sending: false, roomId: clone.roomId}).then((data)=> {
if(data.isOk()) {
} else {
tracing.hasError('failed to update send message')
console.log(sendMessageResult)
console.log(data.error)
}
})
})
return sendMessageResult
} else {
Logger.error('failed to send message to the server', {
error: sendMessageResult.error
})
await this.messageLocalDataSourceService.update(message.$id, {sending: false, $id: message.$id})
return err('no connection')
}
} else {
if(validation.error.formErrors.fieldErrors.attachments) {
Logger.error('failed to send message doe to invalid attachment', {
zodErrorList: validation.error.errors,
data: message.attachments
})
} else {
Logger.error('failed to send message, validation failed', {
zodErrorList: validation.error.errors,
data: message
})
}
}
}
}
@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
import { IMessage, MessageAttachmentSource, MessageEntity, MessageEntitySchema, } from '../../entity/message';
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';
import { InstanceId } from '../../../../module/chat/domain/chat-service.service';
import { createBlobFromBase64, createDataURL } from 'src/app/utils/ToBase64';
import { zodSafeValidation } from 'src/app/utils/zodValidation';
import { Logger } from 'src/app/services/logger/main/service';
@@ -16,7 +15,8 @@ import { IMessageLocalRepository } from 'src/app/core/chat/repository/message/me
import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository';
import { IMemberLocalRepository } from 'src/app/core/chat/repository/member/member-local-repository';
import { IAttachmentLocalRepository } from 'src/app/core/chat/repository/typing/typing-local-repository';
import { base64Schema } from 'src/app/utils/zod';
import { IMessageRemoteRepository } from '../../repository/message/message-remote-repository';
import { SessionStore } from 'src/app/store/session.service';
export const MessageInputDTOSchema = z.object({
@@ -29,16 +29,8 @@ export const MessageInputDTOSchema = z.object({
oneShot: z.boolean(),
requireUnlock: z.boolean(),
requestId: z.string(),
attachment: z.object({
fileType: z.nativeEnum(MessageAttachmentFileType),
source: z.nativeEnum(MessageAttachmentSource),
file: base64Schema.optional(),
fileName: z.string().optional(),
applicationId: z.number().optional(),
docId: z.number().optional(),
mimeType: z.string().nullable().optional(),
description: z.string().optional()
}).optional()
attachment: z.string().array(),
deviceId: z.string().optional()
});
export type MessageInputDTO = z.infer<typeof MessageInputDTOSchema>
@@ -71,15 +63,7 @@ export const MessageCreatePutDataDTOSchema = z.object({
readAt: z.string().nullable(),
deliverAt: z.string().nullable()
})),
attachments: z.array(z.object({
fileType: z.nativeEnum(MessageAttachmentFileType),
source: z.nativeEnum(MessageAttachmentSource),
file: z.string().optional(),
fileName: z.string().optional(),
applicationId: z.number().optional(),
docId: z.number().optional(),
id: z.string().optional()
}))
attachments: z.array(z.string())
});
export type MessageCreateOutPutDataDTO = z.infer<typeof MessageCreatePutDataDTOSchema>
@@ -93,7 +77,8 @@ export class MessageCreateUseCaseService {
private AttachmentLocalRepositoryService: IAttachmentLocalRepository,
private messageLocalDataSourceService: IMessageLocalRepository,
private messageSocketRepositoryService: IMessageSocketRepository,
private MemberListLocalRepository: IMemberLocalRepository
private MemberListLocalRepository: IMemberLocalRepository,
private MessageRemoteRepository: IMessageRemoteRepository
) { }
@@ -103,58 +88,78 @@ export class MessageCreateUseCaseService {
const validation = zodSafeValidation<IMessage>(MessageEntitySchema, message)
if(validation.isOk()) {
console.log("send data", message);
message.sendAttemp++;
message.requestId = InstanceId +'@'+ uuidv4();
const createMessageLocally = await this.messageLocalDataSourceService.insert(message)
const createMessageLocally = this.messageLocalDataSourceService.insert(message)
if(createMessageLocally.isOk()) {
createMessageLocally.then((value) => {
if(value.isOk()) {
message.$id = createMessageLocally.value
console.log("set image")
message.$id = value.value
if(message.hasAttachment) {
if(message.hasAttachment) {
for (const attachment of message.attachments) {
const isWebtrix = attachment.source === MessageAttachmentSource.Webtrix;
for (const attachment of message.attachments) {
if(attachment.source != MessageAttachmentSource.Webtrix) {
this.AttachmentLocalRepositoryService.insert({
$messageId: value.value,
file: createBlobFromBase64(attachment.file, attachment.mimeType),
fileType: attachment.fileType,
source: attachment.source,
fileName: attachment.fileName,
applicationId: attachment.applicationId,
docId: attachment.docId,
mimeType: attachment.mimeType,
base64: createDataURL(attachment.file, attachment.mimeType)
}).then((e) => {
if(e.isErr()) {
Logger.error('failed to create attachment locally on send message', {
error: e.error,
data: createDataURL(attachment.file, attachment.mimeType).slice(0, 100) +'...'
})
}
})
attachment.safeFile = createDataURL(attachment.file, attachment.mimeType)
}
await this.AttachmentLocalRepositoryService.insert({
$messageId: createMessageLocally.value,
file: isWebtrix ? null : createBlobFromBase64(attachment.file || '', attachment.mimeType),
fileType: attachment.fileType,
source: attachment.source,
fileName: attachment.fileName,
applicationId: attachment.applicationId,
docId: attachment.docId,
mimeType: attachment.mimeType,
base64: isWebtrix ? null : createDataURL(attachment.file || '', attachment.mimeType),
id: null,
}).then((e) => {
if(e.isErr()) {
Logger.error('failed to create attachment locally on send message', {
error: e.error,
data: !isWebtrix ? createDataURL(attachment.file, attachment.mimeType).slice(0, 100) +'...' : undefined
})
}
})
if(!isWebtrix) {
attachment.safeFile = createDataURL(attachment.file, attachment.mimeType)
}
} else {
Logger.error('failed to insert locally', {
error: value.error.message
})
}
});
var attachments = await this.AttachmentLocalRepositoryService.find({
$messageId: createMessageLocally.value,
});
if(attachments.isOk()) {
var i = 0;
for (const att of attachments.value) {
console.log(att);
const isWebtrix = att.source === MessageAttachmentSource.Webtrix;
var a = await this.MessageRemoteRepository.messageAttachmentUpload({
fileType: att.fileType,
source: att.source,
file: isWebtrix ? null : att.base64.split(",")[1],
fileName: att.fileName,
applicationId: att.applicationId || 0,
docId: att.docId || 0,
mimeType: att.mimeType,
description: att.description || att.fileName,
senderId: SessionStore.user.UserId,
});
if(a.isOk()) {
att.id = a.value as string;
message.attachments[i].id = a.value as string;
await this.AttachmentLocalRepositoryService.update(att.$id, att)
} else {
return err('failed to upload attachment')
}
}
}
}
//====================
message.sending = true
@@ -175,7 +180,6 @@ export class MessageCreateUseCaseService {
tracing.setAttribute("duration", `Execution time: ${duration}ms`);
// return this sendMessageResult
if(sendMessageResult.isOk()) {
@@ -188,24 +192,23 @@ export class MessageCreateUseCaseService {
delete sendMessageResult.value.sender
}
createMessageLocally.then((value) => {
console.log('sendMessageResult', (sendMessageResult as any).value)
let clone: MessageTable = {
...(sendMessageResult as any).value,
id: (sendMessageResult as any).value.id,
$id : message.$id
}
console.log('set update')
this.messageLocalDataSourceService.update(message.$id, {...clone, sending: false, roomId: clone.roomId}).then((data)=> {
if(data.isOk()) {
} else {
tracing.hasError('failed to update send message')
console.log(sendMessageResult)
console.log(data.error)
}
})
});
console.log('sendMessageResult', (sendMessageResult as any).value)
let clone: MessageTable = {
...(sendMessageResult as any).value,
id: (sendMessageResult as any).value.id,
$id : message.$id
}
this.messageLocalDataSourceService.update(message.$id, {...clone, sending: false, roomId: clone.roomId}).then((data)=> {
if(data.isOk()) {
} else {
tracing.hasError('failed to update send message')
console.log(sendMessageResult)
console.log(data.error)
}
})
return sendMessageResult
@@ -216,21 +219,10 @@ export class MessageCreateUseCaseService {
await this.messageLocalDataSourceService.update(message.$id, {sending: false, $id: message.$id})
return err('no connection')
}
} else {
if(validation.error.formErrors.fieldErrors.attachments) {
Logger.error('failed to send message doe to invalid attachment', {
zodErrorList: validation.error.errors,
data: message.attachments
})
} else {
Logger.error('failed to send message, validation failed', {
zodErrorList: validation.error.errors,
data: message
})
}
}
} else {
console.log('cant send error', validation.error);
}
}
}
@@ -5,6 +5,7 @@ import { MessageSocketRepositoryService } from 'src/app/module/chat/data/reposit
import { SessionStore } from 'src/app/store/session.service';
import { IMessageLocalRepository } from '../../repository/message/message-local-repository';
import { IMessageSocketRepository } from '../../repository/message/message-socket-repository';
import { v4 as uuidv4 } from 'uuid';
const MessageMarkAllMessageAsReadByRoomIdInputSchema = z.object({
roomId: z.string(),
@@ -36,7 +37,7 @@ export class MessageMarkAllMessageAsReadByRoomIdService {
memberId: SessionStore.user.UserId,
messageId: message.id,
roomId: input.roomId,
requestId: 'uuid'
requestId: uuidv4()
});
}
}
@@ -1,22 +1,23 @@
import { Injectable } from '@angular/core';
import { MessageLocalDataSourceService } from '../../../../module/chat/data/repository/message/message-local-data-source.service';
import { MessageSocketRepositoryService } from '../../../../module/chat/data/repository/message/message-live-signalr-data-source.service';
import { InstanceId } from '../../../../module/chat/domain/chat-service.service';
import { MessageMapper } from '../../mapper/messageMapper';
import { v4 as uuidv4 } from 'uuid'
import { AttachmentLocalDataSource } from '../../../../module/chat/data/repository/attachment/attachment-local-repository.service';
import { RoomLocalRepository } from '../../../../module/chat/data/repository/room/room-local-repository.service';
import { MemberListLocalRepository } from 'src/app/module/chat/data/repository/member/member-list-local-repository.service'
import { Result } from 'neverthrow';
import { err, Result } from 'neverthrow';
import { RoomType } from 'src/app/core/chat/entity/group';
import { MessageTable } from 'src/app/infra/database/dexie/instance/chat/schema/message';
import { MessageOutPutDataDTO } from 'src/app/core/chat/repository/dto/messageOutputDTO';
import { MessageAttachmentSource, MessageOutPutDataDTO } from 'src/app/core/chat/repository/dto/messageOutputDTO';
import { IDBoolean } from 'src/app/infra/database/dexie/type';
import { IMemberLocalRepository } from '../../repository/member/member-local-repository';
import { IMessageLocalRepository } from '../../repository/message/message-local-repository';
import { IMessageSocketRepository } from '../../repository/message/message-socket-repository';
import { IRoomLocalRepository } from '../../repository/room/room-local-repository';
import { IAttachmentLocalRepository } from '../../repository/typing/typing-local-repository';
import { IMessageRemoteRepository } from '../../repository/message/message-remote-repository';
import { SessionStore } from 'src/app/store/session.service';
@Injectable({
providedIn: 'root'
@@ -30,6 +31,8 @@ export class SendLocalMessagesUseCaseService {
private roomLocalDataSourceService: IRoomLocalRepository,
private MemberListLocalRepository: IMemberLocalRepository,
private messageSocketRepositoryService: IMessageSocketRepository,
private MessageRemoteRepository: IMessageRemoteRepository,
private AttachmentLocalRepositoryService: IAttachmentLocalRepository,
) { }
async execute() {
@@ -54,19 +57,51 @@ export class SendLocalMessagesUseCaseService {
if(attachments.isOk()) {
message.attachments = attachments.value.map(e => ({
fileType: e.fileType,
source: e.source,
fileName: e.fileName,
applicationId: e.applicationId,
docId: e.docId,
id: e.id,
mimeType: e.mimeType,
description: e.description,
file: e.base64.split(',')[1]
}))
console.log('to upload', messages)
const requestId = InstanceId +'@'+ uuidv4();
message.attachments = attachments.value.map(e => {
const base64 = typeof e.base64 === "string"
? e.base64.replace(/^data:[a-zA-Z]+\/[a-zA-Z]+;base64,/, "")
: "";
return {
fileType: e.fileType,
source: e.source,
fileName: e.fileName,
applicationId: e.applicationId,
docId: e.docId,
id: e.id,
mimeType: e.mimeType,
description: e.description,
file: base64
};
});
var i = 0;
for (const att of attachments.value) {
console.log(att);
const isWebtrix = att.source === MessageAttachmentSource.Webtrix;
var a = await this.MessageRemoteRepository.messageAttachmentUpload({
fileType: att.fileType,
source: att.source,
file: isWebtrix ? null : att.base64.split(",")[1],
fileName: att.fileName,
applicationId: att.applicationId || 0,
docId: att.docId || 0,
mimeType: att.mimeType,
description: att.description || att.fileName,
senderId: SessionStore.user.UserId,
});
if(a.isOk()) {
att.id = a.value as string;
message.attachments[i].id = a.value as string;
await this.AttachmentLocalRepositoryService.update(att.$id, att)
} else {
return err('failed to upload attachment')
}
}
await this.messageLocalDataSourceService.update(message.$id, { sending: true })
@@ -4,11 +4,11 @@ import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/m
import { MessageEntity } from 'src/app/core/chat/entity/message';
import { IBoldLocalRepository } from 'src/app/core/chat/repository/bold/bold-local-repository';
import { IMessageLocalRepository } from 'src/app/core/chat/repository/message/message-local-repository';
import { InstanceId } from '../../../../module/chat/domain/chat-service.service';
import { HttpAdapter } from 'src/app/infra/http/adapter';
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository';
import { IMessageGetAllByRoomIdOutPut } from 'src/app/core/chat/usecase/message/message-get-all-by-room-Id';
import { getInstanceId } from 'src/app/module/chat/domain/chat-service.service';
@Injectable({
@@ -24,8 +24,6 @@ export class RoomBoldSyncUseCaseService {
private roomLocalDataSourceService: IRoomLocalRepository,
) {
this.listenToIncomingMessage();
// this.loadHistory()
//this.onInsertToDB()
this.listenToUpdateMessages();
this.loadHistory()
}
@@ -37,7 +35,7 @@ export class RoomBoldSyncUseCaseService {
private listenToIncomingMessage() {
return this.MessageSocketRepositoryService.listenToMessages().pipe(
map(message => message.data),
filter((message) => !message?.requestId?.startsWith(InstanceId)),
filter((message) => message?.deviceId != getInstanceId()),
map(message => Object.assign(new MessageEntity(), message)),
filter((message) => !message.meSender())
).subscribe(async (message) => {
@@ -59,7 +57,7 @@ export class RoomBoldSyncUseCaseService {
*/
private listenToUpdateMessages() {
return this.MessageSocketRepositoryService.listenToUpdateMessages().pipe(
filter((message) => !message?.requestId?.startsWith(InstanceId)),
filter((message) => message?.deviceId != getInstanceId()),
map(message => Object.assign(new MessageEntity(), message))
).subscribe(async (message) => {
@@ -21,7 +21,7 @@ export const RoomOutPutDTOSchema = z.object({
message: z.string(),
data: z.object({
id: z.string(),
roomName: z.string(),
roomName: z.string().nullable(),
createdBy: z.any().nullable(),
createdAt: z.string(),
expirationDate: z.string().nullable(),
@@ -30,7 +30,7 @@ export const RoomByIdOutputDTOSchema = z.object({
message: z.string(),
data: z.object({
id: z.string(),
roomName: z.string(),
roomName: z.string().nullable(),
createdBy: UserSchema,
createdAt: z.string(),
expirationDate: z.string().nullable(),
@@ -21,7 +21,7 @@ const CreatedBySchema = z.object({
const roomListItemSchema = z.object({
id: z.string(),
roomName: z.string(),
roomName: z.string().nullable(),
createdBy: CreatedBySchema,
createdAt: z.string(),
expirationDate: z.string().nullable(), // api check
@@ -64,7 +64,7 @@ export class RoomSetLastMessageService {
if(room.messages?.[0]?.id == message.id) {
// 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, {
messages: [messageToUpdate]
@@ -170,7 +170,7 @@ export class RoomSetLastMessageService {
}),
map((response: any) => response.value.data as IMessageGetAllByRoomIdOutPut)
).subscribe(async (data)=> {
const loadHistoryFirstMessage = data.data[0]
const loadHistoryFirstMessage = Object.assign(new MessageEntity(), data.data[0]);
if(loadHistoryFirstMessage) {
const roomId = loadHistoryFirstMessage.roomId
@@ -184,7 +184,9 @@ export class RoomSetLastMessageService {
messages: [loadHistoryFirstMessage]
})
} 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()
if(loadHistoryFirstMessage.id == room.value.messages?.[0]?.id) {
@@ -214,7 +216,7 @@ export class RoomSetLastMessageService {
} else if(loadHistoryLastMessageDate == localLastMessageDate) {
// do nothing
} else if(room.value.messages[0].isDeleted != loadHistoryFirstMessage.isDeleted) {
} else if(lastMessage._isDeleted != loadHistoryFirstMessage._isDeleted) {
// await this.roomLocalRepository.update(loadHistoryFirstMessage.roomId, {
// messages: [loadHistoryFirstMessage]
// })
@@ -4,6 +4,9 @@ import { z } from "zod";
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
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 { 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({
roomName: z.string(),
@@ -31,7 +34,7 @@ export const RoomUpdateOutputDTOSchema = z.object({
message: z.string(),
data: z.object({
id: z.string(),
roomName: z.string(),
roomName: z.string().nullable(),
createdBy: UserSchema,
createdAt: z.string(),
expirationDate: z.string().nullable(),
@@ -58,19 +61,21 @@ export class UpdateRoomByIdUseCaseService {
const result = await this.roomRemoteDataSourceService.updateRoom(data)
if(result.isOk()) {
const localList = await this.roomLocalDataSourceService.findAll()
// const { roomsToDelete, roomsToInsert, roomsToUpdate } = roomListDetermineChanges([result.value.data], localList)
// if(result.isOk()) {
// const result = await this.roomRemoteDataSourceService.getRoom(data.roomId)
// for( const roomData of roomsToUpdate) {
// if(!roomData.chatRoom.createdBy?.wxUserId) {
// delete roomData.chatRoom.createdBy;
// }
// if(result.isOk()) {
// const localListRoom = await this.roomLocalDataSourceService.findAll()
// 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
}
@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { IRoomSocketRepository } from 'src/app/core/chat/repository/room/room-socket-repository';
import { GetRoomByIdUseCaseService } from './room-get-by-id-use-case.service'
import { GetRoomListUseCaseService } from 'src/app/core/chat/usecase/room/room-get-list-use-case.service'
@Injectable({
providedIn: 'root'
})
@@ -8,16 +9,18 @@ export class RoomUpdateNameSyncService {
constructor(
private GetRoomByIdUseCaseService: GetRoomByIdUseCaseService,
private roomSocketRepository: IRoomSocketRepository
private roomSocketRepository: IRoomSocketRepository,
private getRoomListUseCaseService: GetRoomListUseCaseService
) {
// this.watch()
this.watch()
}
private watch() {
this.roomSocketRepository.listenToRoomUpdate().subscribe((event) => {
console.log('listenToRoomUpdate')
this.GetRoomByIdUseCaseService.execute(event.data.id)
//this.GetRoomByIdUseCaseService.execute(event.data.id)
this.getRoomListUseCaseService.execute();
})
}
}
@@ -7,7 +7,6 @@ import { z } from 'zod';
const SocketMessageCreateOutputSchema = MessageEntitySchema.pick({
id: true,
attachments: true,
canEdit: true,
editedAt: true,
info: true,
@@ -7,7 +7,6 @@ import { IMessageLocalRepository } from '../../repository/message/message-local-
const SocketMessageDeleteOutputSchema = MessageEntitySchema.pick({
id: true,
attachments: true,
canEdit: true,
editedAt: true,
info: true,
@@ -44,7 +43,7 @@ export class SocketMessageDeleteUseCaseService {
if(result.isOk() && result.value) {
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
}else {
tracing.hasError("failed to delete message")
@@ -11,7 +11,6 @@ import { IMessageLocalRepository } from '../../repository/message/message-local-
const SocketMessageUpdateOutputSchema = MessageEntitySchema.pick({
id: true,
attachments: true,
canEdit: true,
editedAt: true,
info: true,
@@ -0,0 +1,8 @@
import { HttpErrorResponse } from "@angular/common/http";
import { Err, Result } from "neverthrow";
import { HttpResult } from "src/app/infra/http/type";
import { OK } from "zod";
export abstract class ITaskRemoteRepository {
abstract uploadDoc(id: string | number): Promise<Err<HttpResult<unknown>, HttpErrorResponse> | OK<boolean>>
}
+1 -1
View File
@@ -98,7 +98,7 @@ const UserSessionSchema = z.object({
Inactivity: z.boolean(),
UrlBeforeInactivity: z.string(),
UserPermissions: z.unknown(), // Again, you can define it more explicitly if needed
UserPhoto: z.string(),
UserPhoto: z.string().nullable().optional(),
RefreshToken: z.string(),
});
@@ -1,8 +1,8 @@
import { HttpErrorResponse } from "@angular/common/http";
import { Result } from "neverthrow";
import { HttpResult } from "src/app/infra/http/type";
import { UserLoginInput } from "../use-case/user-login-use-case.service";
import { z } from "zod";
import { UserPhotoUploadInput } from "../use-case/user-photo-upload.service";
const UserRepositoryLoginParams = z.object({
Auth: z.string(),
@@ -17,7 +17,7 @@ const UserSchema = z.object({
wxeMail: z.string(),
role: z.string(),
roleId: z.number(),
userPhoto: z.string(),
userPhoto: z.string().nullable().optional(),
adUserSID: z.string(),
});
@@ -55,4 +55,5 @@ export abstract class IUserRemoteRepository {
abstract login(input: IUserRepositoryLoginParams): Promise<Result<HttpResult<UserLoginOutputResponse>, HttpErrorResponse>>
abstract logout(): Promise<Result<HttpResult<any>, HttpErrorResponse>>
abstract refreshToken(input:UserRefreshTokenInputDTO): Promise<Result<HttpResult<any>, HttpErrorResponse>>
abstract uploadPhoto(input: UserPhotoUploadInput): Promise<Result<HttpResult<any>, HttpErrorResponse>>
}
@@ -0,0 +1,58 @@
import { Injectable } from '@angular/core';
import { UserLocalRepositoryService } from 'src/app/module/user/data/datasource/user-local-repository.service';
import { UserRemoteRepositoryService } from 'src/app/module/user/data/datasource/user-remote-repository.service';
import { TracingType } from 'src/app/services/monitoring/opentelemetry/tracer';
import { z, ZodError, ZodSchema } from 'zod';
const UserGetByIdInputSchema = z.object({
username: z.string(),
password: z.string()
})
export type UserGetByIdInput = z.infer<typeof UserGetByIdInputSchema>
export const UserGetByIdOutputSchema = z.object({
success: z.boolean(),
message: z.string(),
data: z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxUser: z.string(),
wxeMail: z.string().email(),
role: z.string(),
roleId: z.number(),
userPhoto: z.string(), // base64 image string
adUserSID: z.string().nullable(),
organicEntityID: z.number(),
organicEntityName: z.string(),
managerId: z.number(),
managerName: z.string(),
status: z.string()
})
});
export type UserGetByIdOutput = z.infer<typeof UserGetByIdOutputSchema>
@Injectable({
providedIn: 'root'
})
export class UserGetByIdService {
constructor(
private remote: UserRemoteRepositoryService,
private local: UserLocalRepositoryService
) { }
async execute(guid: string , tracing?: TracingType) {
const result = await this.remote.getUserProfilePhoto(guid, tracing)
if(result.isOk()) {
this.local.addProfilePicture({base64: result.value.data.data.userPhoto})
} else {
tracing?.setAttribute("picture.upload", "false")
tracing?.hasError("cant upload picture")
}
return result
}
}
@@ -31,6 +31,11 @@ export class UserLogOutUseCaseService {
SessionStore.setUrlBeforeInactivity(this.router.url);
logoutOut == false
document.querySelectorAll('ion-modal:not(.keep-this)').forEach((e: any) => e.remove());
document.querySelectorAll('.popover-viewport:not(.keep-this) ').forEach((e: any) => e.remove());
document.querySelectorAll('.loading-blocker:not(.keep-this)').forEach((e: any) => e.remove());
if (environment.production) {
window.location.pathname = '/auth'
this.notificationService.DeletePostToken()
@@ -90,6 +90,8 @@ export class UserLoginUseCaseService {
return err(result.error.status as LoginError)
}
return err(LoginError.userNotFound);
} else {
tracing.setAttribute('parameter error','true')
// Logger.error('failed to send message doe to invalid attachment', {
@@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { z } from 'zod';
import { IUserRemoteRepository } from '../repository/user-remote-repository';
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
import { UserLocalRepositoryService } from 'src/app/module/user/data/datasource/user-local-repository.service';
// Schema for photo validation
export const UserPhotoUploadInputSchema = z.object({
photo: z.string().regex(/^data:image\/[a-zA-Z]+;base64,/, {
message: 'Photo must start with data:image/...;base64,',
}),
});
export type UserPhotoUploadInput = z.infer<typeof UserPhotoUploadInputSchema>;
@Injectable({
providedIn: 'root'
})
export class UserPhotoUploadUseCase {
constructor(
private remote: IUserRemoteRepository,
private local: UserLocalRepositoryService
) {}
@XTracerAsync({ name: 'UserLoginUseCaseService', module: 'user', bugPrint: true })
async execute(input: unknown, tracing?: TracingType) {
// ✅ Validate input with Zod
const parsed = UserPhotoUploadInputSchema.safeParse(input);
if (!parsed.success) {
tracing?.setAttribute("picture.upload", "false");
tracing?.hasError("invalid input: " + JSON.stringify(parsed.error.format()));
}
const validInput = parsed.data;
const result = await this.remote.uploadPhoto(validInput);
if (result.isOk()) {
this.local.addProfilePicture({ base64: validInput.photo });
} else {
tracing?.setAttribute("picture.upload", "false");
tracing?.hasError("cant upload picture");
}
return result;
}
}
@@ -1,5 +1,4 @@
import { Injectable } from '@angular/core';
import { z } from 'zod';
import { IUserRemoteRepository } from '../repository/user-remote-repository';
import { SessionStore } from 'src/app/store/session.service';
import { Platform } from '@ionic/angular';
+7 -7
View File
@@ -106,10 +106,10 @@ export class HomePage implements OnInit {
this.router.events.subscribe((val) => {
document.querySelectorAll('ion-modal').forEach((e: any) => e.remove())
document.querySelectorAll('popover-viewport').forEach((e: any) => e.remove())
document.querySelectorAll('.loading-blocker').forEach((e: any) => e.remove())
document.querySelectorAll('ion-popover').forEach((e: any) => e.remove())
// document.querySelectorAll('ion-modal').forEach((e: any) => e.remove())
// document.querySelectorAll('popover-viewport').forEach((e: any) => e.remove())
// document.querySelectorAll('.loading-blocker').forEach((e: any) => e.remove())
// document.querySelectorAll('ion-popover').forEach((e: any) => e.remove())
});
window['platform'] = platform
@@ -118,9 +118,9 @@ export class HomePage implements OnInit {
if (window.location.pathname != '/inactivity' && window.location.pathname != '/') {
document.querySelectorAll('ion-modal').forEach((e: any) => e.remove());
document.querySelectorAll('.popover-viewport').forEach((e: any) => e.remove());
document.querySelectorAll('.loading-blocker').forEach((e: any) => e.remove());
document.querySelectorAll('ion-modal:not(.keep-this)').forEach((e: any) => e.remove());
document.querySelectorAll('.popover-viewport:not(.keep-this) ').forEach((e: any) => e.remove());
document.querySelectorAll('.loading-blocker:not(.keep-this)').forEach((e: any) => e.remove());
const pathname = window.location.pathname
SessionStore.setUrlBeforeInactivity(pathname)
@@ -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));
}
}
@@ -0,0 +1,27 @@
import { EntityTable } from 'Dexie';
import { z } from 'zod';
export enum ActionDetailsStatus {
editedLocal,
deletedLocal,
offline,
pending,
sending,
synced,
}
export const ActionTableSchema = z.object({
processId: z.number().nullable().optional(),
//localProcessId: z.string(), // unique identifier
dateIndex: z.string().transform((val) => new Date(val)), // store as ISO string, transform to Date
description: z.string(),
detail: z.string(),
dateBegin: z.string().transform((val) => new Date(val)),
dateEnd: z.string().transform((val) => new Date(val)),
statusIndex: z.number().int().min(0).max(Object.keys(ActionDetailsStatus).length / 2 - 1),
errorCreating: z.boolean().nullable().optional(),
})
export type ActionTable = z.infer<typeof ActionTableSchema>;
export type DexieActionTable = EntityTable<ActionTable, 'processId'>;
export const ActionTableColumn = 'processId, dateIndex, description, detail, dateBegin, dateEnd, statusIndex, errorCreating';
@@ -0,0 +1,24 @@
import { EntityTable } from 'Dexie';
import { z } from 'zod';
export const PublicationTableSchema = z.object({
processId: z.number(),
documentId: z.number(),
applicationId: z.number(),
organicEntityId: z.number(),
securityId: z.number(),
userId: z.number(),
dateIndex: z.string(),
phase: z.number(),
title: z.string(),
message: z.string(),
datePublication: z.string(),
isExported: z.boolean(),
sourceLocation: z.number(),
sourceProcessId: z.number(),
sourceDocumentId: z.number(),
})
export type PublicationTable = z.infer<typeof PublicationTableSchema>;
export type DexiePublicationTable = EntityTable<PublicationTable, 'documentId'>;
export const PublicationTableColumn = 'documentId, processId, applicationId, organicEntityId, securityId, userId, dateIndex, phase, title, message, datePublication, sourceLocation, sourceProcessId, isExported, sourceDocumentId';
@@ -0,0 +1,16 @@
import { EntityTable } from 'Dexie';
import { z } from 'zod';
export const PublicationFileTableSchema = z.object({
id: z.number().optional(),
file: z.string(),
extension: z.string(),
name: z.string(),
path: z.string(),
documentId: z.number(),
datePublication: z.string(),
})
export type PublicationFileTable = z.infer<typeof PublicationFileTableSchema>;
export type DexiePublicationFilesTable = EntityTable<PublicationFileTable, 'id'>;
export const PublicationFileTableColumn = '++id, file, extension, name, path, documentId, datePublication';
@@ -0,0 +1,22 @@
import { Dexie } from 'Dexie';
import { ActionTableColumn, DexieActionTable } from './schema/action';
import { DexiePublicationTable, PublicationTable, PublicationTableColumn } from './schema/publication';
import { DexiePublicationFilesTable, PublicationFileTable, PublicationFileTableColumn } from './schema/publicationFile';
import FDBFactory from 'fake-indexeddb/lib/FDBFactory';
import FDBKeyRange from 'fake-indexeddb/lib/FDBKeyRange';
export const actionDatabase = new Dexie('action-database-v1', {
//indexedDB: new FDBFactory,
//IDBKeyRange: FDBKeyRange, // Mocking IDBKeyRange
}) as Dexie & {
action: DexieActionTable,
publication: DexiePublicationTable,
publicationFile: DexiePublicationFilesTable
};
actionDatabase.version(1).stores({
action: ActionTableColumn,
publication: PublicationTableColumn,
publicationFile: PublicationFileTableColumn
});
@@ -7,7 +7,7 @@ export const AttachmentTableSchema = z.object({
$id: z.number().optional(), // local id
$messageId: z.string(),
attachmentId: z.string().optional(),
file: z.instanceof(Blob).optional(),
file: z.instanceof(Blob).nullable().optional(),
base64: zodDataUrlSchema.nullable().optional(),
//
fileType: z.nativeEnum(MessageAttachmentFileType).optional(),
@@ -16,10 +16,11 @@ export const AttachmentTableSchema = z.object({
applicationId: z.number().optional(),
docId: z.number().optional(),
mimeType: z.string().nullable().optional(),
id: z.string().uuid().optional(),
description: z.string().optional()
})
id: z.string().uuid().nullable().optional(),
description: z.string().optional(),
filePath: z.string().optional().nullable()
});
export type AttachmentTable = z.infer<typeof AttachmentTableSchema>
export type DexieAttachmentsTableSchema = EntityTable<AttachmentTable, '$id'>;
export const AttachmentTableColumn = '++$id, id, $messageId, messageId, file'
export const AttachmentTableColumn = '++$id, id, $messageId, messageId, file, filePath'
@@ -8,7 +8,7 @@ export const MemberTableSchema = z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string().nullable(),
userPhoto: z.string().nullable().optional(),
joinAt: z.string(),
status: z.string().optional(), // useless
isAdmin: z.boolean()
@@ -33,7 +33,8 @@ export const MessageTableSchema = z.object({
info: z.array(z.object({
memberId: z.number(),
readAt: z.string().nullable(),
deliverAt: z.string().nullable()
deliverAt: z.string().nullable(),
isDeleted: z.boolean().optional(),
})).optional(),
attachments: z.array(z.object({
fileType: z.nativeEnum(MessageAttachmentFileType),
@@ -7,7 +7,7 @@ import { IDBoolean } from "../../../type";
export const RoomTableSchema = z.object({
$id: z.string().optional(),
id: z.string().optional(),
roomName: z.string(),
roomName: z.string().nullable(),
createdBy: z.object({
wxUserId: z.number(),
wxFullName: z.string(),
@@ -27,4 +27,19 @@ export class FilePickerWebService {
})
}
getAnyFileFromDevice(): Promise<Result<File, any>> {
let input = document.createElement('input');
input.type = 'file';
input.click();
return new Promise((resolve, reject)=>{
input.onchange = async () => {
const file = Array.from(input.files)
resolve(ok(file[0] as File));
};
})
}
}
+51 -93
View File
@@ -3,136 +3,94 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ok, err, Result } from 'neverthrow';
import { HttpResult } from './type';
import { BehaviorSubject, Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { BehaviorSubject } from 'rxjs';
import { IHttpOptions } from './adapter';
import { TracingType } from 'src/app/services/monitoring/opentelemetry/tracer';
@Injectable({
providedIn: 'root'
})
export class HttpService {
pendingRequests = new Set<string>();
private responseSubject = new BehaviorSubject<Result<HttpResult<any>, HttpErrorResponse>>(null);
constructor(private http: HttpClient) { }
async post<T>(url: string, body: any): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
private async handleRequest<T>(fux: Function, tracing?: TracingType, url?: string, method?: string): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
const requestKey = `${method}:${url.replace(":", "")}`;
this.pendingRequests.add(requestKey);
try {
const response = await this.http.post<T>(url, body, { observe: 'response' }).toPromise();
const data = {
data: response.body,
status: response.status,
headers: response.headers,
url: response.url || url,
method: '',
}
this.responseSubject.next(ok(data))
return ok(data);
} catch (e) {
this.responseSubject.next(err(e))
return err(e as HttpErrorResponse);
}
}
async get<T>(url: string, options?: IHttpOptions): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
try {
let httpParams = new HttpParams();
if (options?.params) {
Object.keys(options.params).forEach(key => {
if(options.params[key]) {
httpParams = httpParams.append(key, options.params[key]);
}
});
}
const httpOptions = {
params: httpParams,
headers: options?.headers as any || new HttpHeaders(),
responseType: options?.responseType || 'json' as any,
};
const response = await this.http.get<T>(url, { ...httpOptions, observe: 'response', responseType: httpOptions.responseType }).toPromise();
const data = {
method: 'GET',
data: response.body,
status: response.status,
headers: response.headers,
url: response.url || url
}
this.responseSubject.next(ok(data))
return ok(data);
} catch (e) {
this.responseSubject.next(err(e))
return err(e as HttpErrorResponse);
}
}
async put<T>(url: string, body: any): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
try {
const response = await this.http.put<T>(url, body, { observe: 'response' }).toPromise();
var response = await fux(tracing) as HttpResponse<T>
const data = {
data: response.body,
status: response.status,
headers: response.headers,
url: response.url || url,
method: '',
method: method || '',
}
this.responseSubject.next(ok(data))
return ok(data);
} catch (e) {
this.responseSubject.next(err(e))
return err(e as HttpErrorResponse);
} finally {
this.pendingRequests.delete(requestKey);
}
}
async patch<T>(url: string, body: any = {}): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
try {
const response = await this.http.patch<T>(url, body, { observe: 'response' }).toPromise();
hasPendingRequest(method: string, path: string): boolean {
for (const key of this.pendingRequests) {
const [reqMethod, reqUrl] = key.split(':');
const data = {
data: response.body,
status: response.status,
headers: response.headers,
url: response.url || url,
method: '',
if (reqMethod == method && reqUrl.toLowerCase().includes(path.toLowerCase())) {
return true;
}
this.responseSubject.next(ok(data))
return ok(data);
} catch (e) {
this.responseSubject.next(err(e))
return err(e as HttpErrorResponse);
}
return false;
}
async delete<T>(url: string, body = {}): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
async post<T>(url: string, body: any, tracing?: TracingType): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
return await this.handleRequest<T>(async () => await this.http.post<T>(url, body, { observe: 'response' }).toPromise(), tracing, url, 'POST')
}
async get<T>(url: string, options?: IHttpOptions, tracing?: TracingType): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
let httpParams = new HttpParams();
if (options?.params) {
Object.keys(options.params).forEach(key => {
if(options.params[key]) {
httpParams = httpParams.append(key, options.params[key]);
}
});
}
const httpOptions = {
params: httpParams,
headers: options?.headers as any || new HttpHeaders(),
responseType: options?.responseType || 'json' as any,
};
return await this.handleRequest<T>(async () => await this.http.get<T>(url, { ...httpOptions, observe: 'response', responseType: httpOptions.responseType }).toPromise(), tracing, url, 'GET')
}
async put<T>(url: string, body: any, tracing?: TracingType): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
return await this.handleRequest<T>(async () => await this.http.put<T>(url, body, { observe: 'response' }).toPromise(), tracing, url, 'PUT')
}
async patch<T>(url: string, body: any = {}, tracing?: TracingType): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
return await this.handleRequest<T>(async () => await this.http.patch<T>(url, body, { observe: 'response' }).toPromise(), tracing,url, 'PATCH');
}
async delete<T>(url: string, body = {}, tracing?: TracingType): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
const options = {
body: body, // Pass payload as the body of the request
observe: 'response' as 'body'
};
try {
const response: any = await this.http.delete<T>(url, options).toPromise();
const data = {
data: response?.body,
status: response?.status,
headers: response?.headers,
url: response?.url || url,
method: '',
}
this.responseSubject.next(ok(data))
return ok(data as any);
} catch (e) {
this.responseSubject.next(err(e))
return err(e as HttpErrorResponse);
}
return await this.handleRequest<T>(async () => await this.http.delete<T>(url, options).toPromise(), tracing, url, 'DELETE');
}
listen() {
+7
View File
@@ -15,3 +15,10 @@ export interface HttpResult<T> {
}
export type IHttPReturn<T> = Promise<Result<HttpResult<T>, HttpErrorResponse>>
export interface ApiResponse<T> {
success: boolean,
message: string,
data: T
}
@@ -13,8 +13,6 @@ export class MetricsInterceptor implements HttpInterceptor {
if (event instanceof HttpResponse) {
// Capture the status code and check protocol
if (req.method !== 'GET' && !req.urlWithParams.includes('metrics')) {
console.log('response', event.body)
const path = req.urlWithParams;
const url = new URL(path);
if (window.location.protocol !== 'https:') {
@@ -13,11 +13,10 @@ import { catchError, filter, take, switchMap, tap } from "rxjs/operators";
import { SessionStore } from '../../../store/session.service';
import { environment } from "src/environments/environment";
import { Router } from "@angular/router";
import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
import { Platform } from '@ionic/angular';
import { SessionExpiredModalService } from 'src/app/services/session-expired-modal.service';
import { UserLoginOutputResponse } from "../../../core/user/repository/user-remote-repository";
import { UserLoginMapper } from "../../../core/user/mapper/user-login";
import { UserSession } from "../../../models/user.model";
import { IRoomLocalRepository } from "src/app/core/chat/repository/room/room-local-repository";
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
@@ -26,13 +25,14 @@ export class TokenInterceptor implements HttpInterceptor {
null
);
private excludedDomains = [ 'Login', environment.apiChatUrl, 'http://localhost:8019']; // Add the domains you want to exclude
private excludedDomains = [ 'Login', 'http://localhost:8019']; // Add the domains you want to exclude
constructor(
private http: HttpClient,
private router: Router,
private httpErrorHandle: HttpErrorHandle,
private platform: Platform) { }
private sessionExpiredModal: SessionExpiredModalService,
private platform: Platform,
private roomLocalRepository: IRoomLocalRepository) { }
intercept(
@@ -51,14 +51,12 @@ export class TokenInterceptor implements HttpInterceptor {
return next.handle(request).pipe(
catchError((error) => {
console.log('interceptor ',error)
if(error.url.includes('/Users/RefreshToken') && error.status === 401) {
console.log("refresh token error11",error)
return throwError(error);
}
else if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(request, next);
} else if (error.url.includes('https://gdapi-dev.dyndns.info/stage/api/v2') && error.status === 0){
} else if (error.url.includes(`${environment.apiURLStage.slice(0, -1)}`) && error.status === 0) {
return this.handle401Error(request, next);
} else {
return throwError(error);
@@ -119,7 +117,7 @@ export class TokenInterceptor implements HttpInterceptor {
}
return this.http
.post<UserLoginOutputResponse>('https://gdapi-dev.dyndns.info/stage/api/v2/Users/RefreshToken', {
.post<UserLoginOutputResponse>(`${environment.apiURLStage}Users/RefreshToken`, {
authorization: SessionStore.user.Authorization,
refreshToken: SessionStore.user.RefreshToken,
channelId
@@ -136,22 +134,22 @@ export class TokenInterceptor implements HttpInterceptor {
}),
catchError((error) => {
console.log("refresh token error",error)
// SessionStore.user.Authorization = SessionStore.user.Authorization;
// SessionStore.user.RefreshToken = SessionStore.user.RefreshToken;
SessionStore.setInativity(false)
/* SessionStore.setUrlBeforeInactivity(this.router.url); */
if (environment.production) {
window.location.pathname = '/auth'
} else {
const pathBeforeGoOut = window.location.pathname
console.log('Before auth',window.location.pathname)
this.router.navigateByUrl('/auth', { replaceUrl: true }).then(() =>{
if(pathBeforeGoOut != "/auth") {
this.httpErrorHandle.httpsSucessMessagge('sessonExpired')
if(window.location.pathname.includes('/home/')) {
const sessionExpiredMessage =
'A sua sessão expirou. Por favor, faça login novamente para continuar.';
const goToAuth = () => {
if (environment.production) {
window.location.pathname = '/auth';
} else if (!this.router.url.startsWith('/auth')) {
this.router.navigateByUrl('/auth', { replaceUrl: true });
}
})
};
console.log('sessionExpiredModal', this.sessionExpiredModal)
this.roomLocalRepository.clear();
void this.sessionExpiredModal.present(sessionExpiredMessage, goToAuth);
}
return of(false);
})
+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
// }
// }
@@ -4,9 +4,6 @@ import { ZodError, ZodObject, ZodSchema } from 'zod';
import { Logger } from 'src/app/services/logger/main/service';
import { IDexieRepository, ILocalModel, RepositoryResult } from '../adapter'
import { IDBError, IDBErrorParams, IDexieError } from '../types';
// Define a type for the Result of repository operations
class DBError<T> extends Error implements IDBError<T> {
zodError?: ZodError<T>;
parameters: T;
@@ -7,6 +7,7 @@ import { switchMap } from 'rxjs/operators';
import { err, Result } from 'neverthrow';
import { HubConnection } from '@microsoft/signalr';
import { ISignalRInput, ISignalROutput } from '../type';
import { environment } from 'src/environments/environment';
const { App } = Plugins;
@@ -53,7 +54,7 @@ export class SignalRService {
async establishConnection(): Promise<Result<HubConnection, false>> {
// const connection = new SignalRConnection({url:'https://41e3-41-63-166-54.ngrok-free.app/api/v2/chathub'})
const connection = new SignalRConnection({url:'https://gdapi-dev.dyndns.info/stage/api/v2/chathub'})
const connection = new SignalRConnection({url:`${environment.apiURLStage}chathub`})
const attempConnection = await connection.establishConnection()
if(attempConnection.isOk()) {
+3 -3
View File
@@ -131,8 +131,8 @@ export class SignalRConnection {
this.sendDataSubject.pipe(
filter((message) => {
return input.data.requestId == message?.data.requestId ||
input?.data?.roomName == message?.data.roomName && typeof input?.data?.roomName == 'string'
return input.data.requestId == message?.data?.requestId ||
input?.data?.roomName == message?.data?.roomName && typeof input?.data?.roomName == 'string'
}),
).subscribe(value => {
@@ -161,7 +161,7 @@ export class SignalRConnection {
const methods = ['ReceiveMessage', 'TypingMessage', 'AvailableUsers',
'ReadAt', 'DeleteMessage', 'UpdateMessage', 'GroupAddedMembers',
'GroupDeletedMembers', 'UserAddGroup']
'GroupDeletedMembers', 'UserAddGroup', 'GroupUpdate']
for(const method of methods) {
this.hubConnection.on(method, (message: any) => {
@@ -108,7 +108,7 @@ export class CreateProcessPage implements OnInit {
this.fulltask = this.navParams.get('fulltask');
// console.log('this.fulltask', this.fulltask)
console.log('this.fulltask', this.fulltask)
// if(this.fulltask?.Documents) {
+4 -4
View File
@@ -22,11 +22,11 @@ export class CropImagePage implements OnInit {
private modalController: ModalController,
) {
this.base64ToCroppe = this.navParams.get('base64ToCroppe')
console.log('To cropp',this.base64ToCroppe)
//console.log('To cropp',this.base64ToCroppe)
}
ngOnInit() {
console.log('To cropp',this.base64ToCroppe)
//console.log('To cropp',this.base64ToCroppe)
}
fileChangeEvent(event: any): void {
@@ -37,8 +37,8 @@ export class CropImagePage implements OnInit {
imageCropped(event: ImageCroppedEvent, tracing?: TracingType) {
this.croppedImage = event.base64;
console.log('Croped image', event)
console.log('Croped image 22', this.croppedImage)
//console.log('Croped image', event)
//console.log('Croped image 22', this.croppedImage)
tracing.addEvent('Croped image')
tracing.setAttribute('outcome','success')
// event.blob can be used to upload the cropped image
@@ -62,21 +62,10 @@
<div mat-icon-button [matMenuTriggerFor]="menu" aria-label="Example icon-button with a menu">
<div class="d-flex align-center flex-column" >
<div *ngIf="profilePictureSubject == undefined ">
<img
class="profile-pic" src="assets/images/theme/gov/icons-profile.svg">
<!-- <img *ngIf="SessionStore.user.RoleDescription == 'Presidente da República' " class="profile-pic"
src='assets/images/presidente.png'>
<img *ngIf="SessionStore.user.RoleDescription == 'Ministro e Director do Gabinete do PR' "
class="profile-pic" src='assets/images/ministro.png'>
<img *ngIf="SessionStore.user.RoleDescription == 'Secretário Geral' " class="profile-pic"
src='assets/images/secretaria_geral.png'> -->
</div>
<div *ngIf="(profilePictureSubject | async) as calendarData">
<img class="profile-pic" src={{calendarData.base64}}>
<img *ngIf="calendarData.base64 != null" class="profile-pic" src={{calendarData.base64}}>
<img *ngIf="calendarData.base64 == null" class="profile-pic" src="assets/images/theme/gov/icons-profile.svg">
</div>
@@ -125,29 +114,6 @@
</div>
</div>
<div class="profile-title d-flex justify-space-between align-center width-100">
<div class="d-flex align-center">
<div>Tema</div>
</div>
</div>
<div class="profile-title d-flex justify-space-between align-center width-100">
<div class="d-flex align-center">
<div class="btn-close d-flex cursor-pointer" (click)="changeTheme('gov')">
<img style="width: 40px;" src="assets/images/theme/gov/governoangola_A.png">
</div>
<div class="btn-close d-flex cursor-pointer pr-10 pl-10" (click)="changeTheme('default')">
<img style="width: 40px;" src="assets/images/logo-removebg-preview.png" />
</div>
<div class="btn-close d-flex cursor-pointer pr-10 pl-10" (click)="changeTheme('default')">
<img style="width: 40px;" src="assets/images/logo-removebg-preview.png" />
</div>
</div>
</div>
</div>
@@ -16,6 +16,7 @@ import { UserRepositoryService } from 'src/app/module/user/data/user-repository.
import { isHttpError } from 'src/app/services/http.service';
import { UserProfilePicture } from 'src/app/module/user/data/datasource/user-local-repository.service';
import { Observable } from 'rxjs';
import { UserPhotoUploadUseCase } from 'src/app/core/user/use-case/user-photo-upload.service';
@Component({
selector: 'app-edit-profile',
@@ -43,8 +44,8 @@ export class EditProfilePage implements OnInit {
private CameraService: CameraService,
private toastService: ToastService,
private httpErrorHandle: HttpErrorHandle,
private UserRepositoryService: UserRepositoryService
private UserRepositoryService: UserRepositoryService,
private UserPhotoUploadUseCase: UserPhotoUploadUseCase
) {
this.profilePictureSubject = this.UserRepositoryService.getProfilePictureLive() as any
}
@@ -178,33 +179,29 @@ export class EditProfilePage implements OnInit {
if(capturedImage.isOk()) {
this.capturedImage = capturedImage.value;
var object = {
"ImageBase64": this.capturedImage
}
tracing.addEvent('serialize image')
console.log(this.capturedImage);
const guid = await this.UserRepositoryService.addUserProfilePhoto(object)
var guid = await this.UserPhotoUploadUseCase.execute({photo: 'data:image/jpeg;base64,' +this.capturedImage}, tracing)
if(guid.isOk()) {
tracing.addEvent('upload image')
const base = await this.UserRepositoryService.getUserProfilePhoto(guid.value, tracing)
// const base = await this.UserRepositoryService.getUserProfilePhoto(guid.value, tracing)
if(base.isOk()) {
tracing.addEvent('download image')
this.profilePicture = 'data:image/jpeg;base64,' + base.value;
// if(base.isOk()) {
// tracing.addEvent('download image')
// this.profilePicture = 'data:image/jpeg;base64,' + base.value;
tracing.setAttribute("picture.save", "true")
// tracing.setAttribute("picture.save", "true")
} else {
if(!isHttpError(base.error)) {
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico.')
} else {
this.httpErrorHandle.httpStatusHandle(base.error)
}
}
// } else {
// if(!isHttpError(base.error)) {
// this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico.')
// } else {
// this.httpErrorHandle.httpStatusHandle(base.error)
// }
// }
} else {
if(!isHttpError(guid.error)) {
+2 -23
View File
@@ -38,31 +38,10 @@
<div class="profile-content">
<div class="d-flex align-center flex-column">
<div *ngIf="profilePictureSubject == undefined ">
<img
class="profile-pic" src="assets/images/theme/gov/icons-profile.svg">
<!-- <img *ngIf="SessionStore.user.RoleDescription == 'Presidente da República' " class="profile-pic"
src='assets/images/presidente.png'>
<img *ngIf="SessionStore.user.RoleDescription == 'Ministro e Director do Gabinete do PR' " class="profile-pic"
src='assets/images/ministro.png'>
<img *ngIf="SessionStore.user.RoleDescription == 'Secretário Geral' " class="profile-pic"
src='assets/images/secretaria_geral.png'> -->
</div>
<div *ngIf="(profilePictureSubject | async) as calendarData"class="profile-pic">
<img class="profile-pic"
src={{calendarData.base64}}>
<img *ngIf="calendarData.base64 != null" class="profile-pic" src={{calendarData.base64}}>
<img *ngIf="calendarData.base64 == null" class="profile-pic" src="assets/images/theme/gov/icons-profile.svg">
</div>
<!-- <ion-icon *ngIf="ThemeService.currentTheme == 'default' " class="profile-pic"
src="assets/images/icons-default-profile.svg"></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'gov' " class="profile-pic"
src="assets/images/theme/gov/icons-profile.svg"></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'doneIt' " class="profile-pic"
src="assets/images/theme/{{ThemeService.currentTheme}}/icons-profile.svg"></ion-icon> -->
</div>
<div class="profile-name d-flex justify-content-center width-100">
+1
View File
@@ -352,6 +352,7 @@ export class ProfilePage implements OnInit {
}
logout() {
this.modalController.dismiss();
this.UserService.logout();
}
@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { SessionExpiredModalPage } from './session-expired-modal.page';
@NgModule({
declarations: [SessionExpiredModalPage],
imports: [CommonModule, IonicModule],
exports: [SessionExpiredModalPage],
})
export class SessionExpiredModalPageModule {}
@@ -0,0 +1,10 @@
<ion-content class="session-expired-content ion-padding">
<div class="session-expired-inner">
<ion-icon class="session-expired-icon" src="/assets/images/notification-error.svg"></ion-icon>
<h2 class="session-expired-title">Sessão expirada</h2>
<p class="session-expired-text">{{ message }}</p>
<button (click)="dismissAndContinue()" class="btn-cancel" fill="clear" color="#fff">
<ion-label>Ok</ion-label>
</button>
</div>
</ion-content>
@@ -0,0 +1,33 @@
.session-expired-content {
--background: transparent;
}
.session-expired-inner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100%;
max-width: 400px;
margin: 0 auto;
text-align: center;
}
.session-expired-icon {
width: 48px;
height: 48px;
margin-bottom: 16px;
}
.session-expired-title {
margin: 0 0 12px;
font-size: 1.25rem;
font-weight: 600;
}
.session-expired-text {
margin: 0 0 24px;
line-height: 1.4;
font-size: 1rem;
color: var(--ion-color-medium-shade, #666);
}
@@ -0,0 +1,19 @@
import { Component, Input } from '@angular/core';
import { ModalController } from '@ionic/angular';
@Component({
selector: 'app-session-expired-modal',
templateUrl: './session-expired-modal.page.html',
styleUrls: ['./session-expired-modal.page.scss'],
})
export class SessionExpiredModalPage {
@Input() message = '';
@Input() onConfirm: () => void = () => {};
constructor(private modalController: ModalController) {}
async dismissAndContinue() {
await this.modalController.dismiss();
this.onConfirm();
}
}
@@ -22,6 +22,7 @@ export class ViewDocumentPage implements OnInit {
docId: any
task: ExpedientTaskModalPageNavParamsTask;
Document: any
link: any;
loader = true
attachment: SearchList[] = [];
@@ -38,6 +39,7 @@ export class ViewDocumentPage implements OnInit {
this.Document = this.navParams.get('Document')
this.task = this.navParams.get('task')
this.attachment = this.navParams.get('attachment')
this.link = this.navParams.get('link')
if(!this.file.title) {
@@ -57,38 +59,35 @@ export class ViewDocumentPage implements OnInit {
ngOnInit() {
this.processes.GetViewer(this.docId, this.applicationId).subscribe(async(res)=> {
if(this.link == undefined) {
this.processes.GetViewer(this.docId, this.applicationId).subscribe(async(res)=> {
const link: string = res.replace('//pdfjs/web/', '/pdfjs/web/')
this.trustedUrl = this.sanitazer.bypassSecurityTrustResourceUrl(link);
const link: string = res.replace('//pdfjs/web/', '/pdfjs/web/')
this.trustedUrl = this.sanitazer.bypassSecurityTrustResourceUrl(link);
// const iframe = document.getElementById("iframe")
// const handleLoad = () => {
// this.loader = false
// };
// iframe.addEventListener('load', handleLoad, true)
if(res == "") {
const alert = await this.alertController.create({
cssClass: 'my-custom-class',
//header: 'Apagar evento!',
message: 'Sem imagem',
buttons: [
{
text: 'Sim',
handler: () => {
this.close();
if(res == "") {
const alert = await this.alertController.create({
cssClass: 'my-custom-class',
//header: 'Apagar evento!',
message: 'Sem imagem',
buttons: [
{
text: 'Sim',
handler: () => {
this.close();
}
}
}
]
});
}
}, ()=>{
this.close();
});
]
});
}
}, ()=>{
this.close();
});
} else {
//console.log(this.link);
this.trustedUrl = this.sanitazer.bypassSecurityTrustResourceUrl(this.link);
}
}
+6 -6
View File
@@ -38,13 +38,13 @@ export class ViewMediaPage implements OnInit {
ngOnInit() {
this.base64Sanitize = this.sanitizer.bypassSecurityTrustResourceUrl(this.image);
this.base64Sanitize = this.sanitizer.bypassSecurityTrustResourceUrl(this.image);
if (this.platform.is('desktop')) {
this.view = true;
} else {
this.view = false;
}
if (this.platform.is('desktop')) {
this.view = true;
} else {
this.view = false;
}
}
+1
View File
@@ -12,6 +12,7 @@ export class ExpedientTaskModalPageNavParamsTask {
SourceType: 'DOC' | 'FOLDER',
SourceID: any // doc id
}
Documents?: any[]
}
export class task extends ExpedientTaskModalPageNavParamsTask {}
@@ -160,7 +160,7 @@ const OwnerSchema = z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string(),
userPhoto: z.string().nullable().optional(),
});
+1 -2
View File
@@ -1,8 +1,7 @@
export interface Environment {
id: string;
apiURL: string;
apiChatUrl: string;
apiWsChatUrl: string;
apiURLStage: String
apiPCURL: string;
logoLabel: string;
production: boolean;
+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() {}
}
@@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { IActionLocalRepository } from 'src/app/core/chat/repository/action/member-local-repository';
import { ActionTable, ActionTableSchema } from 'src/app/infra/database/dexie/instance/action/schema/action';
import { actionDatabase } from 'src/app/infra/database/dexie/instance/action/service';
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
@Injectable({
providedIn: 'root'
})
export class ActionLocalRepositoryService extends DexieRepository<ActionTable, ActionTable> implements IActionLocalRepository {
constructor() {
super(actionDatabase.action, ActionTableSchema, actionDatabase)
}
}
@@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
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 { 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 { PublicationListByProcessIdOutPut } from 'src/app/core/actions/use-case/publication-list-by-process-id.service';
import { HttpService } from 'src/app/infra/http/http.service';
import { ApiResponse } from 'src/app/infra/http/type';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class ActionRemoteRepositoryService {
private baseUrl = `${environment.apiURLStage.slice(0, -1)}/PresidentialActions`; // Your base URL
constructor(
private http: HttpService
) { }
async actionGetAll() {
return await this.http.get<ApiResponse<ActionGetAllOutPut>>(`${this.baseUrl}`);
}
async create(input: ActionsCreateInput) {
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) {
return await this.http.get<ApiResponse<PublicationListByProcessIdOutPut>>(`${this.baseUrl}/${processId}/Posts`);
}
async postGetDocumentListByProcessId(documentId: number) {
var result = await this.http.get<ApiResponse<PublicationGetDocumentByProcessIdOutPut>>(`${this.baseUrl}/Posts/${documentId}/file`);
return result.map((e) => {
// Add a new attribute to each item
e.data.data = e.data.data.map((item) => ({
...item,
documentId,
}));
return e;
});
}
}
@@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { PublicationFileTable, PublicationFileTableSchema } from 'src/app/infra/database/dexie/instance/action/schema/publicationFile';
import { actionDatabase } from 'src/app/infra/database/dexie/instance/action/service';
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
@Injectable({
providedIn: 'root'
})
export class PublicationFileLocalRepositoryService extends DexieRepository<PublicationFileTable, PublicationFileTable> {
constructor() {
super(actionDatabase.publicationFile, PublicationFileTableSchema, actionDatabase)
}
}
@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { PublicationFileGetByDocumentIdInput, PublicationFileGetByDocumentIdOutPut } from 'src/app/core/actions/use-case/publication-file-get-by-document-id.service';
import { PublicationListByProcessIdOutPut } from 'src/app/core/actions/use-case/publication-list-by-process-id.service';
import { ApiResponse } from 'src/app/infra/http/type';
import { HttpService } from 'src/app/services/http.service';
import { environment } from 'src/environments/environment';
import { HttpService as HttpServiceInfra } from 'src/app/infra/http/http.service';
@Injectable({
providedIn: 'root'
})
export class PublicationFileRemoteRepositoryService {
private baseUrl = `${environment.apiURLStage.slice(0, -1)}/PresidentialActions`; // Your base URL
constructor(
private http: HttpService,
private httpServiceInfra: HttpServiceInfra,
) { }
async listByProcessId(processId: string) {
console.log(this.baseUrl);
return await this.http.get<ApiResponse<PublicationListByProcessIdOutPut>>(`${this.baseUrl}/${processId}/Posts`);
}
async FileListByDocumentId(input: PublicationFileGetByDocumentIdInput) {
return await this.httpServiceInfra.get<ApiResponse<PublicationFileGetByDocumentIdOutPut>>(`${this.baseUrl}/Posts/${input.documentId}/file`);
}
}
@@ -0,0 +1,23 @@
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({
providedIn: 'root'
})
export class PublicationFileRepositoryService {
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 }
);
}
}
@@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { PublicationTable, PublicationTableSchema } from 'src/app/infra/database/dexie/instance/action/schema/publication';
import { PublicationFileTable } from 'src/app/infra/database/dexie/instance/action/schema/publicationFile';
import { actionDatabase } from 'src/app/infra/database/dexie/instance/action/service';
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
@Injectable({
providedIn: 'root'
})
export class PublicationLocalRepositoryService extends DexieRepository<PublicationTable, PublicationTable> {
constructor() {
super(actionDatabase.publication, PublicationTableSchema, actionDatabase)
}
}
@@ -0,0 +1,47 @@
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 { PublicationUpdateInputDto } from 'src/app/core/actions/use-case/publication-update-use-case.service';
import { HttpService } from 'src/app/infra/http/http.service';
import { ApiResponse } from 'src/app/infra/http/type';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class PublicationRemoteRepositoryService {
private baseUrl = `${environment.apiURLStage.slice(0, -1)}/PresidentialActions`; // Your base URL
constructor(
private http: HttpService
) { }
async listByProcessId(processId: number) {
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);
}
isUpdatingPublication(publicationId: string, processId: string): boolean {
return this.http.hasPendingRequest(
'PUT',
`/PresidentialActions/${processId}/Posts/${publicationId}`
);
}
isFechingFilesPublication(publicationId: string): boolean {
//console.log('isFechingFilesPublication', `/PresidentialActions/Posts/${publicationId}/Posts`);
return this.http.hasPendingRequest(
'GET',
`/PresidentialActions/Posts/${publicationId}/file`
);
}
}
@@ -1,4 +1,3 @@
import { z } from "zod";
import { SharedCalendarListItemOutputDTO } from "../../dto/sharedCalendarOutputDTO";
type Changes = {
@@ -11,13 +11,14 @@ import { HttpService } from 'src/app/services/http.service';
import { TracingType } from 'src/app/services/monitoring/opentelemetry/tracer';
import { IGetDraftListByProcessIdOutput, IGetDraftListByProcessIdSchema } from '../../domain/usecase/getDraft-list-by-process-id.service';
import { IDraftSaveByIdInput } from '../../domain/usecase/draft-save-by-id-use-case.service';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class AgendaDataService {
private baseUrl = 'https://gdapi-dev.dyndns.info/stage/api/v2'; // Your base URL
private baseUrl = `${environment.apiURLStage.slice(0, -1)}`; // Your base URL
constructor(
private http: HttpClient,
@@ -28,14 +28,14 @@ const OwnerSchema = z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string(),
userPhoto: z.string().nullable().optional(),
});
const OrganizerSchema = z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string(),
userPhoto: z.string().nullable().optional(),
});
const EventRecurrenceSchema = z.object({
@@ -5,7 +5,7 @@ const OwnerSchema = z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string(),
userPhoto: z.string().nullable().optional(),
});
@@ -5,7 +5,7 @@ const OwnerSchema = z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string(),
userPhoto: z.string().nullable().optional(),
});

Some files were not shown because too many files have changed in this diff Show More