mirror of
https://code.equilibrium.co.ao/ITO/doneit-web.git
synced 2026-04-19 04:57:52 +00:00
Compare commits
46 Commits
api-doneit
...
developer
| Author | SHA1 | Date | |
|---|---|---|---|
| 444e2e0cd3 | |||
| cd902a0daa | |||
| 504fda9c38 | |||
| 40d852c6c1 | |||
| fd7e4a52a8 | |||
| 9010f8789c | |||
| 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/gabinete-web
|
||||||
|
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\":\"gabinete_gabinete-web\",\"image\":\"registry.petermaquiran.xyz/gabinete-web:latest\"}"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
+42
-12
@@ -1,15 +1,45 @@
|
|||||||
FROM node:14 as build
|
# =========================
|
||||||
WORKDIR /app
|
# BUILD STAGE
|
||||||
COPY ./package*.json /app/
|
# =========================
|
||||||
RUN npm config set unsafe-perm true
|
FROM node:16.20.2 AS build
|
||||||
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
|
|
||||||
|
|
||||||
FROM nginx:latest
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Avoid npm randomness
|
||||||
|
ENV NPM_CONFIG_LEGACY_PEER_DEPS=true
|
||||||
|
ENV NPM_CONFIG_AUDIT=false
|
||||||
|
ENV NPM_CONFIG_FUND=false
|
||||||
|
|
||||||
|
# Copy dependency files first (better caching)
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Clean install (IMPORTANT: single install only)
|
||||||
|
RUN npm cache clean --force
|
||||||
|
RUN npm install --legacy-peer-deps --no-audit --no-fund
|
||||||
|
|
||||||
|
# Copy full project
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build Ionic Angular app
|
||||||
|
RUN npx ionic build --configuration production
|
||||||
|
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# PRODUCTION STAGE (NGINX)
|
||||||
|
# =========================
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Nginx config (optional but recommended for SPA routing)
|
||||||
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
|
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Clean default nginx files
|
||||||
RUN rm -rf /usr/share/nginx/html/*
|
RUN rm -rf /usr/share/nginx/html/*
|
||||||
COPY /www/ /usr/share/nginx/html/
|
|
||||||
|
# Copy build output from previous stage
|
||||||
|
COPY --from=build /app/www /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Start nginx
|
||||||
|
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",
|
"@ngrx/store": "^10.1.2",
|
||||||
"@ngx-translate/core": "^13.0.0",
|
"@ngx-translate/core": "^13.0.0",
|
||||||
"@ngxs/store": "^3.8.2",
|
"@ngxs/store": "^3.8.2",
|
||||||
|
"@openreplay/tracker": "^16.4.8",
|
||||||
"@opentelemetry/exporter-metrics-otlp-http": "^0.52.1",
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.52.1",
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
|
||||||
"@opentelemetry/exporter-zipkin": "^1.25.1",
|
"@opentelemetry/exporter-zipkin": "^1.25.1",
|
||||||
@@ -181,7 +182,6 @@
|
|||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"howler": "^2.2.3",
|
"howler": "^2.2.3",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"husky": "^8.0.3",
|
|
||||||
"ionic-angular": "^3.9.10",
|
"ionic-angular": "^3.9.10",
|
||||||
"ionic-image-loader": "^6.3.3",
|
"ionic-image-loader": "^6.3.3",
|
||||||
"ionic-image-loader-v5": "^1.0.2",
|
"ionic-image-loader-v5": "^1.0.2",
|
||||||
@@ -225,6 +225,7 @@
|
|||||||
"uuidv4": "^6.2.11",
|
"uuidv4": "^6.2.11",
|
||||||
"videogular2": "^7.0.2",
|
"videogular2": "^7.0.2",
|
||||||
"webpack": "^5.88.2",
|
"webpack": "^5.88.2",
|
||||||
|
"winston": "^3.17.0",
|
||||||
"wordcloud": "^1.1.2",
|
"wordcloud": "^1.1.2",
|
||||||
"ws": "^7.5.9",
|
"ws": "^7.5.9",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.23.8",
|
||||||
@@ -259,7 +260,7 @@
|
|||||||
"cordova-plugin-media-capture": "^3.0.3",
|
"cordova-plugin-media-capture": "^3.0.3",
|
||||||
"cordova-plugin-network-information": "^3.0.0",
|
"cordova-plugin-network-information": "^3.0.0",
|
||||||
"cordova-plugin-statusbar": "^2.4.2",
|
"cordova-plugin-statusbar": "^2.4.2",
|
||||||
"cypress": "^12.13.0",
|
"cypress": "^15.1.0",
|
||||||
"es6-promise-plugin": "^4.2.2",
|
"es6-promise-plugin": "^4.2.2",
|
||||||
"jasmine-core": "~3.6.0",
|
"jasmine-core": "~3.6.0",
|
||||||
"jasmine-spec-reporter": "~5.0.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 { IonicImageLoaderModule } from 'ionic-image-loader-v5';
|
||||||
import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer';
|
import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer';
|
||||||
import { FileOpener } from '@awesome-cordova-plugins/file-opener/ngx';
|
import { FileOpener } from '@awesome-cordova-plugins/file-opener/ngx';
|
||||||
|
|
||||||
|
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatNativeDateModule } from '@angular/material/core';
|
import { MatNativeDateModule } from '@angular/material/core';
|
||||||
|
import * as SentrySibling from '@sentry/angular';
|
||||||
|
|
||||||
// The example is using Angular, Import '@sentry/vue' or '@sentry/react' when using a Sibling different than Angular.
|
|
||||||
import * as SentrySibling from '@sentry/angular';
|
|
||||||
import * as Sentry from '@sentry/capacitor';
|
|
||||||
// The e xample is using Angular, Import '@sentry/vue' or '@sentry/react' when using a Sibling different than Angular.
|
|
||||||
// For automatic instrumentation (highly recommended)
|
|
||||||
import { Integration } from '@sentry/types';
|
|
||||||
import { BrowserTracing } from '@sentry/tracing';
|
|
||||||
import { AngularFireModule } from '@angular/fire';
|
import { AngularFireModule } from '@angular/fire';
|
||||||
import { AngularFireMessagingModule } from '@angular/fire/messaging';
|
import { AngularFireMessagingModule } from '@angular/fire/messaging';
|
||||||
import { firebaseConfig } from '../firebase-config';
|
import { firebaseConfig } from '../firebase-config';
|
||||||
@@ -87,67 +77,237 @@ import { LoggingInterceptorService } from './services/logging-interceptor.servic
|
|||||||
import { PopupQuestionPipe } from './modals/popup-question.pipe';
|
import { PopupQuestionPipe } from './modals/popup-question.pipe';
|
||||||
import '@teamhive/capacitor-video-recorder';
|
import '@teamhive/capacitor-video-recorder';
|
||||||
import { tokenInterceptor } from './infra/monitoring/interceptors/token.interceptors';
|
import { tokenInterceptor } from './infra/monitoring/interceptors/token.interceptors';
|
||||||
|
|
||||||
import { InputFilterDirective } from './services/directives/input-filter.directive';
|
import { InputFilterDirective } from './services/directives/input-filter.directive';
|
||||||
import { VisibilityDirective } from './services/directives/visibility.directive';
|
|
||||||
import { DeplomaOptionsPageModule } from './shared/popover/deploma-options/deploma-options.module';
|
import { DeplomaOptionsPageModule } from './shared/popover/deploma-options/deploma-options.module';
|
||||||
import { DiplomaOptionsPage } from './shared/popover/deploma-options/deploma-options.page';
|
import { DiplomaOptionsPage } from './shared/popover/deploma-options/deploma-options.page';
|
||||||
import { ImageCropperModule } from 'ngx-image-cropper';
|
import { ImageCropperModule } from 'ngx-image-cropper';
|
||||||
import { metricsInterceptor } from './infra/monitoring/interceptors/metter.interceptor';
|
import { metricsInterceptor } from './infra/monitoring/interceptors/metter.interceptor';
|
||||||
|
|
||||||
import {MatMenuModule} from '@angular/material/menu';
|
import {MatMenuModule} from '@angular/material/menu';
|
||||||
import {MatIconModule} from '@angular/material/icon';
|
import {MatIconModule} from '@angular/material/icon';
|
||||||
import { ChatModule } from './module/chat/chat.module';
|
import { ChatModule } from './module/chat/chat.module';
|
||||||
import { openTelemetryLogging } from './services/monitoring/opentelemetry/logging';
|
|
||||||
|
|
||||||
import { registerLocaleData } from '@angular/common';
|
import { registerLocaleData } from '@angular/common';
|
||||||
import localePt from '@angular/common/locales/pt';
|
import localePt from '@angular/common/locales/pt';
|
||||||
import { LogsDatabase } from './infra/database/dexie/instance/logs/service';
|
|
||||||
import { UserModule } from './module/user/user.module';
|
import { UserModule } from './module/user/user.module';
|
||||||
|
import { SessionExpiredModalPageModule } from './modals/session-expired-modal/session-expired-modal.module';
|
||||||
|
import { Logger } from './services/logger/main/service';
|
||||||
|
|
||||||
// Register the locale data
|
|
||||||
registerLocaleData(localePt, 'pt');
|
registerLocaleData(localePt, 'pt');
|
||||||
|
// import * as Sentry from '@sentry/capacitor';
|
||||||
|
// import { Integration } from '@sentry/types';
|
||||||
|
// import { BrowserTracing } from '@sentry/tracing';
|
||||||
|
// import { LogsDatabase } from './infra/database/dexie/instance/logs/service';
|
||||||
|
// import { AppErrorHandler } from './infra/crash-analytics/app-error-handler';
|
||||||
|
|
||||||
Sentry.init(
|
|
||||||
{
|
|
||||||
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(() => {
|
// Sentry.init(
|
||||||
console.log('event', event)
|
// {
|
||||||
})
|
// 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({
|
// if (event.level === 'error') {
|
||||||
type: 'graylog',
|
|
||||||
payload: {
|
// LogsDatabase.sentryError.add(event as any).then(() => {
|
||||||
message: event.exception.values[0].value,
|
// console.log('event', event)
|
||||||
object: {
|
// })
|
||||||
sentry: true,
|
|
||||||
error: event
|
// // openTelemetryLogging.send({
|
||||||
}
|
// // level: 'info',
|
||||||
},
|
// // message: event.exception.values[0].value,
|
||||||
spanContext: null
|
// // 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({
|
@NgModule({
|
||||||
declarations: [AppComponent, PopupQuestionPipe, InputFilterDirective],
|
declarations: [AppComponent, PopupQuestionPipe, InputFilterDirective],
|
||||||
@@ -210,7 +370,8 @@ registerLocaleData(localePt, 'pt');
|
|||||||
MatIconModule,
|
MatIconModule,
|
||||||
// module
|
// module
|
||||||
ChatModule,
|
ChatModule,
|
||||||
UserModule
|
UserModule,
|
||||||
|
SessionExpiredModalPageModule
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
DiplomaOptionsPage,
|
DiplomaOptionsPage,
|
||||||
@@ -218,11 +379,12 @@ registerLocaleData(localePt, 'pt');
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: MAT_DATE_LOCALE, useValue: 'pt' },
|
{ provide: MAT_DATE_LOCALE, useValue: 'pt' },
|
||||||
{
|
//{ provide: ErrorHandler, useClass: AppErrorHandler },
|
||||||
provide: ErrorHandler,
|
// {
|
||||||
// Attach the Sentry ErrorHandler
|
// provide: ErrorHandler,
|
||||||
useValue: SentrySibling.createErrorHandler(),
|
// // Attach the Sentry ErrorHandler
|
||||||
},
|
// useValue: SentrySibling.createErrorHandler(),
|
||||||
|
// },
|
||||||
StatusBar,
|
StatusBar,
|
||||||
//SplashScreen,
|
//SplashScreen,
|
||||||
HttpClient,
|
HttpClient,
|
||||||
@@ -253,7 +415,7 @@ registerLocaleData(localePt, 'pt');
|
|||||||
FFmpeg,
|
FFmpeg,
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorService, multi: true },
|
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorService, multi: true },
|
||||||
tokenInterceptor,
|
tokenInterceptor,
|
||||||
metricsInterceptor
|
metricsInterceptor,
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
|||||||
@@ -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(),
|
wxUserId: z.number(),
|
||||||
wxFullName: z.string(),
|
wxFullName: z.string(),
|
||||||
wxeMail: z.string(),
|
wxeMail: z.string(),
|
||||||
userPhoto: z.string().nullable(),
|
userPhoto: z.string().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const MemberSchema = z.object({
|
const MemberSchema = z.object({
|
||||||
@@ -27,7 +27,7 @@ const MemberSchema = z.object({
|
|||||||
export const RoomEntitySchema = z.object({
|
export const RoomEntitySchema = z.object({
|
||||||
$id: z.string(),
|
$id: z.string(),
|
||||||
id: z.string().uuid().optional(),
|
id: z.string().uuid().optional(),
|
||||||
roomName: z.string(),
|
roomName: z.string().nullable(),
|
||||||
createdBy: z.object({
|
createdBy: z.object({
|
||||||
wxUserId: z.number(),
|
wxUserId: z.number(),
|
||||||
wxFullName: z.string(),
|
wxFullName: z.string(),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const MessageEntityAttachmentSchema = z.object({
|
|||||||
mimeType: z.string().nullable().optional(),
|
mimeType: z.string().nullable().optional(),
|
||||||
safeFile: z.any().optional(),
|
safeFile: z.any().optional(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
|
filePath: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
|
|
||||||
export const MessageEntitySchema = z.object({
|
export const MessageEntitySchema = z.object({
|
||||||
@@ -49,7 +50,7 @@ export const MessageEntitySchema = z.object({
|
|||||||
wxUserId: z.number(),
|
wxUserId: z.number(),
|
||||||
wxFullName: z.string(),
|
wxFullName: z.string(),
|
||||||
wxeMail: z.string(),
|
wxeMail: z.string(),
|
||||||
userPhoto: z.string().nullable(),
|
userPhoto: z.string().nullable().optional(),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
reactions: z.object({
|
reactions: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
@@ -60,14 +61,16 @@ export const MessageEntitySchema = z.object({
|
|||||||
info: z.array(z.object({
|
info: z.array(z.object({
|
||||||
memberId: z.number(),
|
memberId: z.number(),
|
||||||
readAt: z.string().nullable(),
|
readAt: z.string().nullable(),
|
||||||
deliverAt: z.string().nullable()
|
deliverAt: z.string().nullable(),
|
||||||
|
isDeleted: z.boolean().optional(),
|
||||||
})).optional(),
|
})).optional(),
|
||||||
sending: z.boolean().optional(),
|
sending: z.boolean().optional(),
|
||||||
attachments: z.array(MessageEntityAttachmentSchema).optional(),
|
attachments: z.array(MessageEntityAttachmentSchema).optional(),
|
||||||
origin: z.enum(['history', 'local', 'incoming']).optional(),
|
origin: z.enum(['history', 'local', 'incoming']).optional(),
|
||||||
requestId: z.string().nullable().optional(),
|
requestId: z.string().nullable().optional(),
|
||||||
sendAttemp: z.number().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>;
|
export type IMessage = z.infer<typeof MessageEntitySchema>;
|
||||||
@@ -120,4 +123,8 @@ export class MessageEntity {
|
|||||||
return this.sender?.wxUserId == SessionStore.user.UserId
|
return this.sender?.wxUserId == SessionStore.user.UserId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _isDeleted() {
|
||||||
|
return this.isDeleted || this.info.filter(e =>e.memberId == SessionStore.user.UserId && e.isDeleted).length == 1
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { MessageEntity, IMessage } from "../entity/message";
|
import { MessageEntity, IMessage } from "../entity/message";
|
||||||
import { MessageOutPutDataDTO } from "src/app/core/chat/repository/dto/messageOutputDTO";
|
import { MessageOutPutDataDTO } from "src/app/core/chat/repository/dto/messageOutputDTO";
|
||||||
import { MessageInputDTO } from "../usecase/message/message-create-use-case.service";
|
import { MessageInputDTO } from "../usecase/message/message-create-use-case.service";
|
||||||
|
import { getInstanceId } from "src/app/module/chat/domain/chat-service.service";
|
||||||
|
|
||||||
export class MessageMapper {
|
export class MessageMapper {
|
||||||
static toDomain(DTO: MessageOutPutDataDTO) : MessageEntity {
|
static toDomain(DTO: MessageOutPutDataDTO) : MessageEntity {
|
||||||
@@ -18,17 +19,8 @@ export class MessageMapper {
|
|||||||
roomId: entity.roomId,
|
roomId: entity.roomId,
|
||||||
senderId: entity.sender.wxUserId,
|
senderId: entity.sender.wxUserId,
|
||||||
requestId: entity.requestId || requestId,
|
requestId: entity.requestId || requestId,
|
||||||
attachment: entity.attachments.map((e)=>({
|
attachment: entity.attachments.map((e)=>(e.id)) || [],
|
||||||
fileType:e.fileType,
|
deviceId: getInstanceId()
|
||||||
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] || {}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
applicationId: z.number().optional(),
|
||||||
docId: z.number().optional(),
|
docId: z.number().optional(),
|
||||||
id: z.string().optional()
|
id: z.string().optional()
|
||||||
}))
|
})),
|
||||||
|
deviceId: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type MessageOutPutDataDTO = z.infer<typeof MessageOutPutDataDTOSchema>
|
export type MessageOutPutDataDTO = z.infer<typeof MessageOutPutDataDTOSchema>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { DataSourceReturn } from "src/app/services/Repositorys/type";
|
import { DataSourceReturn } from "src/app/services/Repositorys/type";
|
||||||
import { IMessageGetAllByRoomIdOutPut } from "../../usecase/message/message-get-all-by-room-Id";
|
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 {
|
export interface IGetMessagesFromRoomParams {
|
||||||
roomId: string
|
roomId: string
|
||||||
@@ -8,4 +11,5 @@ export interface IGetMessagesFromRoomParams {
|
|||||||
|
|
||||||
export abstract class IMessageRemoteRepository {
|
export abstract class IMessageRemoteRepository {
|
||||||
abstract getMessagesFromRoom(input: IGetMessagesFromRoomParams): DataSourceReturn<IMessageGetAllByRoomIdOutPut>
|
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 {
|
export abstract class IMessageSocketRepository {
|
||||||
|
|
||||||
abstract connect(): Promise<Result<HubConnection, false>>
|
abstract connect(): Promise<Result<HubConnection, false>>
|
||||||
abstract sendGroupMessage(data: MessageInputDTO): Promise<Result<MessageCreateOutPutDataDTO, any>>
|
abstract sendGroupMessage(data: MessageInputDTO): Promise<Result<MessageOutPutDataDTO, any>>
|
||||||
abstract sendDirectMessage(data: MessageInputDTO): Promise<Result<MessageCreateOutPutDataDTO, any>>
|
abstract sendDirectMessage(data: MessageInputDTO): Promise<Result<MessageOutPutDataDTO, any>>
|
||||||
|
|
||||||
// abstract sendDeliverAt(): Promise<Result<any, any>>
|
// abstract sendDeliverAt(): Promise<Result<any, any>>
|
||||||
abstract sendReadAt(data: MessageMarkAsReadInput): Promise<Result<any, any>>
|
abstract sendReadAt(data: MessageMarkAsReadInput): Promise<Result<any, any>>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const SocketRoomUpdateOutPutSchema = z.object({
|
const SocketRoomUpdateOutPutSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
roomName: z.string().min(1),
|
roomName: z.string().nullable(),
|
||||||
createdBy: z.string().nullable(), // Allowing null for createdBy
|
createdBy: z.string().nullable(), // Allowing null for createdBy
|
||||||
createdAt: z.string().datetime(),
|
createdAt: z.string().datetime(),
|
||||||
expirationDate: z.string().nullable().optional(), // Allowing null and making it optional
|
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({
|
export const AddMemberToRoomInputDTOSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
members: z.array(z.number()),
|
members: z.array(z.number()),
|
||||||
|
userId: z.number()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AddMemberToRoomInputDTO = z.infer<typeof AddMemberToRoomInputDTOSchema>
|
export type AddMemberToRoomInputDTO = z.infer<typeof AddMemberToRoomInputDTOSchema>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { filter, map } from 'rxjs/operators';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository';
|
import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository';
|
||||||
import { MessageEntity } from '../../entity/message';
|
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({
|
export const ListenMessageByRoomIdNewInputDTOSchema = z.object({
|
||||||
@@ -25,8 +25,10 @@ export class ListenMessageByRoomIdNewUseCase {
|
|||||||
|
|
||||||
return this.MessageSocketRepositoryService.listenToMessages().pipe(
|
return this.MessageSocketRepositoryService.listenToMessages().pipe(
|
||||||
map(message => message.data),
|
map(message => message.data),
|
||||||
filter((message) => !message?.requestId?.startsWith(InstanceId) && message?.roomId == data.roomId),
|
filter((message) => message?.deviceId != getInstanceId() && data.roomId == message?.roomId),
|
||||||
map(message => Object.assign(new MessageEntity(), message))
|
map(message => {
|
||||||
|
return Object.assign(new MessageEntity(), message)
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { MessageSocketRepositoryService } from 'src/app/module/chat/data/repository/message/message-live-signalr-data-source.service'
|
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 { filter, map } from 'rxjs/operators';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { IMessageSocketRepository } from '../../repository/message/message-socket-repository';
|
import { IMessageSocketRepository } from '../../repository/message/message-socket-repository';
|
||||||
|
import { getInstanceId } from 'src/app/module/chat/domain/chat-service.service';
|
||||||
|
|
||||||
export const ListenSendMessageInputDTOSchema = z.object({
|
export const ListenSendMessageInputDTOSchema = z.object({
|
||||||
roomId: z.string(),
|
roomId: z.string(),
|
||||||
@@ -25,10 +25,7 @@ export class ListenSendMessageUseCase {
|
|||||||
|
|
||||||
return this.MessageSocketRepositoryService.listenToMessages().pipe(
|
return this.MessageSocketRepositoryService.listenToMessages().pipe(
|
||||||
map(message => message.data),
|
map(message => message.data),
|
||||||
filter((message) => {
|
filter((message) => message?.deviceId != getInstanceId()),
|
||||||
|
|
||||||
return message?.requestId?.startsWith(InstanceId) && message?.roomId == roomId
|
|
||||||
}),
|
|
||||||
map(message => message)
|
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 { IMessage, MessageAttachmentSource, MessageEntity, MessageEntitySchema, } from '../../entity/message';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { InstanceId } from '../../../../module/chat/domain/chat-service.service';
|
|
||||||
import { createBlobFromBase64, createDataURL } from 'src/app/utils/ToBase64';
|
import { createBlobFromBase64, createDataURL } from 'src/app/utils/ToBase64';
|
||||||
import { zodSafeValidation } from 'src/app/utils/zodValidation';
|
import { zodSafeValidation } from 'src/app/utils/zodValidation';
|
||||||
import { Logger } from 'src/app/services/logger/main/service';
|
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 { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository';
|
||||||
import { IMemberLocalRepository } from 'src/app/core/chat/repository/member/member-local-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 { 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({
|
export const MessageInputDTOSchema = z.object({
|
||||||
@@ -29,16 +29,8 @@ export const MessageInputDTOSchema = z.object({
|
|||||||
oneShot: z.boolean(),
|
oneShot: z.boolean(),
|
||||||
requireUnlock: z.boolean(),
|
requireUnlock: z.boolean(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
attachment: z.object({
|
attachment: z.string().array(),
|
||||||
fileType: z.nativeEnum(MessageAttachmentFileType),
|
deviceId: z.string().optional()
|
||||||
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 type MessageInputDTO = z.infer<typeof MessageInputDTOSchema>
|
||||||
|
|
||||||
@@ -71,15 +63,7 @@ export const MessageCreatePutDataDTOSchema = z.object({
|
|||||||
readAt: z.string().nullable(),
|
readAt: z.string().nullable(),
|
||||||
deliverAt: z.string().nullable()
|
deliverAt: z.string().nullable()
|
||||||
})),
|
})),
|
||||||
attachments: z.array(z.object({
|
attachments: z.array(z.string())
|
||||||
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>
|
export type MessageCreateOutPutDataDTO = z.infer<typeof MessageCreatePutDataDTOSchema>
|
||||||
@@ -93,7 +77,8 @@ export class MessageCreateUseCaseService {
|
|||||||
private AttachmentLocalRepositoryService: IAttachmentLocalRepository,
|
private AttachmentLocalRepositoryService: IAttachmentLocalRepository,
|
||||||
private messageLocalDataSourceService: IMessageLocalRepository,
|
private messageLocalDataSourceService: IMessageLocalRepository,
|
||||||
private messageSocketRepositoryService: IMessageSocketRepository,
|
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)
|
const validation = zodSafeValidation<IMessage>(MessageEntitySchema, message)
|
||||||
|
|
||||||
if(validation.isOk()) {
|
if(validation.isOk()) {
|
||||||
|
console.log("send data", message);
|
||||||
message.sendAttemp++;
|
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) => {
|
message.$id = createMessageLocally.value
|
||||||
if(value.isOk()) {
|
|
||||||
|
|
||||||
console.log("set image")
|
if(message.hasAttachment) {
|
||||||
message.$id = value.value
|
|
||||||
|
|
||||||
if(message.hasAttachment) {
|
for (const attachment of message.attachments) {
|
||||||
|
const isWebtrix = attachment.source === MessageAttachmentSource.Webtrix;
|
||||||
|
|
||||||
for (const attachment of message.attachments) {
|
await this.AttachmentLocalRepositoryService.insert({
|
||||||
|
$messageId: createMessageLocally.value,
|
||||||
if(attachment.source != MessageAttachmentSource.Webtrix) {
|
file: isWebtrix ? null : createBlobFromBase64(attachment.file || '', attachment.mimeType),
|
||||||
|
fileType: attachment.fileType,
|
||||||
this.AttachmentLocalRepositoryService.insert({
|
source: attachment.source,
|
||||||
$messageId: value.value,
|
fileName: attachment.fileName,
|
||||||
file: createBlobFromBase64(attachment.file, attachment.mimeType),
|
applicationId: attachment.applicationId,
|
||||||
fileType: attachment.fileType,
|
docId: attachment.docId,
|
||||||
source: attachment.source,
|
mimeType: attachment.mimeType,
|
||||||
fileName: attachment.fileName,
|
base64: isWebtrix ? null : createDataURL(attachment.file || '', attachment.mimeType),
|
||||||
applicationId: attachment.applicationId,
|
id: null,
|
||||||
docId: attachment.docId,
|
}).then((e) => {
|
||||||
mimeType: attachment.mimeType,
|
if(e.isErr()) {
|
||||||
base64: createDataURL(attachment.file, attachment.mimeType)
|
Logger.error('failed to create attachment locally on send message', {
|
||||||
}).then((e) => {
|
error: e.error,
|
||||||
if(e.isErr()) {
|
data: !isWebtrix ? createDataURL(attachment.file, attachment.mimeType).slice(0, 100) +'...' : undefined
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
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
|
message.sending = true
|
||||||
@@ -175,7 +180,6 @@ export class MessageCreateUseCaseService {
|
|||||||
|
|
||||||
tracing.setAttribute("duration", `Execution time: ${duration}ms`);
|
tracing.setAttribute("duration", `Execution time: ${duration}ms`);
|
||||||
|
|
||||||
// return this sendMessageResult
|
|
||||||
|
|
||||||
if(sendMessageResult.isOk()) {
|
if(sendMessageResult.isOk()) {
|
||||||
|
|
||||||
@@ -188,24 +192,23 @@ export class MessageCreateUseCaseService {
|
|||||||
delete sendMessageResult.value.sender
|
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 {
|
console.log('sendMessageResult', (sendMessageResult as any).value)
|
||||||
tracing.hasError('failed to update send message')
|
let clone: MessageTable = {
|
||||||
console.log(sendMessageResult)
|
...(sendMessageResult as any).value,
|
||||||
console.log(data.error)
|
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
|
return sendMessageResult
|
||||||
@@ -216,21 +219,10 @@ export class MessageCreateUseCaseService {
|
|||||||
await this.messageLocalDataSourceService.update(message.$id, {sending: false, $id: message.$id})
|
await this.messageLocalDataSourceService.update(message.$id, {sending: false, $id: message.$id})
|
||||||
return err('no connection')
|
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 { SessionStore } from 'src/app/store/session.service';
|
||||||
import { IMessageLocalRepository } from '../../repository/message/message-local-repository';
|
import { IMessageLocalRepository } from '../../repository/message/message-local-repository';
|
||||||
import { IMessageSocketRepository } from '../../repository/message/message-socket-repository';
|
import { IMessageSocketRepository } from '../../repository/message/message-socket-repository';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
const MessageMarkAllMessageAsReadByRoomIdInputSchema = z.object({
|
const MessageMarkAllMessageAsReadByRoomIdInputSchema = z.object({
|
||||||
roomId: z.string(),
|
roomId: z.string(),
|
||||||
@@ -36,7 +37,7 @@ export class MessageMarkAllMessageAsReadByRoomIdService {
|
|||||||
memberId: SessionStore.user.UserId,
|
memberId: SessionStore.user.UserId,
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
roomId: input.roomId,
|
roomId: input.roomId,
|
||||||
requestId: 'uuid'
|
requestId: uuidv4()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { MessageLocalDataSourceService } from '../../../../module/chat/data/repository/message/message-local-data-source.service';
|
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 { 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 { MessageMapper } from '../../mapper/messageMapper';
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { AttachmentLocalDataSource } from '../../../../module/chat/data/repository/attachment/attachment-local-repository.service';
|
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 { 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 { 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 { RoomType } from 'src/app/core/chat/entity/group';
|
||||||
import { MessageTable } from 'src/app/infra/database/dexie/instance/chat/schema/message';
|
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 { IDBoolean } from 'src/app/infra/database/dexie/type';
|
||||||
import { IMemberLocalRepository } from '../../repository/member/member-local-repository';
|
import { IMemberLocalRepository } from '../../repository/member/member-local-repository';
|
||||||
import { IMessageLocalRepository } from '../../repository/message/message-local-repository';
|
import { IMessageLocalRepository } from '../../repository/message/message-local-repository';
|
||||||
import { IMessageSocketRepository } from '../../repository/message/message-socket-repository';
|
import { IMessageSocketRepository } from '../../repository/message/message-socket-repository';
|
||||||
import { IRoomLocalRepository } from '../../repository/room/room-local-repository';
|
import { IRoomLocalRepository } from '../../repository/room/room-local-repository';
|
||||||
import { IAttachmentLocalRepository } from '../../repository/typing/typing-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({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -30,6 +31,8 @@ export class SendLocalMessagesUseCaseService {
|
|||||||
private roomLocalDataSourceService: IRoomLocalRepository,
|
private roomLocalDataSourceService: IRoomLocalRepository,
|
||||||
private MemberListLocalRepository: IMemberLocalRepository,
|
private MemberListLocalRepository: IMemberLocalRepository,
|
||||||
private messageSocketRepositoryService: IMessageSocketRepository,
|
private messageSocketRepositoryService: IMessageSocketRepository,
|
||||||
|
private MessageRemoteRepository: IMessageRemoteRepository,
|
||||||
|
private AttachmentLocalRepositoryService: IAttachmentLocalRepository,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async execute() {
|
async execute() {
|
||||||
@@ -54,19 +57,51 @@ export class SendLocalMessagesUseCaseService {
|
|||||||
|
|
||||||
if(attachments.isOk()) {
|
if(attachments.isOk()) {
|
||||||
|
|
||||||
message.attachments = attachments.value.map(e => ({
|
message.attachments = attachments.value.map(e => {
|
||||||
fileType: e.fileType,
|
const base64 = typeof e.base64 === "string"
|
||||||
source: e.source,
|
? e.base64.replace(/^data:[a-zA-Z]+\/[a-zA-Z]+;base64,/, "")
|
||||||
fileName: e.fileName,
|
: "";
|
||||||
applicationId: e.applicationId,
|
|
||||||
docId: e.docId,
|
return {
|
||||||
id: e.id,
|
fileType: e.fileType,
|
||||||
mimeType: e.mimeType,
|
source: e.source,
|
||||||
description: e.description,
|
fileName: e.fileName,
|
||||||
file: e.base64.split(',')[1]
|
applicationId: e.applicationId,
|
||||||
}))
|
docId: e.docId,
|
||||||
console.log('to upload', messages)
|
id: e.id,
|
||||||
const requestId = InstanceId +'@'+ uuidv4();
|
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 })
|
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 { MessageEntity } from 'src/app/core/chat/entity/message';
|
||||||
import { IBoldLocalRepository } from 'src/app/core/chat/repository/bold/bold-local-repository';
|
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 { 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 { HttpAdapter } from 'src/app/infra/http/adapter';
|
||||||
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
|
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
|
||||||
import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository';
|
import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository';
|
||||||
import { IMessageGetAllByRoomIdOutPut } from 'src/app/core/chat/usecase/message/message-get-all-by-room-Id';
|
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({
|
@Injectable({
|
||||||
@@ -24,8 +24,6 @@ export class RoomBoldSyncUseCaseService {
|
|||||||
private roomLocalDataSourceService: IRoomLocalRepository,
|
private roomLocalDataSourceService: IRoomLocalRepository,
|
||||||
) {
|
) {
|
||||||
this.listenToIncomingMessage();
|
this.listenToIncomingMessage();
|
||||||
// this.loadHistory()
|
|
||||||
//this.onInsertToDB()
|
|
||||||
this.listenToUpdateMessages();
|
this.listenToUpdateMessages();
|
||||||
this.loadHistory()
|
this.loadHistory()
|
||||||
}
|
}
|
||||||
@@ -37,7 +35,7 @@ export class RoomBoldSyncUseCaseService {
|
|||||||
private listenToIncomingMessage() {
|
private listenToIncomingMessage() {
|
||||||
return this.MessageSocketRepositoryService.listenToMessages().pipe(
|
return this.MessageSocketRepositoryService.listenToMessages().pipe(
|
||||||
map(message => message.data),
|
map(message => message.data),
|
||||||
filter((message) => !message?.requestId?.startsWith(InstanceId)),
|
filter((message) => message?.deviceId != getInstanceId()),
|
||||||
map(message => Object.assign(new MessageEntity(), message)),
|
map(message => Object.assign(new MessageEntity(), message)),
|
||||||
filter((message) => !message.meSender())
|
filter((message) => !message.meSender())
|
||||||
).subscribe(async (message) => {
|
).subscribe(async (message) => {
|
||||||
@@ -59,7 +57,7 @@ export class RoomBoldSyncUseCaseService {
|
|||||||
*/
|
*/
|
||||||
private listenToUpdateMessages() {
|
private listenToUpdateMessages() {
|
||||||
return this.MessageSocketRepositoryService.listenToUpdateMessages().pipe(
|
return this.MessageSocketRepositoryService.listenToUpdateMessages().pipe(
|
||||||
filter((message) => !message?.requestId?.startsWith(InstanceId)),
|
filter((message) => message?.deviceId != getInstanceId()),
|
||||||
map(message => Object.assign(new MessageEntity(), message))
|
map(message => Object.assign(new MessageEntity(), message))
|
||||||
).subscribe(async (message) => {
|
).subscribe(async (message) => {
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const RoomOutPutDTOSchema = z.object({
|
|||||||
message: z.string(),
|
message: z.string(),
|
||||||
data: z.object({
|
data: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
roomName: z.string(),
|
roomName: z.string().nullable(),
|
||||||
createdBy: z.any().nullable(),
|
createdBy: z.any().nullable(),
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
expirationDate: z.string().nullable(),
|
expirationDate: z.string().nullable(),
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const RoomByIdOutputDTOSchema = z.object({
|
|||||||
message: z.string(),
|
message: z.string(),
|
||||||
data: z.object({
|
data: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
roomName: z.string(),
|
roomName: z.string().nullable(),
|
||||||
createdBy: UserSchema,
|
createdBy: UserSchema,
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
expirationDate: z.string().nullable(),
|
expirationDate: z.string().nullable(),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const CreatedBySchema = z.object({
|
|||||||
|
|
||||||
const roomListItemSchema = z.object({
|
const roomListItemSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
roomName: z.string(),
|
roomName: z.string().nullable(),
|
||||||
createdBy: CreatedBySchema,
|
createdBy: CreatedBySchema,
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
expirationDate: z.string().nullable(), // api check
|
expirationDate: z.string().nullable(), // api check
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class RoomSetLastMessageService {
|
|||||||
if(room.messages?.[0]?.id == message.id) {
|
if(room.messages?.[0]?.id == message.id) {
|
||||||
|
|
||||||
// incoming _message does not have sender
|
// incoming _message does not have sender
|
||||||
const messageToUpdate = ({...room.messages?.[0],isDeleted: true})
|
const messageToUpdate = ({...room.messages?.[0],isDeleted: true, info: message.info})
|
||||||
|
|
||||||
const result = await this.roomLocalRepository.update(room.$id, {
|
const result = await this.roomLocalRepository.update(room.$id, {
|
||||||
messages: [messageToUpdate]
|
messages: [messageToUpdate]
|
||||||
@@ -170,7 +170,7 @@ export class RoomSetLastMessageService {
|
|||||||
}),
|
}),
|
||||||
map((response: any) => response.value.data as IMessageGetAllByRoomIdOutPut)
|
map((response: any) => response.value.data as IMessageGetAllByRoomIdOutPut)
|
||||||
).subscribe(async (data)=> {
|
).subscribe(async (data)=> {
|
||||||
const loadHistoryFirstMessage = data.data[0]
|
const loadHistoryFirstMessage = Object.assign(new MessageEntity(), data.data[0]);
|
||||||
if(loadHistoryFirstMessage) {
|
if(loadHistoryFirstMessage) {
|
||||||
|
|
||||||
const roomId = loadHistoryFirstMessage.roomId
|
const roomId = loadHistoryFirstMessage.roomId
|
||||||
@@ -184,7 +184,9 @@ export class RoomSetLastMessageService {
|
|||||||
messages: [loadHistoryFirstMessage]
|
messages: [loadHistoryFirstMessage]
|
||||||
})
|
})
|
||||||
} else if (roomEntity.hasLastMessage()) {
|
} else if (roomEntity.hasLastMessage()) {
|
||||||
const localLastMessageDate = new Date(room.value.messages[0].sentAt).getTime()
|
var lastMessage = Object.assign(new MessageEntity(), room.value.messages[0]);
|
||||||
|
|
||||||
|
const localLastMessageDate = new Date(lastMessage.sentAt).getTime()
|
||||||
const loadHistoryLastMessageDate = new Date(loadHistoryFirstMessage.sentAt).getTime()
|
const loadHistoryLastMessageDate = new Date(loadHistoryFirstMessage.sentAt).getTime()
|
||||||
|
|
||||||
if(loadHistoryFirstMessage.id == room.value.messages?.[0]?.id) {
|
if(loadHistoryFirstMessage.id == room.value.messages?.[0]?.id) {
|
||||||
@@ -214,7 +216,7 @@ export class RoomSetLastMessageService {
|
|||||||
|
|
||||||
} else if(loadHistoryLastMessageDate == localLastMessageDate) {
|
} else if(loadHistoryLastMessageDate == localLastMessageDate) {
|
||||||
// do nothing
|
// do nothing
|
||||||
} else if(room.value.messages[0].isDeleted != loadHistoryFirstMessage.isDeleted) {
|
} else if(lastMessage._isDeleted != loadHistoryFirstMessage._isDeleted) {
|
||||||
// await this.roomLocalRepository.update(loadHistoryFirstMessage.roomId, {
|
// await this.roomLocalRepository.update(loadHistoryFirstMessage.roomId, {
|
||||||
// messages: [loadHistoryFirstMessage]
|
// messages: [loadHistoryFirstMessage]
|
||||||
// })
|
// })
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { z } from "zod";
|
|||||||
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
|
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
|
||||||
import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository';
|
import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository';
|
||||||
import { IRoomRemoteRepository } from 'src/app/core/chat/repository/room/room-remote-repository';
|
import { IRoomRemoteRepository } from 'src/app/core/chat/repository/room/room-remote-repository';
|
||||||
|
import { zodSafeValidation } from 'src/app/utils/zodValidation';
|
||||||
|
import { RoomByIdOutputDTO, RoomByIdOutputDTOSchema } from './room-get-by-id-use-case.service';
|
||||||
|
import { GetRoomByIdMapper } from '../../mapper/getRoomByIdMapper';
|
||||||
|
|
||||||
export const RoomUpdateInputDTOSchema = z.object({
|
export const RoomUpdateInputDTOSchema = z.object({
|
||||||
roomName: z.string(),
|
roomName: z.string(),
|
||||||
@@ -31,7 +34,7 @@ export const RoomUpdateOutputDTOSchema = z.object({
|
|||||||
message: z.string(),
|
message: z.string(),
|
||||||
data: z.object({
|
data: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
roomName: z.string(),
|
roomName: z.string().nullable(),
|
||||||
createdBy: UserSchema,
|
createdBy: UserSchema,
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
expirationDate: z.string().nullable(),
|
expirationDate: z.string().nullable(),
|
||||||
@@ -58,19 +61,21 @@ export class UpdateRoomByIdUseCaseService {
|
|||||||
|
|
||||||
const result = await this.roomRemoteDataSourceService.updateRoom(data)
|
const result = await this.roomRemoteDataSourceService.updateRoom(data)
|
||||||
|
|
||||||
if(result.isOk()) {
|
// if(result.isOk()) {
|
||||||
const localList = await this.roomLocalDataSourceService.findAll()
|
// const result = await this.roomRemoteDataSourceService.getRoom(data.roomId)
|
||||||
// const { roomsToDelete, roomsToInsert, roomsToUpdate } = roomListDetermineChanges([result.value.data], localList)
|
|
||||||
|
|
||||||
// for( const roomData of roomsToUpdate) {
|
// if(result.isOk()) {
|
||||||
// if(!roomData.chatRoom.createdBy?.wxUserId) {
|
// const localListRoom = await this.roomLocalDataSourceService.findAll()
|
||||||
// delete roomData.chatRoom.createdBy;
|
// if(localListRoom.isOk()) {
|
||||||
// }
|
|
||||||
|
|
||||||
// this.roomLocalDataSourceService.updateRoom(roomData.chatRoom)
|
// const getRoomById = await this.roomLocalDataSourceService.findOne({id:result.value.data.id})
|
||||||
// }
|
// if(getRoomById.isOk() && getRoomById.value) {
|
||||||
|
// const room = GetRoomByIdMapper.toDomain(result.value)
|
||||||
}
|
// this.roomLocalDataSourceService.update(room.id, room)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { IRoomSocketRepository } from 'src/app/core/chat/repository/room/room-socket-repository';
|
import { IRoomSocketRepository } from 'src/app/core/chat/repository/room/room-socket-repository';
|
||||||
import { GetRoomByIdUseCaseService } from './room-get-by-id-use-case.service'
|
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({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@@ -8,16 +9,18 @@ export class RoomUpdateNameSyncService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private GetRoomByIdUseCaseService: GetRoomByIdUseCaseService,
|
private GetRoomByIdUseCaseService: GetRoomByIdUseCaseService,
|
||||||
private roomSocketRepository: IRoomSocketRepository
|
private roomSocketRepository: IRoomSocketRepository,
|
||||||
|
private getRoomListUseCaseService: GetRoomListUseCaseService
|
||||||
) {
|
) {
|
||||||
// this.watch()
|
this.watch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private watch() {
|
private watch() {
|
||||||
this.roomSocketRepository.listenToRoomUpdate().subscribe((event) => {
|
this.roomSocketRepository.listenToRoomUpdate().subscribe((event) => {
|
||||||
console.log('listenToRoomUpdate')
|
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({
|
const SocketMessageCreateOutputSchema = MessageEntitySchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
attachments: true,
|
|
||||||
canEdit: true,
|
canEdit: true,
|
||||||
editedAt: true,
|
editedAt: true,
|
||||||
info: true,
|
info: true,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { IMessageLocalRepository } from '../../repository/message/message-local-
|
|||||||
|
|
||||||
const SocketMessageDeleteOutputSchema = MessageEntitySchema.pick({
|
const SocketMessageDeleteOutputSchema = MessageEntitySchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
attachments: true,
|
|
||||||
canEdit: true,
|
canEdit: true,
|
||||||
editedAt: true,
|
editedAt: true,
|
||||||
info: true,
|
info: true,
|
||||||
@@ -44,7 +43,7 @@ export class SocketMessageDeleteUseCaseService {
|
|||||||
if(result.isOk() && result.value) {
|
if(result.isOk() && result.value) {
|
||||||
|
|
||||||
tracing?.addEvent("Message found")
|
tracing?.addEvent("Message found")
|
||||||
const updateResult = await this.messageLocalDataSourceService.update(result.value.$id, { isDeleted: true })
|
const updateResult = await this.messageLocalDataSourceService.update(result.value.$id, { isDeleted: true, info: input.info })
|
||||||
return updateResult
|
return updateResult
|
||||||
}else {
|
}else {
|
||||||
tracing.hasError("failed to delete message")
|
tracing.hasError("failed to delete message")
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { IMessageLocalRepository } from '../../repository/message/message-local-
|
|||||||
|
|
||||||
const SocketMessageUpdateOutputSchema = MessageEntitySchema.pick({
|
const SocketMessageUpdateOutputSchema = MessageEntitySchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
attachments: true,
|
|
||||||
canEdit: true,
|
canEdit: true,
|
||||||
editedAt: true,
|
editedAt: true,
|
||||||
info: 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(),
|
Inactivity: z.boolean(),
|
||||||
UrlBeforeInactivity: z.string(),
|
UrlBeforeInactivity: z.string(),
|
||||||
UserPermissions: z.unknown(), // Again, you can define it more explicitly if needed
|
UserPermissions: z.unknown(), // Again, you can define it more explicitly if needed
|
||||||
UserPhoto: z.string(),
|
UserPhoto: z.string().nullable().optional(),
|
||||||
RefreshToken: z.string(),
|
RefreshToken: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { HttpErrorResponse } from "@angular/common/http";
|
import { HttpErrorResponse } from "@angular/common/http";
|
||||||
import { Result } from "neverthrow";
|
import { Result } from "neverthrow";
|
||||||
import { HttpResult } from "src/app/infra/http/type";
|
import { HttpResult } from "src/app/infra/http/type";
|
||||||
import { UserLoginInput } from "../use-case/user-login-use-case.service";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { UserPhotoUploadInput } from "../use-case/user-photo-upload.service";
|
||||||
|
|
||||||
const UserRepositoryLoginParams = z.object({
|
const UserRepositoryLoginParams = z.object({
|
||||||
Auth: z.string(),
|
Auth: z.string(),
|
||||||
@@ -17,7 +17,7 @@ const UserSchema = z.object({
|
|||||||
wxeMail: z.string(),
|
wxeMail: z.string(),
|
||||||
role: z.string(),
|
role: z.string(),
|
||||||
roleId: z.number(),
|
roleId: z.number(),
|
||||||
userPhoto: z.string(),
|
userPhoto: z.string().nullable().optional(),
|
||||||
adUserSID: z.string(),
|
adUserSID: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,4 +55,5 @@ export abstract class IUserRemoteRepository {
|
|||||||
abstract login(input: IUserRepositoryLoginParams): Promise<Result<HttpResult<UserLoginOutputResponse>, HttpErrorResponse>>
|
abstract login(input: IUserRepositoryLoginParams): Promise<Result<HttpResult<UserLoginOutputResponse>, HttpErrorResponse>>
|
||||||
abstract logout(): Promise<Result<HttpResult<any>, HttpErrorResponse>>
|
abstract logout(): Promise<Result<HttpResult<any>, HttpErrorResponse>>
|
||||||
abstract refreshToken(input:UserRefreshTokenInputDTO): 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);
|
SessionStore.setUrlBeforeInactivity(this.router.url);
|
||||||
logoutOut == false
|
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) {
|
if (environment.production) {
|
||||||
window.location.pathname = '/auth'
|
window.location.pathname = '/auth'
|
||||||
this.notificationService.DeletePostToken()
|
this.notificationService.DeletePostToken()
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ export class UserLoginUseCaseService {
|
|||||||
return err(result.error.status as LoginError)
|
return err(result.error.status as LoginError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return err(LoginError.userNotFound);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
tracing.setAttribute('parameter error','true')
|
tracing.setAttribute('parameter error','true')
|
||||||
// Logger.error('failed to send message doe to invalid attachment', {
|
// Logger.error('failed to send message doe to invalid attachment', {
|
||||||
|
|||||||
@@ -0,0 +1,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 { Injectable } from '@angular/core';
|
||||||
import { z } from 'zod';
|
|
||||||
import { IUserRemoteRepository } from '../repository/user-remote-repository';
|
import { IUserRemoteRepository } from '../repository/user-remote-repository';
|
||||||
import { SessionStore } from 'src/app/store/session.service';
|
import { SessionStore } from 'src/app/store/session.service';
|
||||||
import { Platform } from '@ionic/angular';
|
import { Platform } from '@ionic/angular';
|
||||||
|
|||||||
@@ -106,10 +106,10 @@ export class HomePage implements OnInit {
|
|||||||
|
|
||||||
|
|
||||||
this.router.events.subscribe((val) => {
|
this.router.events.subscribe((val) => {
|
||||||
document.querySelectorAll('ion-modal').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('popover-viewport').forEach((e: any) => e.remove())
|
||||||
document.querySelectorAll('.loading-blocker').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-popover').forEach((e: any) => e.remove())
|
||||||
});
|
});
|
||||||
|
|
||||||
window['platform'] = platform
|
window['platform'] = platform
|
||||||
@@ -118,9 +118,9 @@ export class HomePage implements OnInit {
|
|||||||
|
|
||||||
if (window.location.pathname != '/inactivity' && window.location.pathname != '/') {
|
if (window.location.pathname != '/inactivity' && window.location.pathname != '/') {
|
||||||
|
|
||||||
document.querySelectorAll('ion-modal').forEach((e: any) => e.remove());
|
document.querySelectorAll('ion-modal:not(.keep-this)').forEach((e: any) => e.remove());
|
||||||
document.querySelectorAll('.popover-viewport').forEach((e: any) => e.remove());
|
document.querySelectorAll('.popover-viewport:not(.keep-this) ').forEach((e: any) => e.remove());
|
||||||
document.querySelectorAll('.loading-blocker').forEach((e: any) => e.remove());
|
document.querySelectorAll('.loading-blocker:not(.keep-this)').forEach((e: any) => e.remove());
|
||||||
|
|
||||||
const pathname = window.location.pathname
|
const pathname = window.location.pathname
|
||||||
SessionStore.setUrlBeforeInactivity(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
|
$id: z.number().optional(), // local id
|
||||||
$messageId: z.string(),
|
$messageId: z.string(),
|
||||||
attachmentId: z.string().optional(),
|
attachmentId: z.string().optional(),
|
||||||
file: z.instanceof(Blob).optional(),
|
file: z.instanceof(Blob).nullable().optional(),
|
||||||
base64: zodDataUrlSchema.nullable().optional(),
|
base64: zodDataUrlSchema.nullable().optional(),
|
||||||
//
|
//
|
||||||
fileType: z.nativeEnum(MessageAttachmentFileType).optional(),
|
fileType: z.nativeEnum(MessageAttachmentFileType).optional(),
|
||||||
@@ -16,10 +16,11 @@ export const AttachmentTableSchema = z.object({
|
|||||||
applicationId: z.number().optional(),
|
applicationId: z.number().optional(),
|
||||||
docId: z.number().optional(),
|
docId: z.number().optional(),
|
||||||
mimeType: z.string().nullable().optional(),
|
mimeType: z.string().nullable().optional(),
|
||||||
id: z.string().uuid().optional(),
|
id: z.string().uuid().nullable().optional(),
|
||||||
description: z.string().optional()
|
description: z.string().optional(),
|
||||||
})
|
filePath: z.string().optional().nullable()
|
||||||
|
});
|
||||||
|
|
||||||
export type AttachmentTable = z.infer<typeof AttachmentTableSchema>
|
export type AttachmentTable = z.infer<typeof AttachmentTableSchema>
|
||||||
export type DexieAttachmentsTableSchema = EntityTable<AttachmentTable, '$id'>;
|
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(),
|
wxUserId: z.number(),
|
||||||
wxFullName: z.string(),
|
wxFullName: z.string(),
|
||||||
wxeMail: z.string(),
|
wxeMail: z.string(),
|
||||||
userPhoto: z.string().nullable(),
|
userPhoto: z.string().nullable().optional(),
|
||||||
joinAt: z.string(),
|
joinAt: z.string(),
|
||||||
status: z.string().optional(), // useless
|
status: z.string().optional(), // useless
|
||||||
isAdmin: z.boolean()
|
isAdmin: z.boolean()
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ export const MessageTableSchema = z.object({
|
|||||||
info: z.array(z.object({
|
info: z.array(z.object({
|
||||||
memberId: z.number(),
|
memberId: z.number(),
|
||||||
readAt: z.string().nullable(),
|
readAt: z.string().nullable(),
|
||||||
deliverAt: z.string().nullable()
|
deliverAt: z.string().nullable(),
|
||||||
|
isDeleted: z.boolean().optional(),
|
||||||
})).optional(),
|
})).optional(),
|
||||||
attachments: z.array(z.object({
|
attachments: z.array(z.object({
|
||||||
fileType: z.nativeEnum(MessageAttachmentFileType),
|
fileType: z.nativeEnum(MessageAttachmentFileType),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { IDBoolean } from "../../../type";
|
|||||||
export const RoomTableSchema = z.object({
|
export const RoomTableSchema = z.object({
|
||||||
$id: z.string().optional(),
|
$id: z.string().optional(),
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
roomName: z.string(),
|
roomName: z.string().nullable(),
|
||||||
createdBy: z.object({
|
createdBy: z.object({
|
||||||
wxUserId: z.number(),
|
wxUserId: z.number(),
|
||||||
wxFullName: z.string(),
|
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 { HttpClient } from '@angular/common/http';
|
||||||
import { ok, err, Result } from 'neverthrow';
|
import { ok, err, Result } from 'neverthrow';
|
||||||
import { HttpResult } from './type';
|
import { HttpResult } from './type';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { catchError, map } from 'rxjs/operators';
|
|
||||||
import { IHttpOptions } from './adapter';
|
import { IHttpOptions } from './adapter';
|
||||||
|
import { TracingType } from 'src/app/services/monitoring/opentelemetry/tracer';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class HttpService {
|
export class HttpService {
|
||||||
|
|
||||||
|
pendingRequests = new Set<string>();
|
||||||
private responseSubject = new BehaviorSubject<Result<HttpResult<any>, HttpErrorResponse>>(null);
|
private responseSubject = new BehaviorSubject<Result<HttpResult<any>, HttpErrorResponse>>(null);
|
||||||
|
|
||||||
constructor(private http: HttpClient) { }
|
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 {
|
try {
|
||||||
const response = await this.http.post<T>(url, body, { observe: 'response' }).toPromise();
|
var response = await fux(tracing) as HttpResponse<T>
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
data: response.body,
|
data: response.body,
|
||||||
status: response.status,
|
status: response.status,
|
||||||
headers: response.headers,
|
headers: response.headers,
|
||||||
url: response.url || url,
|
url: response.url || url,
|
||||||
method: '',
|
method: method || '',
|
||||||
}
|
}
|
||||||
this.responseSubject.next(ok(data))
|
this.responseSubject.next(ok(data))
|
||||||
return ok(data);
|
return ok(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.responseSubject.next(err(e))
|
this.responseSubject.next(err(e))
|
||||||
return err(e as HttpErrorResponse);
|
return err(e as HttpErrorResponse);
|
||||||
|
} finally {
|
||||||
|
this.pendingRequests.delete(requestKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async get<T>(url: string, options?: IHttpOptions): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
hasPendingRequest(method: string, path: string): boolean {
|
||||||
try {
|
for (const key of this.pendingRequests) {
|
||||||
let httpParams = new HttpParams();
|
const [reqMethod, reqUrl] = key.split(':');
|
||||||
if (options?.params) {
|
|
||||||
Object.keys(options.params).forEach(key => {
|
if (reqMethod == method && reqUrl.toLowerCase().includes(path.toLowerCase())) {
|
||||||
if(options.params[key]) {
|
return true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async put<T>(url: string, body: any): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
async post<T>(url: string, body: any, tracing?: TracingType): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
||||||
try {
|
return await this.handleRequest<T>(async () => await this.http.post<T>(url, body, { observe: 'response' }).toPromise(), tracing, url, 'POST')
|
||||||
const response = await this.http.put<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 patch<T>(url: string, body: any = {}): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
async get<T>(url: string, options?: IHttpOptions, tracing?: TracingType): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
||||||
try {
|
|
||||||
const response = await this.http.patch<T>(url, body, { observe: 'response' }).toPromise();
|
|
||||||
|
|
||||||
const data = {
|
let httpParams = new HttpParams();
|
||||||
data: response.body,
|
if (options?.params) {
|
||||||
status: response.status,
|
Object.keys(options.params).forEach(key => {
|
||||||
headers: response.headers,
|
if(options.params[key]) {
|
||||||
url: response.url || url,
|
httpParams = httpParams.append(key, options.params[key]);
|
||||||
method: '',
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.responseSubject.next(ok(data))
|
});
|
||||||
return ok(data);
|
|
||||||
} catch (e) {
|
|
||||||
this.responseSubject.next(err(e))
|
|
||||||
return err(e as HttpErrorResponse);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 delete<T>(url: string, body = {}): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
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 = {
|
const options = {
|
||||||
body: body, // Pass payload as the body of the request
|
body: body, // Pass payload as the body of the request
|
||||||
observe: 'response' as 'body'
|
observe: 'response' as 'body'
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
return await this.handleRequest<T>(async () => await this.http.delete<T>(url, options).toPromise(), tracing, url, 'DELETE');
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listen() {
|
listen() {
|
||||||
|
|||||||
@@ -14,4 +14,11 @@ export interface HttpResult<T> {
|
|||||||
method: string
|
method: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IHttPReturn<T> = Promise<Result<HttpResult<T>, HttpErrorResponse>>
|
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) {
|
if (event instanceof HttpResponse) {
|
||||||
// Capture the status code and check protocol
|
// Capture the status code and check protocol
|
||||||
if (req.method !== 'GET' && !req.urlWithParams.includes('metrics')) {
|
if (req.method !== 'GET' && !req.urlWithParams.includes('metrics')) {
|
||||||
|
|
||||||
console.log('response', event.body)
|
|
||||||
const path = req.urlWithParams;
|
const path = req.urlWithParams;
|
||||||
const url = new URL(path);
|
const url = new URL(path);
|
||||||
if (window.location.protocol !== 'https:') {
|
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 { SessionStore } from '../../../store/session.service';
|
||||||
import { environment } from "src/environments/environment";
|
import { environment } from "src/environments/environment";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
|
|
||||||
import { Platform } from '@ionic/angular';
|
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 { UserLoginOutputResponse } from "../../../core/user/repository/user-remote-repository";
|
||||||
import { UserLoginMapper } from "../../../core/user/mapper/user-login";
|
import { IRoomLocalRepository } from "src/app/core/chat/repository/room/room-local-repository";
|
||||||
import { UserSession } from "../../../models/user.model";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TokenInterceptor implements HttpInterceptor {
|
export class TokenInterceptor implements HttpInterceptor {
|
||||||
@@ -26,13 +25,14 @@ export class TokenInterceptor implements HttpInterceptor {
|
|||||||
null
|
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(
|
constructor(
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private httpErrorHandle: HttpErrorHandle,
|
private sessionExpiredModal: SessionExpiredModalService,
|
||||||
private platform: Platform) { }
|
private platform: Platform,
|
||||||
|
private roomLocalRepository: IRoomLocalRepository) { }
|
||||||
|
|
||||||
|
|
||||||
intercept(
|
intercept(
|
||||||
@@ -51,14 +51,12 @@ export class TokenInterceptor implements HttpInterceptor {
|
|||||||
return next.handle(request).pipe(
|
return next.handle(request).pipe(
|
||||||
|
|
||||||
catchError((error) => {
|
catchError((error) => {
|
||||||
console.log('interceptor ',error)
|
|
||||||
if(error.url.includes('/Users/RefreshToken') && error.status === 401) {
|
if(error.url.includes('/Users/RefreshToken') && error.status === 401) {
|
||||||
console.log("refresh token error11",error)
|
|
||||||
return throwError(error);
|
return throwError(error);
|
||||||
}
|
}
|
||||||
else if (error instanceof HttpErrorResponse && error.status === 401) {
|
else if (error instanceof HttpErrorResponse && error.status === 401) {
|
||||||
return this.handle401Error(request, next);
|
return this.handle401Error(request, next);
|
||||||
} else if (error.url.includes('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);
|
return this.handle401Error(request, next);
|
||||||
} else {
|
} else {
|
||||||
return throwError(error);
|
return throwError(error);
|
||||||
@@ -119,7 +117,7 @@ export class TokenInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.http
|
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,
|
authorization: SessionStore.user.Authorization,
|
||||||
refreshToken: SessionStore.user.RefreshToken,
|
refreshToken: SessionStore.user.RefreshToken,
|
||||||
channelId
|
channelId
|
||||||
@@ -136,22 +134,22 @@ export class TokenInterceptor implements HttpInterceptor {
|
|||||||
}),
|
}),
|
||||||
catchError((error) => {
|
catchError((error) => {
|
||||||
console.log("refresh token error",error)
|
console.log("refresh token error",error)
|
||||||
// SessionStore.user.Authorization = SessionStore.user.Authorization;
|
|
||||||
// SessionStore.user.RefreshToken = SessionStore.user.RefreshToken;
|
|
||||||
SessionStore.setInativity(false)
|
SessionStore.setInativity(false)
|
||||||
/* SessionStore.setUrlBeforeInactivity(this.router.url); */
|
|
||||||
|
|
||||||
if (environment.production) {
|
if(window.location.pathname.includes('/home/')) {
|
||||||
window.location.pathname = '/auth'
|
|
||||||
} else {
|
const sessionExpiredMessage =
|
||||||
const pathBeforeGoOut = window.location.pathname
|
'A sua sessão expirou. Por favor, faça login novamente para continuar.';
|
||||||
console.log('Before auth',window.location.pathname)
|
const goToAuth = () => {
|
||||||
this.router.navigateByUrl('/auth', { replaceUrl: true }).then(() =>{
|
if (environment.production) {
|
||||||
if(pathBeforeGoOut != "/auth") {
|
window.location.pathname = '/auth';
|
||||||
this.httpErrorHandle.httpsSucessMessagge('sessonExpired')
|
} 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);
|
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 { Logger } from 'src/app/services/logger/main/service';
|
||||||
import { IDexieRepository, ILocalModel, RepositoryResult } from '../adapter'
|
import { IDexieRepository, ILocalModel, RepositoryResult } from '../adapter'
|
||||||
import { IDBError, IDBErrorParams, IDexieError } from '../types';
|
import { IDBError, IDBErrorParams, IDexieError } from '../types';
|
||||||
|
|
||||||
// Define a type for the Result of repository operations
|
|
||||||
|
|
||||||
class DBError<T> extends Error implements IDBError<T> {
|
class DBError<T> extends Error implements IDBError<T> {
|
||||||
zodError?: ZodError<T>;
|
zodError?: ZodError<T>;
|
||||||
parameters: T;
|
parameters: T;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { switchMap } from 'rxjs/operators';
|
|||||||
import { err, Result } from 'neverthrow';
|
import { err, Result } from 'neverthrow';
|
||||||
import { HubConnection } from '@microsoft/signalr';
|
import { HubConnection } from '@microsoft/signalr';
|
||||||
import { ISignalRInput, ISignalROutput } from '../type';
|
import { ISignalRInput, ISignalROutput } from '../type';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
|
||||||
const { App } = Plugins;
|
const { App } = Plugins;
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ export class SignalRService {
|
|||||||
async establishConnection(): Promise<Result<HubConnection, false>> {
|
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://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()
|
const attempConnection = await connection.establishConnection()
|
||||||
|
|
||||||
if(attempConnection.isOk()) {
|
if(attempConnection.isOk()) {
|
||||||
|
|||||||
@@ -131,8 +131,8 @@ export class SignalRConnection {
|
|||||||
|
|
||||||
this.sendDataSubject.pipe(
|
this.sendDataSubject.pipe(
|
||||||
filter((message) => {
|
filter((message) => {
|
||||||
return input.data.requestId == message?.data.requestId ||
|
return input.data.requestId == message?.data?.requestId ||
|
||||||
input?.data?.roomName == message?.data.roomName && typeof input?.data?.roomName == 'string'
|
input?.data?.roomName == message?.data?.roomName && typeof input?.data?.roomName == 'string'
|
||||||
|
|
||||||
}),
|
}),
|
||||||
).subscribe(value => {
|
).subscribe(value => {
|
||||||
@@ -161,7 +161,7 @@ export class SignalRConnection {
|
|||||||
|
|
||||||
const methods = ['ReceiveMessage', 'TypingMessage', 'AvailableUsers',
|
const methods = ['ReceiveMessage', 'TypingMessage', 'AvailableUsers',
|
||||||
'ReadAt', 'DeleteMessage', 'UpdateMessage', 'GroupAddedMembers',
|
'ReadAt', 'DeleteMessage', 'UpdateMessage', 'GroupAddedMembers',
|
||||||
'GroupDeletedMembers', 'UserAddGroup']
|
'GroupDeletedMembers', 'UserAddGroup', 'GroupUpdate']
|
||||||
|
|
||||||
for(const method of methods) {
|
for(const method of methods) {
|
||||||
this.hubConnection.on(method, (message: any) => {
|
this.hubConnection.on(method, (message: any) => {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export class CreateProcessPage implements OnInit {
|
|||||||
this.fulltask = this.navParams.get('fulltask');
|
this.fulltask = this.navParams.get('fulltask');
|
||||||
|
|
||||||
|
|
||||||
// console.log('this.fulltask', this.fulltask)
|
console.log('this.fulltask', this.fulltask)
|
||||||
|
|
||||||
|
|
||||||
// if(this.fulltask?.Documents) {
|
// if(this.fulltask?.Documents) {
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ export class CropImagePage implements OnInit {
|
|||||||
private modalController: ModalController,
|
private modalController: ModalController,
|
||||||
) {
|
) {
|
||||||
this.base64ToCroppe = this.navParams.get('base64ToCroppe')
|
this.base64ToCroppe = this.navParams.get('base64ToCroppe')
|
||||||
console.log('To cropp',this.base64ToCroppe)
|
//console.log('To cropp',this.base64ToCroppe)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
console.log('To cropp',this.base64ToCroppe)
|
//console.log('To cropp',this.base64ToCroppe)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileChangeEvent(event: any): void {
|
fileChangeEvent(event: any): void {
|
||||||
@@ -37,8 +37,8 @@ export class CropImagePage implements OnInit {
|
|||||||
imageCropped(event: ImageCroppedEvent, tracing?: TracingType) {
|
imageCropped(event: ImageCroppedEvent, tracing?: TracingType) {
|
||||||
this.croppedImage = event.base64;
|
this.croppedImage = event.base64;
|
||||||
|
|
||||||
console.log('Croped image', event)
|
//console.log('Croped image', event)
|
||||||
console.log('Croped image 22', this.croppedImage)
|
//console.log('Croped image 22', this.croppedImage)
|
||||||
tracing.addEvent('Croped image')
|
tracing.addEvent('Croped image')
|
||||||
tracing.setAttribute('outcome','success')
|
tracing.setAttribute('outcome','success')
|
||||||
// event.blob can be used to upload the cropped image
|
// 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 mat-icon-button [matMenuTriggerFor]="menu" aria-label="Example icon-button with a menu">
|
||||||
<div class="d-flex align-center flex-column" >
|
<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">
|
<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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -125,29 +114,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { UserRepositoryService } from 'src/app/module/user/data/user-repository.
|
|||||||
import { isHttpError } from 'src/app/services/http.service';
|
import { isHttpError } from 'src/app/services/http.service';
|
||||||
import { UserProfilePicture } from 'src/app/module/user/data/datasource/user-local-repository.service';
|
import { UserProfilePicture } from 'src/app/module/user/data/datasource/user-local-repository.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
import { UserPhotoUploadUseCase } from 'src/app/core/user/use-case/user-photo-upload.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-edit-profile',
|
selector: 'app-edit-profile',
|
||||||
@@ -43,8 +44,8 @@ export class EditProfilePage implements OnInit {
|
|||||||
private CameraService: CameraService,
|
private CameraService: CameraService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private httpErrorHandle: HttpErrorHandle,
|
private httpErrorHandle: HttpErrorHandle,
|
||||||
private UserRepositoryService: UserRepositoryService
|
private UserRepositoryService: UserRepositoryService,
|
||||||
|
private UserPhotoUploadUseCase: UserPhotoUploadUseCase
|
||||||
) {
|
) {
|
||||||
this.profilePictureSubject = this.UserRepositoryService.getProfilePictureLive() as any
|
this.profilePictureSubject = this.UserRepositoryService.getProfilePictureLive() as any
|
||||||
}
|
}
|
||||||
@@ -178,33 +179,29 @@ export class EditProfilePage implements OnInit {
|
|||||||
if(capturedImage.isOk()) {
|
if(capturedImage.isOk()) {
|
||||||
|
|
||||||
this.capturedImage = capturedImage.value;
|
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()) {
|
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()) {
|
// if(base.isOk()) {
|
||||||
tracing.addEvent('download image')
|
// tracing.addEvent('download image')
|
||||||
this.profilePicture = 'data:image/jpeg;base64,' + base.value;
|
// this.profilePicture = 'data:image/jpeg;base64,' + base.value;
|
||||||
|
|
||||||
tracing.setAttribute("picture.save", "true")
|
// tracing.setAttribute("picture.save", "true")
|
||||||
|
|
||||||
} else {
|
// } else {
|
||||||
if(!isHttpError(base.error)) {
|
// 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.')
|
// this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico.')
|
||||||
} else {
|
// } else {
|
||||||
this.httpErrorHandle.httpStatusHandle(base.error)
|
// this.httpErrorHandle.httpStatusHandle(base.error)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if(!isHttpError(guid.error)) {
|
if(!isHttpError(guid.error)) {
|
||||||
|
|||||||
@@ -38,31 +38,10 @@
|
|||||||
<div class="profile-content">
|
<div class="profile-content">
|
||||||
|
|
||||||
<div class="d-flex align-center flex-column">
|
<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">
|
<div *ngIf="(profilePictureSubject | async) as calendarData"class="profile-pic">
|
||||||
<img class="profile-pic"
|
<img *ngIf="calendarData.base64 != null" class="profile-pic" src={{calendarData.base64}}>
|
||||||
src={{calendarData.base64}}>
|
<img *ngIf="calendarData.base64 == null" class="profile-pic" src="assets/images/theme/gov/icons-profile.svg">
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="profile-name d-flex justify-content-center width-100">
|
<div class="profile-name d-flex justify-content-center width-100">
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ export class ProfilePage implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
|
this.modalController.dismiss();
|
||||||
this.UserService.logout();
|
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
|
docId: any
|
||||||
task: ExpedientTaskModalPageNavParamsTask;
|
task: ExpedientTaskModalPageNavParamsTask;
|
||||||
Document: any
|
Document: any
|
||||||
|
link: any;
|
||||||
loader = true
|
loader = true
|
||||||
attachment: SearchList[] = [];
|
attachment: SearchList[] = [];
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ export class ViewDocumentPage implements OnInit {
|
|||||||
this.Document = this.navParams.get('Document')
|
this.Document = this.navParams.get('Document')
|
||||||
this.task = this.navParams.get('task')
|
this.task = this.navParams.get('task')
|
||||||
this.attachment = this.navParams.get('attachment')
|
this.attachment = this.navParams.get('attachment')
|
||||||
|
this.link = this.navParams.get('link')
|
||||||
|
|
||||||
|
|
||||||
if(!this.file.title) {
|
if(!this.file.title) {
|
||||||
@@ -57,38 +59,35 @@ export class ViewDocumentPage implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
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/')
|
const link: string = res.replace('//pdfjs/web/', '/pdfjs/web/')
|
||||||
this.trustedUrl = this.sanitazer.bypassSecurityTrustResourceUrl(link);
|
this.trustedUrl = this.sanitazer.bypassSecurityTrustResourceUrl(link);
|
||||||
|
|
||||||
// const iframe = document.getElementById("iframe")
|
if(res == "") {
|
||||||
// const handleLoad = () => {
|
const alert = await this.alertController.create({
|
||||||
// this.loader = false
|
cssClass: 'my-custom-class',
|
||||||
// };
|
//header: 'Apagar evento!',
|
||||||
|
message: 'Sem imagem',
|
||||||
// iframe.addEventListener('load', handleLoad, true)
|
buttons: [
|
||||||
|
{
|
||||||
if(res == "") {
|
text: 'Sim',
|
||||||
const alert = await this.alertController.create({
|
handler: () => {
|
||||||
cssClass: 'my-custom-class',
|
this.close();
|
||||||
//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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class ViewMediaPage implements OnInit {
|
|||||||
private navParams:NavParams,
|
private navParams:NavParams,
|
||||||
public sanitizer: DomSanitizer,
|
public sanitizer: DomSanitizer,
|
||||||
private platform: Platform,
|
private platform: Platform,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
this.image = this.navParams.get('image')
|
this.image = this.navParams.get('image')
|
||||||
this.type = this.navParams.get('type')
|
this.type = this.navParams.get('type')
|
||||||
@@ -38,32 +38,32 @@ export class ViewMediaPage implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
this.base64Sanitize = this.sanitizer.bypassSecurityTrustResourceUrl(this.image);
|
this.base64Sanitize = this.sanitizer.bypassSecurityTrustResourceUrl(this.image);
|
||||||
|
|
||||||
|
if (this.platform.is('desktop')) {
|
||||||
|
this.view = true;
|
||||||
|
} else {
|
||||||
|
this.view = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.platform.is('desktop')) {
|
|
||||||
this.view = true;
|
|
||||||
} else {
|
|
||||||
this.view = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
|
b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
|
||||||
const byteCharacters = atob(b64Data);
|
const byteCharacters = atob(b64Data);
|
||||||
const byteArrays = [];
|
const byteArrays = [];
|
||||||
|
|
||||||
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
||||||
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
||||||
|
|
||||||
const byteNumbers = new Array(slice.length);
|
const byteNumbers = new Array(slice.length);
|
||||||
for (let i = 0; i < slice.length; i++) {
|
for (let i = 0; i < slice.length; i++) {
|
||||||
byteNumbers[i] = slice.charCodeAt(i);
|
byteNumbers[i] = slice.charCodeAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteArray = new Uint8Array(byteNumbers);
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
byteArrays.push(byteArray);
|
byteArrays.push(byteArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = new Blob(byteArrays, { type: contentType });
|
const blob = new Blob(byteArrays, { type: contentType });
|
||||||
return blob;
|
return blob;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class ExpedientTaskModalPageNavParamsTask {
|
|||||||
SourceType: 'DOC' | 'FOLDER',
|
SourceType: 'DOC' | 'FOLDER',
|
||||||
SourceID: any // doc id
|
SourceID: any // doc id
|
||||||
}
|
}
|
||||||
|
Documents?: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export class task extends ExpedientTaskModalPageNavParamsTask {}
|
export class task extends ExpedientTaskModalPageNavParamsTask {}
|
||||||
@@ -160,7 +160,7 @@ const OwnerSchema = z.object({
|
|||||||
wxUserId: z.number(),
|
wxUserId: z.number(),
|
||||||
wxFullName: z.string(),
|
wxFullName: z.string(),
|
||||||
wxeMail: z.string(),
|
wxeMail: z.string(),
|
||||||
userPhoto: z.string(),
|
userPhoto: z.string().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
export interface Environment {
|
export interface Environment {
|
||||||
id: string;
|
id: string;
|
||||||
apiURL: string;
|
apiURL: string;
|
||||||
apiChatUrl: string;
|
apiURLStage: String
|
||||||
apiWsChatUrl: string;
|
|
||||||
apiPCURL: string;
|
apiPCURL: string;
|
||||||
logoLabel: string;
|
logoLabel: string;
|
||||||
production: boolean;
|
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";
|
import { SharedCalendarListItemOutputDTO } from "../../dto/sharedCalendarOutputDTO";
|
||||||
|
|
||||||
type Changes = {
|
type Changes = {
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ import { HttpService } from 'src/app/services/http.service';
|
|||||||
import { TracingType } from 'src/app/services/monitoring/opentelemetry/tracer';
|
import { TracingType } from 'src/app/services/monitoring/opentelemetry/tracer';
|
||||||
import { IGetDraftListByProcessIdOutput, IGetDraftListByProcessIdSchema } from '../../domain/usecase/getDraft-list-by-process-id.service';
|
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 { IDraftSaveByIdInput } from '../../domain/usecase/draft-save-by-id-use-case.service';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
|
|
||||||
export class AgendaDataService {
|
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(
|
constructor(
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ const OwnerSchema = z.object({
|
|||||||
wxUserId: z.number(),
|
wxUserId: z.number(),
|
||||||
wxFullName: z.string(),
|
wxFullName: z.string(),
|
||||||
wxeMail: z.string(),
|
wxeMail: z.string(),
|
||||||
userPhoto: z.string(),
|
userPhoto: z.string().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const OrganizerSchema = z.object({
|
const OrganizerSchema = z.object({
|
||||||
wxUserId: z.number(),
|
wxUserId: z.number(),
|
||||||
wxFullName: z.string(),
|
wxFullName: z.string(),
|
||||||
wxeMail: z.string(),
|
wxeMail: z.string(),
|
||||||
userPhoto: z.string(),
|
userPhoto: z.string().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const EventRecurrenceSchema = z.object({
|
const EventRecurrenceSchema = z.object({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const OwnerSchema = z.object({
|
|||||||
wxUserId: z.number(),
|
wxUserId: z.number(),
|
||||||
wxFullName: z.string(),
|
wxFullName: z.string(),
|
||||||
wxeMail: 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