mirror of
https://code.equilibrium.co.ao/ITO/doneit-web.git
synced 2026-04-18 20:47:54 +00:00
Compare commits
40 Commits
api-doneit
...
fe2f74e2fa
| Author | SHA1 | Date | |
|---|---|---|---|
| fe2f74e2fa | |||
| b9c3164127 | |||
| 0598a11d2e | |||
| 1429cc89c4 | |||
| 5fb8b67599 | |||
| e147164f07 | |||
| 3c326eeb2a | |||
| 2ee634b354 | |||
| ec57a046ee | |||
| 54411bc20c | |||
| f6ddea8586 | |||
| 154de1f991 | |||
| d9984981d1 | |||
| 4a7a0934de | |||
| c00e1b888c | |||
| cb9fb7aab5 | |||
| 7ca666518d | |||
| e006d7ed96 | |||
| c4689bb103 | |||
| 671e2e9e38 | |||
| 796212cba0 | |||
| 52095e37ec | |||
| 7911a73066 | |||
| e622f305e5 | |||
| 3ae36a33cd | |||
| ee61f066e4 | |||
| a47e7f0c5c | |||
| 2f054bc781 | |||
| 2673f8a17a | |||
| c14431034c | |||
| a9cd4843d5 | |||
| 9e3c6db93a | |||
| f79df96d48 | |||
| 4870a0cd69 | |||
| 3eecedb23f | |||
| 5f7295d41c | |||
| 69e4334ebf | |||
| 0d10ee98fa | |||
| d1e5387d11 | |||
| 430adf394d |
+25
@@ -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\"}"
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
+25
-14
@@ -1,15 +1,26 @@
|
||||
FROM node:14 as build
|
||||
WORKDIR /app
|
||||
COPY ./package*.json /app/
|
||||
RUN npm config set unsafe-perm true
|
||||
RUN npm install -g ionic
|
||||
RUN npm config set legacy-peer-deps true
|
||||
RUN npm i -D typescript@4.3.5
|
||||
RUN npm install --save --legacy-peer-deps
|
||||
COPY ./ /app/
|
||||
RUN ionic build --prod
|
||||
# ---------- Build Stage ----------
|
||||
FROM node:16-alpine AS build
|
||||
|
||||
FROM nginx:latest
|
||||
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
COPY /www/ /usr/share/nginx/html/
|
||||
WORKDIR /app
|
||||
|
||||
# Install Ionic CLI (optional but safe)
|
||||
RUN npm install -g @ionic/cli@6 @angular/cli@12
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
# Build production app
|
||||
RUN ionic build --prod
|
||||
|
||||
# ---------- Run Stage ----------
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy build output to nginx
|
||||
COPY --from=build /app/www /usr/share/nginx/html
|
||||
|
||||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -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
|
||||
@@ -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 1–2)
|
||||
- Optimize profile (photo, banner, headline, about, featured projects).
|
||||
- Follow 100+ relevant devs, recruiters, and companies.
|
||||
|
||||
### Phase 2 – Consistency (Weeks 3–6)
|
||||
- Post 3–4 times per week.
|
||||
- Share lessons, demos, explainers, and personal wins.
|
||||
- Engage with other posts.
|
||||
|
||||
### Phase 3 – Authority Building (Months 2–4)
|
||||
- Write long-form articles (e.g., offline-first, clean architecture).
|
||||
- Share diagrams, benchmarks, and tutorials.
|
||||
- Start a content series.
|
||||
|
||||
### Phase 4 – Expansion (Months 4–6)
|
||||
- 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: “I’m 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, here’s 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 else’s 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 2–3 connection requests/day.
|
||||
- Reply to all comments within 24h.
|
||||
|
||||
---
|
||||
|
||||
✅ With consistency + authenticity, expect exponential growth by **Month 3–4**.
|
||||
Generated
+788
-352
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -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",
|
||||
@@ -181,7 +182,6 @@
|
||||
"hammerjs": "^2.0.8",
|
||||
"howler": "^2.2.3",
|
||||
"http-server": "^14.1.1",
|
||||
"husky": "^8.0.3",
|
||||
"ionic-angular": "^3.9.10",
|
||||
"ionic-image-loader": "^6.3.3",
|
||||
"ionic-image-loader-v5": "^1.0.2",
|
||||
@@ -225,6 +225,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 +260,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
@@ -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 don’t 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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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>>
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 +1,7 @@
|
||||
export interface Environment {
|
||||
id: string;
|
||||
apiURL: string;
|
||||
apiChatUrl: string;
|
||||
apiWsChatUrl: string;
|
||||
apiURLStage: String
|
||||
apiPCURL: string;
|
||||
logoLabel: string;
|
||||
production: boolean;
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user