mirror of
https://code.equilibrium.co.ao/ITO/doneit-web.git
synced 2026-04-21 13:55:51 +00:00
merge chat
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
import { Animation, AnimationController } from '@ionic/angular';
|
||||
|
||||
/* export const enterAnimation = (base) */
|
||||
@@ -10,10 +10,6 @@ const routes = [
|
||||
path: '',
|
||||
loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
|
||||
},
|
||||
{
|
||||
path: 'empty-chat',
|
||||
loadChildren: () => import('./shared/chat/empty-chat/empty-chat.module').then( m => m.EmptyChatPageModule)
|
||||
},
|
||||
{
|
||||
path: 'empty-container',
|
||||
loadChildren: () => import('./shared/empty-container/empty-container.module').then( m => m.EmptyContainerPageModule)
|
||||
@@ -30,10 +26,6 @@ const routes = [
|
||||
path: 'document-detail',
|
||||
loadChildren: () => import('./modals/document-detail/document-detail.module').then( m => m.DocumentDetailPageModule)
|
||||
},
|
||||
{
|
||||
path: 'edit-group',
|
||||
loadChildren: () => import('./shared/chat/edit-group/edit-group.module').then( m => m.EditGroupPageModule)
|
||||
},
|
||||
{
|
||||
path: 'pedidos',
|
||||
loadChildren: () => import('./shared/gabinete-digital/pedidos/pedidos.module').then( m => m.PedidosPageModule)
|
||||
@@ -234,10 +226,6 @@ const routes = [
|
||||
path: 'previewer',
|
||||
loadChildren: () => import('./modals/previewer/previewer.module').then( m => m.PreviewerPageModule)
|
||||
},
|
||||
{
|
||||
path: 'set-room-owner',
|
||||
loadChildren: () => import('./modals/set-room-owner/set-room-owner.module').then( m => m.SetRoomOwnerPageModule)
|
||||
},
|
||||
{
|
||||
path: 'diplomas-gerar',
|
||||
loadChildren: () => import('./shared/gabinete-digital/diplomas-gerar/diplomas-gerar.module').then( m => m.DiplomasGerarPageModule)
|
||||
@@ -246,26 +234,10 @@ const routes = [
|
||||
path: 'diplomas-gerar-options',
|
||||
loadChildren: () => import('./shared/popover/diplomas-gerar-options/diplomas-gerar-options.module').then( m => m.DiplomasGerarOptionsPageModule)
|
||||
},
|
||||
{
|
||||
path: 'chat-debugging',
|
||||
loadChildren: () => import('./shared/popover/chat-debugging/chat-debugging.module').then( m => m.ChatDebuggingPageModule)
|
||||
},
|
||||
{
|
||||
path: 'chat-message-debugging',
|
||||
loadChildren: () => import('./shared/popover/chat-message-debugging/chat-message-debugging.module').then( m => m.ChatMessageDebuggingPageModule)
|
||||
},
|
||||
{
|
||||
path: 'add-user',
|
||||
loadChildren: () => import('./shared/chat/add-user/add-user.module').then( m => m.AddUserPageModule)
|
||||
},
|
||||
{
|
||||
path: 'information',
|
||||
loadChildren: () => import('./modals/information/information.module').then( m => m.InformationPageModule)
|
||||
},
|
||||
{
|
||||
path: 'chat-options-popover',
|
||||
loadChildren:() => import('./shared/popover/chat-options-popover/chat-options-popover.module').then( m => m.ChatOptionsPopoverPageModule)
|
||||
},
|
||||
{
|
||||
path: 'task-listt-header',
|
||||
loadChildren: () => import('./shared/gabinete-digital/generic/task-listt-header/task-listt-header.module').then( m => m.TaskListtHeaderPageModule)
|
||||
@@ -318,8 +290,6 @@ const routes = [
|
||||
path: 'delete-event-recurrence',
|
||||
loadChildren: () => import('./modals/delete-event-recurrence/delete-event-recurrence.module').then( m => m.DeleteEventRecurrencePageModule)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
path: 'chat',
|
||||
component: ChatPage
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Platform } from '@ionic/angular';
|
||||
import { StatusBar } from '@ionic-native/status-bar/ngx';
|
||||
import { NgxMatDateFormats } from '@angular-material-components/datetime-picker';
|
||||
import { NGX_MAT_DATE_FORMATS } from '@angular-material-components/datetime-picker';
|
||||
import { ChatSystemService } from 'src/app/services/chat/chat-system.service';
|
||||
import { InativityService } from "src/app/services/inativity.service";
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { Storage } from '@ionic/storage';
|
||||
import { ChatController } from './controller/chat';
|
||||
import { register } from 'swiper/element/bundle';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import {ScreenOrientation} from "@ionic-native/screen-orientation/ngx";
|
||||
|
||||
import { ScreenOrientation } from "@ionic-native/screen-orientation/ngx";
|
||||
const CUSTOM_DATE_FORMATS: NgxMatDateFormats = {
|
||||
parse: {
|
||||
dateInput: "YYYY-MMMM-DD HH:mm"
|
||||
@@ -41,15 +36,13 @@ export class AppComponent {
|
||||
private statusBar: StatusBar,
|
||||
public ThemeService: ThemeService,
|
||||
private storage: Storage,
|
||||
private ChatSystemService: ChatSystemService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private screenOrientation: ScreenOrientation
|
||||
private screenOrientation: ScreenOrientation,
|
||||
) {
|
||||
|
||||
window["sanitizer"] = this.sanitizer
|
||||
this.initializeApp();
|
||||
this.storage.set('version', environment.version).then(() => {})
|
||||
ChatController.ChatSystemService = this.ChatSystemService
|
||||
|
||||
}
|
||||
|
||||
@@ -82,7 +75,7 @@ export class AppComponent {
|
||||
if(this.platform.is('ios')){
|
||||
this.screenOrientation.lock('portrait')
|
||||
} else {
|
||||
window.screen.orientation.lock('portrait');
|
||||
(window.screen.orientation as any).lock('portrait');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+51
-25
@@ -22,7 +22,6 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { CalendarModule, DateAdapter } from 'angular-calendar';
|
||||
import { adapterFactory } from 'angular-calendar/date-adapters/date-fns';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ChatService } from './services/chat.service';
|
||||
import {MatDatepickerModule} from '@angular/material/datepicker';
|
||||
import {MAT_DATE_LOCALE} from '@angular/material/core';
|
||||
import { Network } from '@ionic-native/network/ngx';
|
||||
@@ -97,20 +96,20 @@ import { DiplomaOptionsPage } from './shared/popover/deploma-options/deploma-opt
|
||||
import { ImageCropperModule } from 'ngx-image-cropper';
|
||||
import { metricsInterceptor, MetricsInterceptor } from './interceptors/metter.interceptor';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
|
||||
import {MatMenuModule} from '@angular/material/menu';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||
import { calendarReducer } from './module/agenda/data/data-source/agenda-memory-source.service';
|
||||
// import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
// import { AngularFireModule } from '@angular/fire';
|
||||
// import { AngularFireMessagingModule } from '@angular/fire/messaging';
|
||||
// import { environment } from 'src/environments/environment';
|
||||
import { createAction, createReducer, on, StoreModule } from '@ngrx/store';
|
||||
import { ChatModule } from './module/chat/chat.module';
|
||||
import { openTelemetryLogging, OpenTelemetryLogging } from './services/monitoring/opentelemetry/logging';
|
||||
|
||||
/* import { FCM } from '@ionic-native/fcm/ngx';
|
||||
import { FirebaseX } from '@ionic-native/firebase-x/ngx'; */
|
||||
//import { FCM } from 'cordova-plugin-fcm-with-dependecy-updated/ionic/ngx';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import localePt from '@angular/common/locales/pt';
|
||||
|
||||
// Register the locale data
|
||||
registerLocaleData(localePt, 'pt');
|
||||
|
||||
Sentry.init(
|
||||
{
|
||||
@@ -122,26 +121,55 @@ import { FirebaseX } from '@ionic-native/firebase-x/ngx'; */
|
||||
// We recommend adjusting this value in production.
|
||||
tracesSampleRate: 1.0,
|
||||
integrations: [
|
||||
// new BrowserTracing({
|
||||
// tracingOrigins: ['localhost', 'https://gd-api.oapr.gov.ao/api/'],
|
||||
// }) as Integration,
|
||||
new BrowserTracing({
|
||||
tracingOrigins: ['localhost', 'https://gd-api.oapr.gov.ao/api/'],
|
||||
}) as Integration,
|
||||
],
|
||||
beforeSend(event, hint) {
|
||||
|
||||
// Modify the event here
|
||||
// console.log('Intercepted Sentry event:', event);
|
||||
|
||||
// Optionally, return null to drop the event or return the modified event
|
||||
beforeSend(event) {
|
||||
if (event.level === 'error') {
|
||||
// console.log(event.exception.values[0].value)
|
||||
openTelemetryLogging.send({
|
||||
type: 'graylog',
|
||||
payload: {
|
||||
message: event.exception.values[0].value,
|
||||
object: {
|
||||
sentry: true,
|
||||
error: event
|
||||
}
|
||||
},
|
||||
spanContext: null
|
||||
})
|
||||
}
|
||||
// Return event to send it to Sentry
|
||||
return event;
|
||||
}
|
||||
},
|
||||
},
|
||||
// Forward the init method to the sibling Framework.
|
||||
SentrySibling.init
|
||||
SentrySibling.init,
|
||||
);
|
||||
|
||||
export const increment = createAction('[Counter Component] Increment');
|
||||
export const decrement = createAction('[Counter Component] Decrement');
|
||||
export const reset = createAction('[Counter Component] Reset');
|
||||
|
||||
export const initialState = 0;
|
||||
|
||||
const _counterReducer = createReducer(
|
||||
initialState,
|
||||
on(increment, (state) => state + 1),
|
||||
on(decrement, (state) => state - 1),
|
||||
on(reset, (state) => 0)
|
||||
);
|
||||
|
||||
export function counterReducer(state, action) {
|
||||
return _counterReducer(state, action);
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, PopupQuestionPipe, InputFilterDirective],
|
||||
imports: [BrowserModule,
|
||||
StoreModule.forRoot({ calendar: calendarReducer }),
|
||||
// StoreModule.forRoot({ userTyping: typingReducer }),
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
CalendarModule.forRoot({
|
||||
@@ -196,9 +224,10 @@ import { FirebaseX } from '@ionic-native/firebase-x/ngx'; */
|
||||
DeplomaOptionsPageModule,
|
||||
CreateProcessPageModule,
|
||||
ImageCropperModule,
|
||||
|
||||
MatMenuModule,
|
||||
MatIconModule
|
||||
MatIconModule,
|
||||
// module
|
||||
ChatModule
|
||||
],
|
||||
entryComponents: [
|
||||
DiplomaOptionsPage,
|
||||
@@ -223,7 +252,6 @@ import { FirebaseX } from '@ionic-native/firebase-x/ngx'; */
|
||||
FilePath,
|
||||
/* FCM,
|
||||
FirebaseX, */
|
||||
ChatService,
|
||||
ScreenOrientation,
|
||||
Network,
|
||||
SQLite,
|
||||
@@ -244,8 +272,6 @@ import { FirebaseX } from '@ionic-native/firebase-x/ngx'; */
|
||||
chatTokenInterceptor,
|
||||
tokenInterceptor,
|
||||
metricsInterceptor
|
||||
|
||||
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/* This file stores all the keys */
|
||||
export class AuthConnstants{
|
||||
/* My reference key */
|
||||
public static readonly AUTH = 'userDataKey';
|
||||
public static readonly PROFILE = 'profile';
|
||||
public static readonly USER = 'userId';
|
||||
public static readonly USERCHAT = 'userChat';
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ChatSystemService } from "../services/chat/chat-system.service";
|
||||
|
||||
export class ChatController {
|
||||
|
||||
static ChatSystemService: ChatSystemService
|
||||
|
||||
static async createGroup(name) {
|
||||
let data: any = await this.ChatSystemService.createGroup(name)
|
||||
|
||||
if(data?.error?.error == 'error-invalid-room-name') {
|
||||
name = name.slice(0, 12)
|
||||
data = await this.ChatSystemService.createGroup(name)
|
||||
return {
|
||||
data,
|
||||
roomName: name
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
roomName: name
|
||||
}
|
||||
}
|
||||
|
||||
static async createOrFindGroup(name) {
|
||||
let data: any = await this.createGroup(name)
|
||||
}
|
||||
|
||||
}
|
||||
+28
-28
@@ -1,5 +1,5 @@
|
||||
import { chatUser } from '../models/chatMethod'
|
||||
import { ChatController } from './chat'
|
||||
|
||||
//import { ChatController } from './chat'
|
||||
export class EventController {
|
||||
static create() {
|
||||
|
||||
@@ -12,36 +12,36 @@ export class EventController {
|
||||
|
||||
static async createOrFindGroupFromEvent(name, attendees) {
|
||||
// chatController.
|
||||
const {data, roomName} = await ChatController.createGroup(name)
|
||||
// const {data, roomName} = await ChatController.createGroup(name)
|
||||
|
||||
const roomId = data.rid
|
||||
// const roomId = data.rid
|
||||
|
||||
if (data.error.error == "error-duplicate-channel-name") {
|
||||
const getGroupByName = ChatController.ChatSystemService.getGroupByName(roomName)
|
||||
if(getGroupByName) {
|
||||
return getGroupByName
|
||||
}
|
||||
} else if (roomId) {
|
||||
const room = await ChatController.ChatSystemService.waitRoomToCreate(roomId)
|
||||
|
||||
const chatUsers: chatUser[] = []
|
||||
// if (data.error.error == "error-duplicate-channel-name") {
|
||||
// const getGroupByName = ChatController.ChatSystemService.getGroupByName(roomName)
|
||||
// if(getGroupByName) {
|
||||
// return getGroupByName
|
||||
// }
|
||||
// } else if (roomId) {
|
||||
// const room = await ChatController.ChatSystemService.waitRoomToCreate(roomId)
|
||||
|
||||
for (let webTRIXUser of attendees ) {
|
||||
const username = webTRIXUser.EmailAddress.split("@")[0];
|
||||
const name = webTRIXUser.Name
|
||||
|
||||
const findChatUser = ChatController.ChatSystemService.searchContact(name, username)
|
||||
if(findChatUser) {
|
||||
chatUsers.push(findChatUser)
|
||||
}
|
||||
}
|
||||
|
||||
for (let chatUser of chatUsers) {
|
||||
room.addContacts(chatUser._id)
|
||||
}
|
||||
// const chatUsers: chatUser[] = []
|
||||
|
||||
return room
|
||||
}
|
||||
// for (let webTRIXUser of attendees ) {
|
||||
// const username = webTRIXUser.EmailAddress.split("@")[0];
|
||||
// const name = webTRIXUser.Name
|
||||
|
||||
// const findChatUser = ChatController.ChatSystemService.searchContact(name, username)
|
||||
// if(findChatUser) {
|
||||
// chatUsers.push(findChatUser)
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (let chatUser of chatUsers) {
|
||||
// room.addContacts(chatUser._id)
|
||||
// }
|
||||
|
||||
// return room
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { object, z } from 'zod';
|
||||
import { MessageEntitySchema } from './message';
|
||||
import { BaseEntity } from 'src/app/utils/entity';
|
||||
|
||||
const DistributionEntitySchema = z.object({
|
||||
$messageIdMemberId: z.string().optional(),
|
||||
messageId: z.string(),
|
||||
memberId: z.number(),
|
||||
readAt: z.string().nullable(),
|
||||
deliverAt: z.string().nullable(),
|
||||
roomId: z.string(),
|
||||
})
|
||||
|
||||
export type IDistribution = z.infer<typeof DistributionEntitySchema>
|
||||
|
||||
|
||||
export class DistributionEntity extends BaseEntity<DistributionEntity>(DistributionEntitySchema) implements IDistribution{
|
||||
|
||||
roomId: typeof DistributionEntitySchema._input.roomId
|
||||
readAt: typeof DistributionEntitySchema._input.readAt
|
||||
messageId: typeof DistributionEntitySchema._input.messageId
|
||||
memberId: typeof DistributionEntitySchema._input.memberId
|
||||
deliverAt: typeof DistributionEntitySchema._input.deliverAt
|
||||
|
||||
constructor(data: IDistribution) {
|
||||
super();
|
||||
Object.assign(this, data)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { SessionStore } from "src/app/store/session.service";
|
||||
import { BaseEntity } from "src/app/utils/entity";
|
||||
import { z } from "zod"
|
||||
import { MessageEntitySchema } from "./message";
|
||||
|
||||
export enum RoomType {
|
||||
Group = 1,
|
||||
Direct = 2
|
||||
}
|
||||
|
||||
const UserSchema = z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable(),
|
||||
});
|
||||
|
||||
const MemberSchema = z.object({
|
||||
id: z.string(),
|
||||
user: UserSchema,
|
||||
joinAt: z.string(),
|
||||
isAdmin: z.boolean()
|
||||
});
|
||||
|
||||
export const RoomEntitySchema = z.object({
|
||||
id: z.string(),
|
||||
roomName: z.string(),
|
||||
createdBy: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string().email(),
|
||||
userPhoto: z.string().nullable().optional()// api check
|
||||
}),
|
||||
createdAt: z.any(),
|
||||
expirationDate: z.any().nullable(),
|
||||
roomType: z.nativeEnum(RoomType),
|
||||
members: z.array(MemberSchema).optional(),
|
||||
messages: MessageEntitySchema.array().optional()
|
||||
})
|
||||
|
||||
export type IRoom = z.infer<typeof RoomEntitySchema>
|
||||
|
||||
export class RoomEntity extends BaseEntity<RoomEntity>(RoomEntitySchema) implements IRoom{
|
||||
|
||||
id: typeof RoomEntitySchema._input.id
|
||||
roomName: typeof RoomEntitySchema._input.roomName
|
||||
createdBy: typeof RoomEntitySchema._input.createdBy
|
||||
createdAt: typeof RoomEntitySchema._input.createdAt
|
||||
expirationDate: typeof RoomEntitySchema._input.expirationDate
|
||||
roomType: typeof RoomEntitySchema._input.roomType
|
||||
members: typeof RoomEntitySchema._input.members
|
||||
messages: typeof RoomEntitySchema._input.messages
|
||||
|
||||
constructor(data: IRoom) {
|
||||
super();
|
||||
Object.assign(this, data)
|
||||
if(data.roomType == RoomType.Direct) {
|
||||
this.setName()
|
||||
}
|
||||
}
|
||||
|
||||
setName() {
|
||||
const userChatName = this.members?.find((e) => e.user.wxUserId != SessionStore.user.UserId)
|
||||
if(userChatName) {
|
||||
this.roomName = userChatName.user.wxFullName
|
||||
}
|
||||
}
|
||||
|
||||
hasLastMessage() {
|
||||
return this.messages?.length >= 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const MemberEntitySchema = z.object({
|
||||
$roomIdUserId: z.string().optional(),
|
||||
id: z.string().optional(), // useless
|
||||
roomId: z.string(),
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable(),
|
||||
joinAt: z.string(),
|
||||
status: z.string().optional(), // useless
|
||||
isAdmin: z.boolean()
|
||||
})
|
||||
|
||||
export type IMember = z.infer<typeof MemberEntitySchema>
|
||||
|
||||
|
||||
export class MemberEntity implements IMember {
|
||||
id: typeof MemberEntitySchema._type.id
|
||||
roomId: typeof MemberEntitySchema._type.roomId
|
||||
wxUserId: typeof MemberEntitySchema._type.wxUserId
|
||||
wxFullName: typeof MemberEntitySchema._type.wxFullName
|
||||
wxeMail: typeof MemberEntitySchema._type.wxeMail
|
||||
userPhoto: typeof MemberEntitySchema._type.userPhoto
|
||||
joinAt: typeof MemberEntitySchema._type.joinAt
|
||||
status: typeof MemberEntitySchema._type.status
|
||||
isAdmin: typeof MemberEntitySchema._type.isAdmin
|
||||
|
||||
hasPhoto() {
|
||||
return typeof this.userPhoto == 'string'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { z } from "zod";
|
||||
import { base64Schema } from "src/app/utils/zod";
|
||||
import { SessionStore } from "src/app/store/session.service";
|
||||
|
||||
export enum MessageAttachmentSource {
|
||||
Webtrix = 1,
|
||||
Device,
|
||||
}
|
||||
|
||||
export enum MessageAttachmentFileType {
|
||||
Doc = 1,
|
||||
Image ,
|
||||
Audio ,
|
||||
Video
|
||||
}
|
||||
|
||||
export enum IMessageType {
|
||||
normal = 1,
|
||||
information
|
||||
}
|
||||
|
||||
export const MessageEntityAttachmentSchema = 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(),
|
||||
id: z.string().optional().nullable(),
|
||||
mimeType: z.string().nullable().optional(),
|
||||
safeFile: z.any().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
})
|
||||
|
||||
export const MessageEntitySchema = z.object({
|
||||
$id: z.any().optional(),
|
||||
id: z.string().uuid().optional(),
|
||||
roomId: z.string().uuid().optional(),
|
||||
receiverId: z.number().optional(),
|
||||
message: z.string().nullable().optional(),
|
||||
messageType: z.nativeEnum(IMessageType),
|
||||
canEdit: z.boolean(),
|
||||
oneShot: z.boolean(),
|
||||
sentAt: z.string().optional(),
|
||||
requireUnlock: z.boolean(),
|
||||
isDeleted: z.boolean().optional(),
|
||||
editedAt: z.string().nullable().optional(),
|
||||
sender: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable(),
|
||||
}).nullable(),
|
||||
reactions: z.object({
|
||||
id: z.string(),
|
||||
reactedAt: z.string(),
|
||||
reaction: z.string(),
|
||||
sender: z.object({}),
|
||||
}).array().optional(),
|
||||
info: z.array(z.object({
|
||||
memberId: z.number(),
|
||||
readAt: z.string().nullable(),
|
||||
deliverAt: z.string().nullable()
|
||||
})).optional(),
|
||||
sending: z.boolean().optional(),
|
||||
attachments: z.array(MessageEntityAttachmentSchema).optional(),
|
||||
origin: z.enum(['history', 'local', 'incoming']).optional(),
|
||||
requestId: z.string().nullable().optional(),
|
||||
sendAttemp: z.number().optional(),
|
||||
hasAttachment: z.boolean().optional()
|
||||
})
|
||||
|
||||
export type IMessage = z.infer<typeof MessageEntitySchema>;
|
||||
|
||||
export class MessageEntity {
|
||||
|
||||
$id?: string
|
||||
id?: string
|
||||
roomId?: string
|
||||
receiverId?: number
|
||||
message?: string
|
||||
canEdit: boolean = false
|
||||
oneShot: boolean = false
|
||||
sentAt?: string
|
||||
requireUnlock: boolean = false
|
||||
info: typeof MessageEntitySchema._type.info = []
|
||||
sender!: typeof MessageEntitySchema._type.sender
|
||||
sending: boolean = false
|
||||
sendAttemp = 0
|
||||
messageType = IMessageType.normal
|
||||
attachments: typeof MessageEntitySchema._type.attachments = []
|
||||
reactions: typeof MessageEntitySchema._type.reactions = []
|
||||
requestId!: string
|
||||
isDeleted: typeof MessageEntitySchema._type.isDeleted = false
|
||||
|
||||
constructor() {}
|
||||
|
||||
get messageStatus() {
|
||||
if(this.id) {
|
||||
return 'send'
|
||||
}
|
||||
}
|
||||
|
||||
get hasAttachment() {
|
||||
return this.attachments.length >= 1
|
||||
}
|
||||
|
||||
static haveSeen(info: typeof MessageEntitySchema._type.info) {
|
||||
return info.filter(e => typeof e.readAt == 'string' && e.memberId == SessionStore.user.UserId).length == 1
|
||||
}
|
||||
|
||||
meSender() {
|
||||
return this.sender?.wxUserId == SessionStore.user.UserId
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { RoomByIdOutputDTO } from "src/app/module/chat/domain/use-case/room/room-get-by-id-use-case.service";
|
||||
import { RoomEntity } from "../entity/group";
|
||||
|
||||
export class GetRoomByIdMapper {
|
||||
static toDomain(input: RoomByIdOutputDTO): RoomEntity {
|
||||
|
||||
return new RoomEntity({
|
||||
createdAt: input.data.createdAt,
|
||||
createdBy: input.data.createdBy,
|
||||
expirationDate: input.data.expirationDate,
|
||||
id: input.data.id,
|
||||
members: input.data.members,
|
||||
roomName: input.data.roomName,
|
||||
roomType: input.data.roomType,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { RoomListItemOutPutDTO } from "src/app/module/chat/domain/use-case/room/room-get-list-use-case.service";
|
||||
import { RoomEntity } from "../entity/group";
|
||||
|
||||
export class GetRoomListMapper{
|
||||
|
||||
// @captureAndReraiseAsync('GetRoomListMapper/toDomain')
|
||||
static toDomain(inputs: RoomListItemOutPutDTO[]): RoomEntity[] {
|
||||
|
||||
return inputs.map((roomData) => new RoomEntity({
|
||||
createdAt: roomData.chatRoom.createdAt,
|
||||
createdBy: roomData.chatRoom.createdBy,
|
||||
expirationDate: roomData.chatRoom.expirationDate,
|
||||
id: roomData.chatRoom.id,
|
||||
roomName: roomData.chatRoom.roomName,
|
||||
roomType: roomData.chatRoom.roomType,
|
||||
members: [ roomData.chatRoom.user2, roomData.chatRoom.user1].filter((e) => e?.wxUserId).map((b) => ({
|
||||
id: '',
|
||||
isAdmin: false,
|
||||
joinAt: '',
|
||||
user: {
|
||||
userPhoto: b.userPhoto,
|
||||
wxeMail: b.wxeMail,
|
||||
wxFullName: b.wxFullName,
|
||||
wxUserId: b.wxUserId
|
||||
}
|
||||
})),
|
||||
messages: roomData.chatRoom.messages,
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { MessageTable } from "src/app/infra/database/dexie/instance/chat/schema/message";
|
||||
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
|
||||
import { MessageEntity } from "../../entity/message";
|
||||
import { Observable as DexieObservable, PromiseExtended } from 'Dexie';
|
||||
import { AttachmentTable } from "src/app/infra/database/dexie/instance/chat/schema/attachment";
|
||||
import { TypingTable } from "src/app/infra/database/dexie/instance/chat/schema/typing";
|
||||
|
||||
export abstract class ITypingLocalRepository extends DexieRepository<TypingTable, TypingTable> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { DataSourceReturn } from "src/app/services/Repositorys/type";
|
||||
|
||||
export abstract class IAttachmentRemoteRepository {
|
||||
abstract getAttachment(id: string | number): DataSourceReturn<Blob>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import Dexie, { PromiseExtended } from "Dexie";
|
||||
import { BoldTable } from "src/app/infra/database/dexie/instance/chat/schema/bold";
|
||||
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
|
||||
|
||||
|
||||
export abstract class IBoldLocalRepository extends DexieRepository<BoldTable, BoldTable> implements IBoldLocalRepository {
|
||||
abstract open(): PromiseExtended<Dexie>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { DistributionTable, DistributionTableSchema } from "src/app/infra/database/dexie/instance/chat/schema/destribution";
|
||||
import { chatDatabase } from "src/app/infra/database/dexie/service";
|
||||
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
|
||||
|
||||
|
||||
|
||||
export abstract class IDistributionLocalRepository extends DexieRepository<DistributionTable, DistributionTable> implements IDistributionLocalRepository {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export enum MessageAttachmentSource {
|
||||
Webtrix = 1,
|
||||
Device,
|
||||
}
|
||||
|
||||
export enum MessageAttachmentFileType {
|
||||
Doc = 1,
|
||||
Image ,
|
||||
Audio ,
|
||||
Video
|
||||
}
|
||||
|
||||
export const MessageOutPutDataDTOSchema = z.object({
|
||||
id: z.string(),
|
||||
roomId: z.string(),
|
||||
sender: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable().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 MessageOutPutDataDTO = z.infer<typeof MessageOutPutDataDTOSchema>
|
||||
@@ -0,0 +1,40 @@
|
||||
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/module/chat/domain/use-case/socket/member-list-update-status-use-case.service";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
|
||||
export const DirectMemberInput = MemberTableSchema.pick({
|
||||
roomId: true,
|
||||
}).extend({
|
||||
currentUserId: z.number(),
|
||||
})
|
||||
|
||||
export type IDirectMemberInput = z.infer<typeof DirectMemberInput>
|
||||
|
||||
export const GetMemberLiveInput = MemberTableSchema.pick({
|
||||
wxUserId: true,
|
||||
roomId: true,
|
||||
})
|
||||
|
||||
export type IGetMemberLive = z.infer<typeof GetMemberLiveInput>
|
||||
export abstract class IMemberLocalRepository extends DexieRepository<AttachmentTable, AttachmentTable> {
|
||||
|
||||
abstract directMember(input:IDirectMemberInput): Promise<Result<MemberTable, any>>
|
||||
abstract addMember(data: MemberTable): Promise<RepositoryResult<number, MemberTable>>
|
||||
abstract updateMemberRole(data: MemberTable): Promise<Result<number, any>>
|
||||
abstract updateMembersStatus(data: MemberListUPdateStatusInputDTO): Promise<Result<true, any>>
|
||||
abstract allMemberOnline(roomId:string): DexieObservable<boolean>
|
||||
abstract removeMemberFromRoom(roomId:string): Promise<Result<any ,any>>
|
||||
|
||||
abstract getMemberLive(input: IGetMemberLive): DexieObservable<MemberTable>
|
||||
abstract getRoomMemberById(roomId:string): Promise<MemberTable[]>
|
||||
abstract getRoomMemberByIdLive(roomId:string): Observable<MemberTable[]>
|
||||
abstract getRoomMemberNoneAdminByIdLive(roomId:string): DexieObservable<MemberTable[]>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { DataSourceReturn } from "src/app/services/Repositorys/type";
|
||||
import { Result } from "neverthrow";
|
||||
import { AddMemberToRoomInputDTO } from "src/app/module/chat/domain/use-case/member/member-add-use-case.service";
|
||||
import { MemberSetAdminDTO } from "src/app/module/chat/domain/use-case/member/member-admin-use-case.service";
|
||||
import { UserRemoveListInputDTO } from "src/app/module/chat/domain/use-case/room/room-leave-by-id-use-case.service";
|
||||
|
||||
export abstract class IMemberRemoteRepository {
|
||||
abstract addMemberToRoom(data: AddMemberToRoomInputDTO): DataSourceReturn<AddMemberToRoomInputDTO>
|
||||
|
||||
abstract removeMemberFromRoom(data: UserRemoveListInputDTO): Promise<Result<any ,any>>
|
||||
abstract setAmin(data: MemberSetAdminDTO): Promise<Result<any ,any>>
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MessageTable } from "src/app/infra/database/dexie/instance/chat/schema/message";
|
||||
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
|
||||
import { MessageEntity } from "../../entity/message";
|
||||
import { Observable as DexieObservable, PromiseExtended } from 'Dexie';
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
export abstract class IMessageLocalRepository extends DexieRepository<MessageTable, MessageEntity> {
|
||||
abstract setAllSenderToFalse(): void
|
||||
abstract getItems(roomId: string): PromiseExtended<MessageEntity[]>
|
||||
abstract getItemsLive(roomId: string): DexieObservable<MessageEntity[]>
|
||||
abstract getOfflineMessages(): Promise<MessageEntity[]>
|
||||
abstract onCreateObservable(): Observable<MessageTable>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { DataSourceReturn } from "src/app/services/Repositorys/type";
|
||||
import { IMessageGetAllByRoomIdOutPut } from "../../usecase/message/message-get-all-by-room-Id";
|
||||
|
||||
export abstract class IMessageRemoteRepository {
|
||||
abstract getMessagesFromRoom(id: string): DataSourceReturn<IMessageGetAllByRoomIdOutPut>
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { HubConnection } from "@microsoft/signalr";
|
||||
import { Result } from "neverthrow";
|
||||
import { MessageCreateOutPutDataDTO, MessageInputDTO } from "src/app/module/chat/domain/use-case/message/message-create-use-case.service";
|
||||
import { MessageMarkAsReadInput } from "src/app/module/chat/domain/use-case/message/message-mark-as-read-use-case.service";
|
||||
import { MessageDeleteInputDTO } from "src/app/module/chat/domain/use-case/message/message-delete-by-id-live-use-case.service";
|
||||
import { MessageOutPutDataDTO } from "../dto/messageOutputDTO";
|
||||
import { Observable } from "rxjs";
|
||||
import { MessageReactionInput } from "src/app/module/chat/domain/use-case/message/message-reaction-by-id-use-case.service";
|
||||
import { MessageUpdateInput } from "src/app/module/chat/domain/use-case/message/message-update-by-id-use-case.service";
|
||||
import { SocketMessage } from "src/app/infra/socket/signalR/signalR";
|
||||
|
||||
export abstract class IMessageSocketRepository {
|
||||
|
||||
abstract connect(): Promise<Result<HubConnection, false>>
|
||||
abstract sendGroupMessage(data: MessageInputDTO): Promise<Result<MessageCreateOutPutDataDTO, any>>
|
||||
abstract sendDirectMessage(data: MessageInputDTO): Promise<Result<MessageCreateOutPutDataDTO, any>>
|
||||
|
||||
// abstract sendDeliverAt(): Promise<Result<any, any>>
|
||||
abstract sendReadAt(data: MessageMarkAsReadInput): Promise<Result<any, any>>
|
||||
abstract sendDelete(data: MessageDeleteInputDTO): Promise<Result<any, any>>
|
||||
|
||||
abstract listenToMessages(): Observable<MessageOutPutDataDTO>
|
||||
abstract listenToDeleteMessages(): Observable<MessageOutPutDataDTO>
|
||||
abstract listenToUpdateMessages(): Observable<MessageOutPutDataDTO>
|
||||
|
||||
abstract listenToSendMessage(): Observable<SocketMessage<MessageInputDTO>>
|
||||
|
||||
abstract reactToMessageSocket(data: MessageReactionInput): void
|
||||
abstract updateMessage(input: MessageUpdateInput): void
|
||||
abstract sendMessageDelete(data: MessageDeleteInputDTO): Promise<Result<any, any>>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
export interface sendReadAtInput {
|
||||
memberId: number,
|
||||
messageId:string,
|
||||
roomId: string,
|
||||
requestId: string
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MessageTable } from "src/app/infra/database/dexie/instance/chat/schema/message";
|
||||
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
|
||||
import { MessageEntity } from "../../entity/message";
|
||||
import { Observable as DexieObservable, PromiseExtended } from 'Dexie';
|
||||
import { AttachmentTable } from "src/app/infra/database/dexie/instance/chat/schema/attachment";
|
||||
import { RoomTable } from "src/app/infra/database/dexie/instance/chat/schema/room";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
export abstract class IRoomLocalRepository extends DexieRepository<RoomTable, RoomTable> {
|
||||
abstract getItemsLive(): Observable<RoomTable[]>
|
||||
abstract getRoomByIdLive(id: any) : Observable<RoomTable>
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { DataSourceReturn } from "src/app/services/Repositorys/type";
|
||||
import { IMessageGetAllByRoomIdOutPut } from "../../usecase/message/message-get-all-by-room-Id";
|
||||
import { CreateRoomInputDTO, RoomOutPutDTO } from "src/app/module/chat/domain/use-case/room/room-create-use-case.service";
|
||||
import { RoomByIdInputDTO, RoomByIdOutputDTO } from "src/app/module/chat/domain/use-case/room/room-get-by-id-use-case.service";
|
||||
import { RoomUpdateInputDTO, RoomUpdateOutputDTO } from "src/app/module/chat/domain/use-case/room/room-update-by-id-use-case.service";
|
||||
import { Result } from "neverthrow";
|
||||
import { AddMemberToRoomInputDTO } from "src/app/module/chat/domain/use-case/member/member-add-use-case.service";
|
||||
import { RoomListOutPutDTO } from "src/app/module/chat/domain/use-case/room/room-get-list-use-case.service";
|
||||
|
||||
export abstract class IRoomRemoteRepository {
|
||||
abstract createRoom(data: CreateRoomInputDTO): DataSourceReturn<RoomOutPutDTO>
|
||||
abstract getRoomList(): Promise<DataSourceReturn<RoomListOutPutDTO>>
|
||||
abstract getRoom(id: RoomByIdInputDTO): DataSourceReturn<RoomByIdOutputDTO>
|
||||
abstract updateRoom(data: RoomUpdateInputDTO): Promise<DataSourceReturn<RoomUpdateOutputDTO>>
|
||||
abstract deleteRoom(id: string): Promise<Result<any ,any>>
|
||||
abstract addMemberToRoomSocket(data: AddMemberToRoomInputDTO): Promise<Result<any ,any>>
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { HubConnection } from "@microsoft/signalr";
|
||||
import { Result } from "neverthrow";
|
||||
import { Observable } from "rxjs";
|
||||
import { ListenToDeleteRoomInput, RoomSocketOutPutDTO } from "src/app/module/chat/data/repository/room/room-socket-repository.service";
|
||||
import { CreateRoomInputDTO } from "src/app/module/chat/domain/use-case/room/room-create-use-case.service";
|
||||
|
||||
export abstract class IRoomSocketRepository {
|
||||
|
||||
|
||||
abstract CreateGroup(data: CreateRoomInputDTO): Promise<Result<RoomSocketOutPutDTO,any>>
|
||||
|
||||
abstract listenToCreateRoom(): Observable<any>
|
||||
abstract listenToDeleteRoom(): Observable<ListenToDeleteRoomInput>
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { MessageTable } from "src/app/infra/database/dexie/instance/chat/schema/message";
|
||||
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
|
||||
import { MessageEntity } from "../../entity/message";
|
||||
import { Observable as DexieObservable, PromiseExtended } from 'Dexie';
|
||||
import { AttachmentTable } from "src/app/infra/database/dexie/instance/chat/schema/attachment";
|
||||
|
||||
export abstract class IAttachmentLocalRepository extends DexieRepository<AttachmentTable, AttachmentTable> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Observable } from "rxjs";
|
||||
import { Result } from "neverthrow";
|
||||
import { UserTypingDTO } from "src/app/module/chat/data/repository/typing/user-typing-live-data-source.service";
|
||||
|
||||
export abstract class ITypingRemoteRepository {
|
||||
abstract sendTyping(roomId: string): Promise<Result<unknown, any>>
|
||||
abstract listenToTyping(): Observable<UserTypingDTO>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import Dexie, { PromiseExtended } from "Dexie";
|
||||
import { UserPhotoTable } from "src/app/infra/database/dexie/instance/chat/schema/user-foto";
|
||||
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
|
||||
|
||||
|
||||
export abstract class IUserPhotoLocalRepository extends DexieRepository<UserPhotoTable, UserPhotoTable> implements IUserPhotoLocalRepository {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { HttpResult, IHttPReturn } from "src/app/infra/http/type"
|
||||
import { UserPhotoGetByIdInputSchema } from "src/app/module/chat/domain/use-case/user-photo/user-photo-get-by-id-use-case.service"
|
||||
import { z } from "zod"
|
||||
|
||||
export const IGetUserPhotoByAttachmentIdInputSchema = z.object({
|
||||
attachmentId: z.string(),
|
||||
})
|
||||
|
||||
export type IGetUserPhotoByAttachmentId = z.infer<typeof IGetUserPhotoByAttachmentIdInputSchema>
|
||||
|
||||
export abstract class IUserPhotoRemoteRepository {
|
||||
abstract getUserPhotoByAttachmentId(input: IGetUserPhotoByAttachmentId): IHttPReturn<string>
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { filter, map } from "rxjs/operators";
|
||||
import { HttpAdapter } from "src/app/infra/http/adapter";
|
||||
import { z } from "zod";
|
||||
import { IMessageGetAllByRoomIdOutPut } from "./message-get-all-by-room-Id";
|
||||
|
||||
const HttpListenToMessageLoadHistoryUseCaseInputSchema = z.object({
|
||||
roomId: z.string()
|
||||
})
|
||||
|
||||
export type HttpListenToMessageLoadHistoryUseCaseInput = z.infer<typeof HttpListenToMessageLoadHistoryUseCaseInputSchema>
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class HttpListenToMessageLoadHistoryUseCase{
|
||||
|
||||
constructor(
|
||||
private http: HttpAdapter
|
||||
) {}
|
||||
|
||||
execute(input: HttpListenToMessageLoadHistoryUseCaseInput): Observable<IMessageGetAllByRoomIdOutPut> {
|
||||
return this.http.listen().pipe(
|
||||
filter((response)=> response.isOk()),
|
||||
filter((response: any)=> {
|
||||
return response.value.url.includes(`Room/${input.roomId}/Messages`)
|
||||
}),
|
||||
map((response: any) => response.value.data as IMessageGetAllByRoomIdOutPut)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Result } from "neverthrow";
|
||||
import { Observable } from "rxjs";
|
||||
import { filter, map } from "rxjs/operators";
|
||||
import { HttpAdapter } from "src/app/infra/http/adapter";
|
||||
import { z } from "zod";
|
||||
import { MessageAttachmentFileType, MessageAttachmentSource } from "../../entity/message";
|
||||
|
||||
export const MessageGetAllByRoomIdUseCaseOutputSchema = z.object({
|
||||
success: z.boolean(),
|
||||
message: z.string(),
|
||||
data: z.object({
|
||||
id: z.string(),
|
||||
roomId: z.string(),
|
||||
isDeleted: z.boolean(),
|
||||
sender: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable().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()
|
||||
}))
|
||||
}).array()
|
||||
})
|
||||
|
||||
export type IMessageGetAllByRoomIdOutPut = z.infer<typeof MessageGetAllByRoomIdUseCaseOutputSchema>
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageGetAllByRoomIdUseCase{
|
||||
|
||||
constructor(
|
||||
private http: HttpAdapter
|
||||
) {}
|
||||
|
||||
execute(input: any) {}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { ISignalRService } from "src/app/infra/socket/adapter";
|
||||
import { z } from "zod"
|
||||
|
||||
const e = z.object({})
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SocketOnReconnectUseCase {
|
||||
|
||||
constructor(private SignalRService: ISignalRService) {}
|
||||
|
||||
execute(): Observable<boolean> {
|
||||
|
||||
const connection = this.SignalRService.onReconnect()
|
||||
return connection
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@ import { Routes, RouterModule } from '@angular/router';
|
||||
import { AuthGuard } from '../guards/auth.guard';
|
||||
import { InactivityGuard } from '../guards/inactivity.guard';
|
||||
import { AgendaPageModule } from '../pages/agenda/agenda.module';
|
||||
import { ChatPageModule } from '../pages/chat/chat.module';
|
||||
import { EventsPageModule } from '../pages/events/events.module';
|
||||
import { GabineteDigitalPageModule } from '../pages/gabinete-digital/gabinete-digital.module';
|
||||
import { PublicationsPageModule } from '../pages/publications/publications.module';
|
||||
import { HomePage } from './home.page';
|
||||
import { ChatPageModule } from '../ui/chat/chat.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -322,47 +322,8 @@ const routes: Routes = [
|
||||
path:'',
|
||||
loadChildren: ()=> ChatPageModule
|
||||
},
|
||||
{
|
||||
path:'messages',
|
||||
children: [
|
||||
{
|
||||
path:':roomId',
|
||||
loadChildren: ()=> import('../pages/chat/messages/messages.module').then(m => m.MessagesPageModule)
|
||||
},
|
||||
{
|
||||
path:'contacts',
|
||||
loadChildren: ()=> import('../shared/chat/messages/contacts/contacts.module').then(m => m.ContactsPageModule)
|
||||
},
|
||||
{
|
||||
path:'contacts',
|
||||
loadChildren: ()=> import('../pages/chat/messages/contacts/contacts.module').then(m => m.ContactsPageModule)
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'group-messages',
|
||||
children:[
|
||||
{
|
||||
path:'',
|
||||
loadChildren: ()=> import('../pages/chat/group-messages/group-messages.module').then(m => m.GroupMessagesPageModule)
|
||||
},
|
||||
{
|
||||
path:'group-contacts',
|
||||
loadChildren: ()=> import('../shared/chat/group-messages/group-contacts/group-contacts.module').then(m => m.GroupContactsPageModule)
|
||||
},
|
||||
{
|
||||
path:'group-contacts',
|
||||
loadChildren: ()=> import('../pages/chat/group-messages/group-contacts/group-contacts.module').then(m => m.GroupContactsPageModule)
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path:':eventId/:caller',
|
||||
loadChildren: () => import('../pages/agenda/view-event/view-event.module').then( m => m.ViewEventPageModule),
|
||||
},
|
||||
|
||||
],
|
||||
// canActivate: [AuthGuard]
|
||||
canActivate: [AuthGuard]
|
||||
|
||||
},
|
||||
{
|
||||
@@ -376,7 +337,7 @@ const routes: Routes = [
|
||||
|
||||
},
|
||||
],
|
||||
// canActivate: [AuthGuard]
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'inactivity',
|
||||
@@ -386,7 +347,7 @@ const routes: Routes = [
|
||||
loadChildren: ()=> import('../pages/inactivity/inactivity.module').then(m => m.InactivityPageModule)
|
||||
},
|
||||
],
|
||||
// canActivate: [InactivityGuard]
|
||||
canActivate: [InactivityGuard]
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
@@ -396,7 +357,7 @@ const routes: Routes = [
|
||||
loadChildren: ()=> import('../pages/inactivity/inactivity.module').then(m => m.InactivityPageModule)
|
||||
},
|
||||
],
|
||||
// canActivate: [InactivityGuard]
|
||||
canActivate: [InactivityGuard]
|
||||
},
|
||||
{
|
||||
path: 'pin',
|
||||
@@ -406,7 +367,7 @@ const routes: Routes = [
|
||||
loadChildren: ()=> import('../pages/inactivity/inactivity.module').then(m => m.InactivityPageModule)
|
||||
},
|
||||
],
|
||||
// canActivate: [InactivityGuard]
|
||||
canActivate: [InactivityGuard]
|
||||
|
||||
},
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<app-header ></app-header>
|
||||
</ion-header>
|
||||
|
||||
<ion-tab-bar class="dnone" *ngIf="p.userPermissionCount([permissionList.Agenda.access, permissionList.Gabinete.access, permissionList.Actions.access, permissionList.Chat.access]) >= 2 || (p.userPermission([permissionList.Agenda.access]) && SessionStore.user.OwnerCalendars.length != 0) || p.userPermission([permissionList.Gabinete.access])" class="bottoms" slot="bottom">
|
||||
<ion-tab-bar id="bottom-navigation" class="dnone" *ngIf="p.userPermissionCount([permissionList.Agenda.access, permissionList.Gabinete.access, permissionList.Actions.access, permissionList.Chat.access]) >= 2 || (p.userPermission([permissionList.Agenda.access]) && SessionStore.user.OwnerCalendars.length != 0) || p.userPermission([permissionList.Gabinete.access])" class="bottoms" slot="bottom">
|
||||
<ion-tab-button *ngIf="SessionStore.user.OwnerCalendars.length >= 1 || p.userPermission([permissionList.Gabinete.access])" (click)="goto('/home/events')" tab="events" [class.active]="ActiveTabService.pages.home">
|
||||
<!-- <ion-icon name="home"></ion-icon> -->
|
||||
<ion-icon *ngIf="!ActiveTabService.pages.home" class="font-30-em" src="assets/images/icons-nav-home.svg"></ion-icon>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PermissionService } from '../services/permission.service';
|
||||
import { BackgroundService } from 'src/app/services/background.service';
|
||||
import { Storage } from '@ionic/storage';
|
||||
import { EventsService } from 'src/app/services/events.service';
|
||||
import { RochetChatConnectorService } from 'src/app/services/chat/rochet-chat-connector.service';
|
||||
// import { RochetChatConnectorService } from 'src/app/services/chat/rochet-chat-connector.service';
|
||||
import { ProcessesService } from 'src/app/services/processes.service';
|
||||
import { RoleIdService } from 'src/app/services/role-id.service';
|
||||
import { ActiveTabService } from 'src/app/services/active-tab.service';
|
||||
@@ -91,7 +91,7 @@ export class HomePage implements OnInit {
|
||||
private storage: Storage,
|
||||
private processservice: ProcessesService,
|
||||
public RouteService: RouteService,
|
||||
private RochetChatConnectorService: RochetChatConnectorService,
|
||||
// private RochetChatConnectorService: RochetChatConnectorService,
|
||||
private NetworkServiceService: NetworkServiceService,
|
||||
public eventService: EventsService,
|
||||
public ActiveTabService: ActiveTabService,
|
||||
@@ -205,12 +205,12 @@ export class HomePage implements OnInit {
|
||||
}
|
||||
});
|
||||
|
||||
this.RochetChatConnectorService.registerCallback({
|
||||
type: 'reConnect',
|
||||
funx: async () => {
|
||||
this.backgroundservice.online()
|
||||
}
|
||||
})
|
||||
// this.RochetChatConnectorService.registerCallback({
|
||||
// type: 'reConnect',
|
||||
// funx: async () => {
|
||||
// this.backgroundservice.online()
|
||||
// }
|
||||
// })
|
||||
|
||||
this.NetworkServiceService.onNetworkChange().subscribe((status) => {
|
||||
if (status == ConnectionStatus.Online) {
|
||||
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NfService } from './nf.service';
|
||||
import { CameraService } from './camera.service';
|
||||
|
||||
describe('NfService', () => {
|
||||
let service: NfService;
|
||||
describe('CameraService', () => {
|
||||
let service: CameraService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(NfService);
|
||||
service = TestBed.inject(CameraService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
@@ -5,6 +5,21 @@ import { err, ok, Result } from 'neverthrow';
|
||||
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
|
||||
import { error } from '../../services/Either/index';
|
||||
|
||||
/**
|
||||
* Parameters for taking a picture.
|
||||
* @typedef {Object} takePictureParams
|
||||
* @property {number} [quality=90] - The quality of the picture, from 0 to 100.
|
||||
* @property {CameraResultType} cameraResultType - The result type of the photo, e.g., Base64, URI.
|
||||
*/
|
||||
export type takePictureParams = {
|
||||
quality?: number;
|
||||
cameraResultType: CameraResultType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Service for handling camera functionality.
|
||||
* This service provides methods to interact with the device's camera.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
@@ -13,7 +28,7 @@ export class CameraService {
|
||||
constructor() { }
|
||||
|
||||
|
||||
async takePicture(data: ITakePictureParams, tracing?: TracingType): Promise<Result<string, any>> {
|
||||
async takePictureAgenda(data: ITakePictureParams, tracing?: TracingType): Promise<Result<string, any>> {
|
||||
try {
|
||||
tracing?.addEvent('take picture')
|
||||
const capturedImage = await Camera.getPhoto({
|
||||
@@ -35,4 +50,25 @@ export class CameraService {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a picture using the device's camera.
|
||||
* @param {takePictureParams} params - The parameters for taking the picture.
|
||||
* @param {number} [params.quality=90] - The quality of the picture, from 0 to 100.
|
||||
* @param {CameraResultType} params.cameraResultType - The result type of the photo.
|
||||
* @returns {Promise<ok<File> | err<any>>} A promise that resolves with an `ok` result containing the file or an `err` result containing the error.
|
||||
*/
|
||||
async takePicture({quality = 90, cameraResultType }: takePictureParams) {
|
||||
try {
|
||||
const file = await Camera.getPhoto({
|
||||
quality: quality,
|
||||
resultType: cameraResultType,
|
||||
source: CameraSource.Camera
|
||||
});
|
||||
|
||||
return ok(file);
|
||||
} catch (e) {
|
||||
return err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { z } from "zod";
|
||||
import { EntityTable } from 'Dexie';
|
||||
import { zodDataUrlSchema } from "src/app/utils/zod";
|
||||
import { MessageAttachmentFileType, MessageAttachmentSource } from "src/app/core/chat/entity/message";
|
||||
|
||||
export const AttachmentTableSchema = z.object({
|
||||
$id: z.number().optional(), // local id
|
||||
$messageId: z.string(),
|
||||
attachmentId: z.string().optional(),
|
||||
file: z.instanceof(Blob).optional(),
|
||||
base64: zodDataUrlSchema.nullable().optional(),
|
||||
//
|
||||
fileType: z.nativeEnum(MessageAttachmentFileType).optional(),
|
||||
source: z.nativeEnum(MessageAttachmentSource).optional(),
|
||||
fileName: z.string().optional(),
|
||||
applicationId: z.number().optional(),
|
||||
docId: z.number().optional(),
|
||||
mimeType: z.string().nullable().optional(),
|
||||
id: z.string().uuid().optional(),
|
||||
description: z.string().optional()
|
||||
})
|
||||
|
||||
export type AttachmentTable = z.infer<typeof AttachmentTableSchema>
|
||||
export type DexieAttachmentsTableSchema = EntityTable<AttachmentTable, '$id'>;
|
||||
export const AttachmentTableColumn = '++$id, id, $messageId, messageId, file'
|
||||
@@ -0,0 +1,11 @@
|
||||
import { EntityTable } from 'Dexie';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const BoldTableSchema = z.object({
|
||||
roomId: z.string(),
|
||||
bold: z.number()
|
||||
})
|
||||
|
||||
export type BoldTable = z.infer<typeof BoldTableSchema>;
|
||||
export type DexieBoldTable = EntityTable<BoldTable, 'roomId'>;
|
||||
export const BoldTableColumn = 'roomId, bold';
|
||||
@@ -0,0 +1,15 @@
|
||||
import { EntityTable } from 'Dexie';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const DistributionTableSchema = z.object({
|
||||
$messageIdMemberId: z.string().optional(),
|
||||
messageId: z.string(),
|
||||
memberId: z.number(),
|
||||
readAt: z.string().nullable(),
|
||||
deliverAt: z.string().nullable(),
|
||||
roomId: z.string(),
|
||||
})
|
||||
|
||||
export type DistributionTable = z.infer<typeof DistributionTableSchema>
|
||||
export type DexieDistributionTable = EntityTable<DistributionTable, '$messageIdMemberId'>;
|
||||
export const DistributionTableColumn = '$messageIdMemberId, messageId, memberId, readAt, deliverAt, roomId'
|
||||
@@ -0,0 +1,19 @@
|
||||
import { z } from "zod";
|
||||
import { EntityTable } from 'Dexie';
|
||||
|
||||
export const MemberTableSchema = z.object({
|
||||
$roomIdUserId: z.string().optional(),
|
||||
id: z.string().optional(), // useless
|
||||
roomId: z.string(),
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable(),
|
||||
joinAt: z.string(),
|
||||
status: z.string().optional(), // useless
|
||||
isAdmin: z.boolean()
|
||||
})
|
||||
|
||||
export type MemberTable = z.infer<typeof MemberTableSchema>
|
||||
export type DexieMembersTableSchema = EntityTable<MemberTable, '$roomIdUserId'>;
|
||||
export const MemberTableColumn = '$roomIdUserId, userId, id, user, joinAt, roomId, status, wxUserId, isAdmin'
|
||||
@@ -0,0 +1,54 @@
|
||||
import { EntityTable } from 'Dexie';
|
||||
import { MessageAttachmentFileType, MessageAttachmentSource } from 'src/app/core/chat/entity/message';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const MessageTableSchema = z.object({
|
||||
$id: z.string().optional(),
|
||||
$createAt: z.number().optional(),
|
||||
id: z.string().uuid().optional(),
|
||||
roomId: z.string().uuid().optional(),
|
||||
message: z.string().nullable().optional(),
|
||||
requestId: z.string().nullable().optional(),
|
||||
messageType: z.number(),
|
||||
canEdit: z.boolean(),
|
||||
oneShot: z.boolean(),
|
||||
sentAt: z.string().optional(),
|
||||
editedAt: z.string().nullable().optional(),
|
||||
isDeleted: z.boolean().optional(),
|
||||
requireUnlock: z.boolean(),
|
||||
sender: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable().optional(),
|
||||
}),
|
||||
receiverId: z.number().optional(),
|
||||
sending: z.boolean().optional(),
|
||||
reactions: z.object({
|
||||
id: z.string(),
|
||||
reactedAt: z.string(),
|
||||
reaction: z.string(),
|
||||
sender: z.object({}),
|
||||
}).array().optional(),
|
||||
info: z.array(z.object({
|
||||
memberId: z.number(),
|
||||
readAt: z.string().nullable(),
|
||||
deliverAt: z.string().nullable()
|
||||
})).optional(),
|
||||
attachments: z.array(z.object({
|
||||
fileType: z.nativeEnum(MessageAttachmentFileType),
|
||||
source: z.nativeEnum(MessageAttachmentSource),
|
||||
fileName: z.string().optional(),
|
||||
applicationId: z.number().optional(),
|
||||
docId: z.number().optional(),
|
||||
id: z.string().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
mimeType: z.string().nullable().optional()
|
||||
})).optional(),
|
||||
origin: z.enum(['history', 'local', 'incoming']).optional()
|
||||
})
|
||||
|
||||
export type MessageTable = z.infer<typeof MessageTableSchema>
|
||||
export type DexieMessageTable = EntityTable<MessageTable, '$id'>;
|
||||
export const messageTableColumn = '$id, id, roomId, senderId, message, messageType, canEdit, oneShot, requireUnlock, sending'
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { z } from "zod";
|
||||
import { EntityTable } from 'Dexie';
|
||||
import { RoomType } from "src/app/core/chat/entity/group";
|
||||
import { MessageEntity, MessageEntitySchema } from "src/app/core/chat/entity/message";
|
||||
import { MessageTableSchema } from "./message";
|
||||
|
||||
export const RoomTableSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
roomName: z.string(),
|
||||
createdBy: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string().email(),
|
||||
userPhoto: z.string().nullable().optional()// api check
|
||||
}),
|
||||
createdAt: z.any(),
|
||||
expirationDate: z.any().nullable(),
|
||||
roomType: z.nativeEnum(RoomType),
|
||||
messages: MessageTableSchema.array().optional(),
|
||||
bold: z.number().optional()
|
||||
})
|
||||
|
||||
export type RoomTable = z.infer<typeof RoomTableSchema>
|
||||
export type DexieRoomsTable = EntityTable<RoomTable, 'id'>;
|
||||
export const RoomTableColumn = 'id, createdBy, roomName, roomType, expirationDate, lastMessage'
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from "zod";
|
||||
import { EntityTable } from 'Dexie';
|
||||
|
||||
export const TypingTableSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
userId: z.number().optional(),
|
||||
userName: z.string(),
|
||||
roomId: z.string(),
|
||||
})
|
||||
|
||||
export type TypingTable = z.infer<typeof TypingTableSchema>
|
||||
export type DexieTypingsTable = EntityTable<TypingTable, 'id'>;
|
||||
export const TypingTableColumn = 'id, userId, userName, roomId, entryDate'
|
||||
@@ -0,0 +1,12 @@
|
||||
import { EntityTable } from 'Dexie';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const UserPhotoTableSchema = z.object({
|
||||
wxUserId: z.number(),
|
||||
file: z.string(),
|
||||
attachmentId: z.string().nullable()
|
||||
})
|
||||
|
||||
export type UserPhotoTable = z.infer<typeof UserPhotoTableSchema>
|
||||
export type DexieUserPhotoTable = EntityTable<UserPhotoTable, 'wxUserId'>;
|
||||
export const UserPhotoTableColumn = 'wxUserId'
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
import { Dexie } from 'Dexie';
|
||||
import { DexieMessageTable, messageTableColumn, MessageTable } from 'src/app/infra/database/dexie/instance/chat/schema/message';
|
||||
import { DexieMembersTableSchema, MemberTableColumn } from 'src/app/infra/database/dexie/instance/chat/schema/members';
|
||||
import { DexieRoomsTable, RoomTableColumn } from 'src/app/infra/database/dexie/instance/chat/schema/room';
|
||||
import { DexieTypingsTable, TypingTableColumn } from 'src/app/infra/database/dexie/instance/chat/schema/typing';
|
||||
import { MessageEntity } from 'src/app/core/chat/entity/message';
|
||||
import { AttachmentTableColumn, DexieAttachmentsTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/attachment';
|
||||
import { DexieDistributionTable, DistributionTable, DistributionTableColumn } from './instance/chat/schema/destribution';
|
||||
import { BoldTableColumn, DexieBoldTable } from './instance/chat/schema/bold';
|
||||
import { DexieUserPhotoTable, UserPhotoTable, UserPhotoTableColumn } from './instance/chat/schema/user-foto';
|
||||
// import FDBFactory from 'fake-indexeddb/lib/FDBFactory';
|
||||
// import FDBKeyRange from 'fake-indexeddb/lib/FDBKeyRange';
|
||||
|
||||
|
||||
// Database declaration (move this to its own module also)
|
||||
export const chatDatabase = new Dexie('chat-database-v2',{
|
||||
// indexedDB: new FDBFactory,
|
||||
// IDBKeyRange: FDBKeyRange, // Mocking IDBKeyRange
|
||||
}) as Dexie & {
|
||||
message: DexieMessageTable,
|
||||
members: DexieMembersTableSchema,
|
||||
room: DexieRoomsTable,
|
||||
typing: DexieTypingsTable,
|
||||
attachment: DexieAttachmentsTableSchema,
|
||||
distribution: DexieDistributionTable,
|
||||
bold: DexieBoldTable,
|
||||
userPhoto: DexieUserPhotoTable
|
||||
};
|
||||
|
||||
chatDatabase.version(1).stores({
|
||||
message: messageTableColumn,
|
||||
members: MemberTableColumn,
|
||||
room: RoomTableColumn,
|
||||
typing: TypingTableColumn,
|
||||
attachment: AttachmentTableColumn,
|
||||
distribution: DistributionTableColumn,
|
||||
bold:BoldTableColumn,
|
||||
userPhoto: UserPhotoTableColumn
|
||||
});
|
||||
|
||||
chatDatabase.message.mapToClass(MessageEntity)
|
||||
// Apply in-memory storage
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Camera, CameraPhoto, CameraResultType, CameraSource } from '@capacitor/camera';
|
||||
import { err, ok, Result } from 'neverthrow';
|
||||
|
||||
/**
|
||||
* Parameters for picking a picture.
|
||||
* @typedef {Object} PickPictureParams
|
||||
* @property {number} [quality=90] - The quality of the picture, from 0 to 100. Defaults to 90.
|
||||
* @property {CameraResultType} [cameraResultType=CameraResultType.DataUrl] - The result type of the photo. Defaults to `CameraResultType.DataUrl`.
|
||||
*/
|
||||
type PickPictureParams = {
|
||||
quality?: number;
|
||||
cameraResultType?: CameraResultType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error types for the FilePickerService.
|
||||
*/
|
||||
export interface FilePickerError {
|
||||
type: 'PERMISSION_DENIED' | 'CANCELLED' | 'UNKNOWN';
|
||||
message: string;
|
||||
originalError?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for handling file picking functionality.
|
||||
* This service provides methods to pick a picture from the device's photo library.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FilePickerService {
|
||||
|
||||
constructor() { }
|
||||
|
||||
/**
|
||||
* Picks a picture from the device's photo library.
|
||||
* @param {PickPictureParams} params - The parameters for picking the picture.
|
||||
* @param {number} [params.quality=90] - The quality of the picture, from 0 to 100. Defaults to 90.
|
||||
* @param {CameraResultType} [params.cameraResultType=CameraResultType.DataUrl] - The result type of the photo. Defaults to `CameraResultType.DataUrl`.
|
||||
* @returns {Promise<ok<File> | err<any>>} A promise that resolves with an `ok` result containing the file or an `err` result containing the error.
|
||||
*/
|
||||
async getPicture({quality = 90, cameraResultType = CameraResultType.DataUrl }: PickPictureParams): Promise<Result<CameraPhoto, FilePickerError>> {
|
||||
try {
|
||||
const file = await Camera.getPhoto({
|
||||
quality: quality,
|
||||
resultType: cameraResultType,
|
||||
source: CameraSource.Photos
|
||||
})
|
||||
|
||||
return ok(file);
|
||||
} catch (e) {
|
||||
if (e.message.includes('denied')) {
|
||||
return err({
|
||||
type: 'PERMISSION_DENIED',
|
||||
message: 'Permission to access photos was denied.',
|
||||
originalError: e
|
||||
});
|
||||
} else if (e.message.includes('User cancelled photos app')) {
|
||||
return err({
|
||||
type: 'CANCELLED',
|
||||
message: 'User cancelled the photo selection.',
|
||||
originalError: e
|
||||
});
|
||||
} else {
|
||||
return err({
|
||||
type: 'UNKNOWN',
|
||||
message: 'An unknown error occurred while picking the picture.',
|
||||
originalError: e
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FilePicker, PickFilesResult } from '@capawesome/capacitor-file-picker';
|
||||
import { err, ok, Result } from 'neverthrow';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FilePickerMobileService {
|
||||
|
||||
constructor() { }
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```typescript
|
||||
* const types = ['application/pdf', 'application/doc', 'application/docx','application/xls', 'application/xlsx', 'application/ppt','application/pptx', 'application/txt'];
|
||||
* const multiple = false; // Invalid due to commas
|
||||
* const readData = true; // Invalid due to commas
|
||||
* ```
|
||||
*/
|
||||
async getFile({types, multiple, readData}): Promise<Result<PickFilesResult, any>> {
|
||||
try {
|
||||
|
||||
const result = await FilePicker.pickFiles({
|
||||
types: types,
|
||||
multiple: multiple,
|
||||
readData: readData,
|
||||
});
|
||||
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
|
||||
import { err, ok, Result } from 'neverthrow';
|
||||
import { FileType } from 'src/app/models/fileType';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FilePickerWebService {
|
||||
|
||||
constructor() { }
|
||||
|
||||
getFileFromDevice(types: typeof FileType[]): Promise<Result<File, any>> {
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = types.join(', ')
|
||||
|
||||
input.click();
|
||||
|
||||
return new Promise((resolve, reject)=>{
|
||||
input.onchange = async () => {
|
||||
const file = Array.from(input.files)
|
||||
resolve(ok(file[0] as File));
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { File, IWriteOptions } from '@awesome-cordova-plugins/file/ngx';
|
||||
import { err, ok, Result } from 'neverthrow';
|
||||
import { FileOpener } from '@awesome-cordova-plugins/file-opener/ngx';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FileSystemMobileService {
|
||||
|
||||
constructor(
|
||||
private file: File,
|
||||
private FileOpener: FileOpener,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Write a new file to the desired location.
|
||||
*
|
||||
* @param {string} path Base FileSystem. Please refer to the iOS and Android filesystem above
|
||||
* @param {string} fileName path relative to base path
|
||||
* @param {string | Blob | ArrayBuffer} text content, blob or ArrayBuffer to write
|
||||
* @param {IWriteOptions} whether to replace/append to an existing file. See IWriteOptions for more information.
|
||||
* @param options
|
||||
* @returns {Promise<any>} Returns a Promise that resolves to updated file entry or rejects with an error.
|
||||
*/
|
||||
async writeFile(path: string, fileName: string, context: string | Blob | ArrayBuffer, options?: IWriteOptions): Promise<Result<any, any>> {
|
||||
try {
|
||||
const result = await this.file.writeFile(path, fileName, context, { replace: true })
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
console.log('Error writing file', e)
|
||||
return err(e)
|
||||
}
|
||||
}
|
||||
|
||||
async fileOpener(filePath: string, mimetype: string) {
|
||||
try {
|
||||
const result = this.FileOpener.open(filePath, mimetype)
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
console.error('Error opening file', e)
|
||||
return err(e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||
import { HttpResult } from './type';
|
||||
import { Result } from 'neverthrow';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
|
||||
export abstract class HttpAdapter {
|
||||
abstract post<T>(url: string, body: any): Promise<Result<HttpResult<T>, HttpErrorResponse>>
|
||||
abstract get<T>(url: string, options?: Object): Promise<Result<HttpResult<T>, HttpErrorResponse>>
|
||||
abstract put<T>(url: string, body: any): Promise<Result<HttpResult<T>, HttpErrorResponse>>
|
||||
abstract patch<T>(url: string, body?: Object): Promise<Result<HttpResult<T>, HttpErrorResponse>>
|
||||
abstract delete<T>(url: string, body?: Object): Promise<Result<HttpResult<T>, HttpErrorResponse>>
|
||||
abstract listen():Observable<Result<HttpResult<any>, HttpErrorResponse>>
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { HttpAdapter } from './adapter';
|
||||
import { HttpService } from './http.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
declarations: [],
|
||||
schemas: [],
|
||||
providers: [
|
||||
{
|
||||
provide: HttpAdapter,
|
||||
useClass: HttpService, // or MockDataService
|
||||
}
|
||||
],
|
||||
entryComponents: []
|
||||
})
|
||||
export class HttpModule {}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { HttpErrorResponse, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ok, err, Result } from 'neverthrow';
|
||||
import { HttpResult } from './type';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class HttpService {
|
||||
|
||||
private responseSubject = new BehaviorSubject<Result<HttpResult<any>, HttpErrorResponse>>(null);
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
async post<T>(url: string, body: any): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
||||
try {
|
||||
const response = await this.http.post<T>(url, body, { observe: 'response' }).toPromise();
|
||||
const data = {
|
||||
data: response.body,
|
||||
status: response.status,
|
||||
headers: response.headers,
|
||||
url: response.url || url,
|
||||
method: '',
|
||||
}
|
||||
this.responseSubject.next(ok(data))
|
||||
return ok(data);
|
||||
} catch (e) {
|
||||
this.responseSubject.next(err(e))
|
||||
return err(e as HttpErrorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(url: string, options = {}): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
||||
try {
|
||||
const response = await this.http.get<T>(url, { ...options, observe: 'response' }).toPromise();
|
||||
|
||||
const data = {
|
||||
method: 'GET',
|
||||
data: response.body,
|
||||
status: response.status,
|
||||
headers: response.headers,
|
||||
url: response.url || url
|
||||
}
|
||||
|
||||
this.responseSubject.next(ok(data))
|
||||
return ok(data);
|
||||
} catch (e) {
|
||||
this.responseSubject.next(err(e))
|
||||
return err(e as HttpErrorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
async put<T>(url: string, body: any): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
||||
try {
|
||||
const response = await this.http.put<T>(url, body, { observe: 'response' }).toPromise();
|
||||
|
||||
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>> {
|
||||
try {
|
||||
const response = await this.http.patch<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 delete<T>(url: string, body = {}): Promise<Result<HttpResult<T>, HttpErrorResponse>> {
|
||||
const options = {
|
||||
body: body, // Pass payload as the body of the request
|
||||
observe: 'response' as 'body'
|
||||
};
|
||||
|
||||
try {
|
||||
const response: any = await this.http.delete<T>(url, options).toPromise();
|
||||
|
||||
const data = {
|
||||
data: response?.body,
|
||||
status: response?.status,
|
||||
headers: response?.headers,
|
||||
url: response?.url || url,
|
||||
method: '',
|
||||
}
|
||||
|
||||
this.responseSubject.next(ok(data))
|
||||
return ok(data as any);
|
||||
} catch (e) {
|
||||
this.responseSubject.next(err(e))
|
||||
return err(e as HttpErrorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
listen() {
|
||||
return this.responseSubject.asObservable()
|
||||
}
|
||||
}
|
||||
|
||||
export function isHttpResponse(data: any): data is HttpResponse<any> {
|
||||
return typeof data.status == 'number';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { Result } from 'neverthrow';
|
||||
|
||||
export interface Options {
|
||||
headers?: HttpHeaders
|
||||
params?: HttpParams
|
||||
}
|
||||
|
||||
export interface HttpResult<T> {
|
||||
data: T | null;
|
||||
status: number;
|
||||
headers: HttpHeaders;
|
||||
url: string;
|
||||
method: string
|
||||
}
|
||||
|
||||
export type IHttPReturn<T> = Promise<Result<HttpResult<T>, HttpErrorResponse>>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Result } from 'neverthrow';
|
||||
import { ZodError} from 'zod';
|
||||
import { IDBError } from './types';
|
||||
import Dexie, { EntityTable, Table } from 'Dexie';
|
||||
|
||||
// Define a type for the Result of repository operations
|
||||
export type RepositoryResult<T, E> = Result<T, IDBError<E>>;
|
||||
export abstract class IDexieRepository<T, R, I = EntityTable<any, any>> {
|
||||
|
||||
abstract createTransaction(callback: (table: I) => Promise<void>): Promise<void>
|
||||
abstract insert(document: T): Promise<RepositoryResult<number, T>>
|
||||
|
||||
abstract insertMany(documents: T[]): Promise<RepositoryResult<number[], T[]>>
|
||||
|
||||
abstract update(id: any, updatedDocument: Partial<T>) : Promise<RepositoryResult<number, T>>
|
||||
|
||||
abstract delete(id: any): Promise<RepositoryResult<void, T>>
|
||||
|
||||
abstract findById(id: any) : Promise<RepositoryResult<R, any>>
|
||||
|
||||
abstract find(filter: Partial<T>): Promise<RepositoryResult<R[], T[]>>
|
||||
|
||||
abstract findOne(filter: Partial<T>): Promise<RepositoryResult<T | undefined, T>>
|
||||
|
||||
abstract findAll(): Promise<RepositoryResult<T[], T>>
|
||||
|
||||
abstract count(filter?: Object): Promise<RepositoryResult<number, T>>
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import { Result, ok, err } from 'neverthrow';
|
||||
import Dexie, { EntityTable, Table } from 'Dexie';
|
||||
import { ZodError, ZodObject, ZodSchema } from 'zod';
|
||||
import { Logger } from 'src/app/services/logger/main/service';
|
||||
import { IDexieRepository, RepositoryResult } from '../adapter'
|
||||
import { IDBError, IDexieError } from '../types';
|
||||
|
||||
// Define a type for the Result of repository operations
|
||||
|
||||
class DBError<T> extends Error implements IDBError<T> {
|
||||
zodError?: ZodError<T>;
|
||||
parameters: T;
|
||||
error?: IDexieError;
|
||||
|
||||
constructor(data: IDBError<T>) {
|
||||
super(data.message);
|
||||
this.zodError = data.zodError;
|
||||
this.parameters = data.parameters;
|
||||
this.error = data.error;
|
||||
|
||||
Logger.error(data.message, {
|
||||
zodError: this.zodError,
|
||||
parameters: this.parameters
|
||||
})
|
||||
|
||||
// // Manually capture the stack trace if needed
|
||||
// if (Error.captureStackTrace) {
|
||||
// Error.captureStackTrace(this, DBError);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
export class DexieRepository<T, R, I = EntityTable<any, any>> implements IDexieRepository<T, R, I> {
|
||||
private table: EntityTable<any, any>;
|
||||
private ZodSchema: ZodSchema<T>
|
||||
private ZodPartialSchema: ZodSchema<T>
|
||||
private db: Dexie
|
||||
|
||||
constructor(table: EntityTable<any, any>, ZodSchema: ZodSchema, db?:Dexie) {
|
||||
this.table = table as any
|
||||
this.ZodSchema = ZodSchema
|
||||
this.ZodPartialSchema = (ZodSchema as ZodObject<any>).partial() as any;
|
||||
this.db = db
|
||||
}
|
||||
|
||||
// Method to create a transaction and use the callback
|
||||
async createTransaction(callback: (table:I) => Promise<void>): Promise<void> {
|
||||
return this.db.transaction('rw', this.table, async () => {
|
||||
try {
|
||||
// Execute the callback function
|
||||
await callback(this.table as any);
|
||||
} catch (error) {
|
||||
// Transactions are automatically rolled back on error
|
||||
throw error;
|
||||
}
|
||||
}).then(() => {
|
||||
console.log('Transaction completed successfully');
|
||||
}).catch((error) => {
|
||||
console.error('Transaction failed: ' + error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async insert(document: T): Promise<RepositoryResult<any, T>> {
|
||||
|
||||
const dataValidation = this.ZodSchema.safeParse(document)
|
||||
|
||||
if(dataValidation.success) {
|
||||
try {
|
||||
const id = await this.table.add(dataValidation.data);
|
||||
return ok(id);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error
|
||||
return err(new DBError({
|
||||
message: `dexie.js failed to insert into ${this.table.name}, ${error.message}`,
|
||||
parameters: document,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
return err(new DBError({
|
||||
message: `dexie.js failed to insert into ${this.table.name}, invalid data`,
|
||||
parameters: document,
|
||||
zodError: dataValidation.error
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async insertMany(documents: T[]): Promise<RepositoryResult<number[], T[]>> {
|
||||
// Validate each document
|
||||
const schema = this.ZodSchema.array()
|
||||
|
||||
const validationResult = schema.safeParse(documents)
|
||||
if(!validationResult.success) {
|
||||
return err(new DBError({
|
||||
message: `dexie.js failed to insert many into ${this.table.name}, invalid data`,
|
||||
parameters: documents,
|
||||
zodError: validationResult.error
|
||||
}))
|
||||
}
|
||||
|
||||
try {
|
||||
const ids = await this.table.bulkAdd(documents as any);
|
||||
return ok(ids);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error
|
||||
return err(new DBError({
|
||||
message: `dexie.js failed to insert into many ${this.table.name}, ${error.message}`,
|
||||
parameters: documents,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async update(id: any, updatedDocument: Partial<T>) {
|
||||
|
||||
const dataValidation = this.ZodPartialSchema.safeParse(updatedDocument)
|
||||
|
||||
if(dataValidation.success) {
|
||||
try {
|
||||
const updatedCount = await this.table.update(id, dataValidation.data);
|
||||
return ok(updatedCount);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error
|
||||
return err(new DBError({
|
||||
message: `dexie.js Failed to update into ${this.table.name}, ${error.message} `,
|
||||
parameters: {
|
||||
...updatedDocument,
|
||||
[this.table.schema.primKey.name]: id
|
||||
} as unknown as T,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
return err(new DBError({
|
||||
message: `dexie.js failed to update into ${this.table.name}, invalid data`,
|
||||
parameters: {
|
||||
...updatedDocument,
|
||||
[this.table.schema.primKey.name]: id
|
||||
} as unknown as T,
|
||||
zodError: dataValidation.error
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async updateMany(updatedDocument: Partial<T>[]) {
|
||||
|
||||
const schema = this.ZodSchema.array()
|
||||
const dataValidation = schema.safeParse(updatedDocument)
|
||||
|
||||
if(dataValidation.success) {
|
||||
try {
|
||||
const updatedCount = await this.table.bulkPut(dataValidation.data);
|
||||
return ok(updatedCount);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error
|
||||
return err(new DBError({
|
||||
message: `dexie.js Failed to update into ${this.table.name}, ${error.message} `,
|
||||
parameters: document,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
return err(new DBError({
|
||||
message: `dexie.js failed to update many into ${this.table.name}, invalid data`,
|
||||
parameters: updatedDocument ,
|
||||
zodError: dataValidation.error
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async delete(id: any): Promise<RepositoryResult<void, T>> {
|
||||
try {
|
||||
await this.table.delete(id);
|
||||
return ok(undefined);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error
|
||||
return err(new DBError({
|
||||
message: `dexie.js Failed to delete into ${this.table.name}, ${error.message} `,
|
||||
parameters: id,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: any) {
|
||||
try {
|
||||
const document = await this.table.get(id);
|
||||
return ok(document);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error
|
||||
return err(new DBError({
|
||||
message: `dexie.js Failed to delete into ${this.table.name}, ${error.message} `,
|
||||
parameters: id,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async find(filter: Partial<T>): Promise<RepositoryResult<R[], T[]>> {
|
||||
try {
|
||||
const documents: any = await this.table.where(filter).toArray();
|
||||
return ok(documents);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error;
|
||||
return err(new DBError({
|
||||
message: `dexie.js Failed to find into ${this.table.name}, ${error.message} `,
|
||||
parameters: filter as any,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async findOne(filter: Partial<T>): Promise<RepositoryResult<T | undefined, T>> {
|
||||
try {
|
||||
const document = await this.table.where(filter).first();
|
||||
return ok(document);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error
|
||||
return err(new DBError({
|
||||
message: `dexie.js Failed to findOne into ${this.table.name}, ${error.message} `,
|
||||
parameters: filter as any,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(): Promise<RepositoryResult<T[], T>> {
|
||||
try {
|
||||
const documents = await this.table.toArray()
|
||||
return ok(documents);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error
|
||||
return err(new DBError({
|
||||
message: `dexie.js Failed to findAll into ${this.table.name}, ${error.message} `,
|
||||
parameters: null,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async count(filter?: Object): Promise<RepositoryResult<number, T>> {
|
||||
try {
|
||||
const count = filter ? await this.table.where(filter).count() : await this.table.count();
|
||||
return ok(count);
|
||||
} catch (_error) {
|
||||
const error: IDexieError = _error
|
||||
return err(new DBError({
|
||||
message: `dexie.js Failed to count into ${this.table.name}, ${error.message}`,
|
||||
parameters: filter as any,
|
||||
error: error
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './adapter';
|
||||
// export * from './types';
|
||||
@@ -0,0 +1,56 @@
|
||||
// export type UpdatedModel = {
|
||||
// matchedCount: number;
|
||||
// modifiedCount: number;
|
||||
// acknowledged: boolean;
|
||||
// upsertedId: unknown | any;
|
||||
// upsertedCount: number;
|
||||
// };
|
||||
|
||||
import { ZodError } from "zod"
|
||||
|
||||
// export type RemovedModel = {
|
||||
// deletedCount: number;
|
||||
// deleted: boolean;
|
||||
// };
|
||||
|
||||
// export type CreatedModel = {
|
||||
// id: string;
|
||||
// created: boolean;
|
||||
// };
|
||||
|
||||
// export type CreatedOrUpdateModel = {
|
||||
// id: string;
|
||||
// created: boolean;
|
||||
// updated: boolean;
|
||||
// };
|
||||
|
||||
// export enum DatabaseOperationEnum {
|
||||
// EQUAL = 'equal',
|
||||
// NOT_EQUAL = 'not_equal',
|
||||
// NOT_CONTAINS = 'not_contains',
|
||||
// CONTAINS = 'contains'
|
||||
// }
|
||||
|
||||
// export type DatabaseOperationCommand<T> = {
|
||||
// property: keyof T;
|
||||
// value: unknown[];
|
||||
// command: DatabaseOperationEnum;
|
||||
// };
|
||||
|
||||
export type IDBError<T> = {
|
||||
message: string,
|
||||
zodError?: ZodError<T>,
|
||||
parameters?: T,
|
||||
error?: IDexieError
|
||||
}
|
||||
|
||||
export interface IDexieError extends Error {
|
||||
name: string; // The name of the error, e.g., 'NotFoundError', 'ConstraintError'
|
||||
message: string; // The error message
|
||||
stack?: string; // Optional stack trace
|
||||
inner?: Error; // Some Dexie errors have an inner error
|
||||
dbName?: string; // The name of the Dexie database where the error occurred
|
||||
tableName?: string; // The name of the table where the error occurred
|
||||
operation?: string; // The operation being performed (e.g., 'put', 'get', 'delete')
|
||||
key?: any; // The key involved in the operation, if applicable
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
import { Result } from 'neverthrow';
|
||||
import { HubConnection } from '@microsoft/signalr';
|
||||
import { ISignalRInput } from './type';
|
||||
|
||||
export abstract class ISignalRService {
|
||||
abstract establishConnection(): Promise<Result<HubConnection, false>>;
|
||||
abstract sendData<T>(input: ISignalRInput): Promise<void>;
|
||||
abstract join(): void;
|
||||
abstract getData<T>(): Observable<{ method: string; data: T }>;
|
||||
abstract getConnectionState(): Observable<boolean>;
|
||||
abstract onReconnect(): Observable<boolean>;
|
||||
abstract newConnection(): void;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, Subject, timer } from 'rxjs';
|
||||
import { Platform } from '@ionic/angular';
|
||||
import { SignalRConnection, SocketMessage } from './signalR';
|
||||
import { Plugins } from '@capacitor/core';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { Result } from 'neverthrow';
|
||||
import { HubConnection } from '@microsoft/signalr';
|
||||
import { ISignalRInput } from '../type';
|
||||
|
||||
const { App } = Plugins;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SignalRService {
|
||||
private connection!: SignalRConnection;
|
||||
private connectingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(null);
|
||||
private sendDataSubject: BehaviorSubject<{method: string, data: any}> = new BehaviorSubject<{method: string, data: any}>(null);
|
||||
|
||||
private deadConnectionBackGround: Subject<any>;
|
||||
|
||||
constructor(private platform: Platform) {
|
||||
|
||||
this.deadConnectionBackGround = new Subject()
|
||||
this.deadConnectionBackGround.pipe(
|
||||
switchMap(() => timer(150000)), // 2 minutes 30 seconds
|
||||
).subscribe(() => {
|
||||
console.log('trigger new connections')
|
||||
this.newConnection()
|
||||
})
|
||||
|
||||
try {
|
||||
if (!this.platform.is('desktop')) {
|
||||
App.addListener('appStateChange', ({ isActive }) => {
|
||||
if (isActive) {
|
||||
// The app is in the foreground.
|
||||
// console.log('App is in the foreground');
|
||||
this.deadConnectionBackGround.next()
|
||||
|
||||
}
|
||||
});
|
||||
} else {
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
this.deadConnectionBackGround.next()
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch(error) {}
|
||||
}
|
||||
|
||||
async establishConnection(): Promise<Result<HubConnection, false>> {
|
||||
|
||||
// const connection = new SignalRConnection({url:'https://41e3-41-63-166-54.ngrok-free.app/api/v2/chathub'})
|
||||
const connection = new SignalRConnection({url:'https://gdapi-dev.dyndns.info/stage/api/v2/chathub'})
|
||||
const attempConnection = await connection.establishConnection()
|
||||
|
||||
if(attempConnection.isOk()) {
|
||||
console.log('connect')
|
||||
this.connection?.closeConnection()
|
||||
this.connection = connection
|
||||
|
||||
this.connection.getData().subscribe((data) => {
|
||||
this.sendDataSubject.next(data)
|
||||
this.deadConnectionBackGround.next()
|
||||
})
|
||||
|
||||
this.connection.getConnectionState().subscribe((data) => {
|
||||
this.connectingSubject.next(data)
|
||||
})
|
||||
|
||||
return attempConnection
|
||||
} else {
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(this.establishConnection())
|
||||
}, 2000)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sendData<T>(input: ISignalRInput) {
|
||||
return this.connection.sendData<T>(input)
|
||||
}
|
||||
|
||||
join() {
|
||||
return this.connection.join()
|
||||
}
|
||||
|
||||
getData<T>() {
|
||||
return this.sendDataSubject.asObservable() as BehaviorSubject<{method: string, data: T}>
|
||||
}
|
||||
|
||||
public getConnectionState(): Observable<boolean> {
|
||||
return this.connectingSubject.asObservable();
|
||||
}
|
||||
|
||||
newConnection() {
|
||||
this.establishConnection()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
import * as signalR from '@microsoft/signalr';
|
||||
import { BehaviorSubject, Observable, race, timer } from 'rxjs';
|
||||
import { ok, Result, err } from 'neverthrow';
|
||||
import { SessionStore } from 'src/app/store/session.service';
|
||||
import { filter, first } from 'rxjs/operators';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ISignalRInput } from '../type';
|
||||
import { MessageOutPutDataDTO } from 'src/app/core/chat/repository/dto/messageOutputDTO';
|
||||
|
||||
export interface SocketMessage<T> {
|
||||
method: string,
|
||||
data: T
|
||||
}
|
||||
|
||||
export enum EnumSocketError {
|
||||
catch = 1,
|
||||
close
|
||||
}
|
||||
|
||||
export class SignalRConnection {
|
||||
|
||||
private hubConnection: signalR.HubConnection;
|
||||
private connectionStateSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
private disconnectSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
private reconnectSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
private sendLaterSubject: BehaviorSubject<Object> = new BehaviorSubject<Object>(false);
|
||||
private reconnect = true
|
||||
|
||||
private sendDataSubject: BehaviorSubject<{method: string, data: any}> = new BehaviorSubject<{method: string, data: any}>(null);
|
||||
private pendingRequests: Map<string, { resolve: Function; reject: Function }> = new Map();
|
||||
url: string
|
||||
private hasConnectOnce = false
|
||||
|
||||
constructor({url}) {
|
||||
this.url = url
|
||||
}
|
||||
|
||||
establishConnection(): Promise<Result<signalR.HubConnection, false>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const hubConnection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(this.url)
|
||||
.build();
|
||||
|
||||
this.hubConnection = hubConnection
|
||||
|
||||
hubConnection
|
||||
.start()
|
||||
.then(() => {
|
||||
this.hubConnection = hubConnection
|
||||
this.hasConnectOnce = true
|
||||
console.log('Connection started');
|
||||
this.connectionStateSubject.next(true);
|
||||
this.join()
|
||||
this.addMessageListener()
|
||||
resolve(ok(hubConnection))
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Error while starting connection: ' + error);
|
||||
if(this.hasConnectOnce) {
|
||||
setTimeout(()=> {
|
||||
resolve(this.attempReconnect());
|
||||
}, 2000)
|
||||
} else {
|
||||
resolve(err(false))
|
||||
}
|
||||
});
|
||||
|
||||
hubConnection.onclose(() => {
|
||||
console.log('Connection closed');
|
||||
this.connectionStateSubject.next(false);
|
||||
this.disconnectSubject.next(true)
|
||||
|
||||
this.pendingRequests.forEach((_, requestId) => {
|
||||
const data = this.pendingRequests.get(requestId);
|
||||
|
||||
if(data) {
|
||||
const { reject } = data
|
||||
reject(err({
|
||||
type: EnumSocketError.close
|
||||
}));
|
||||
this.pendingRequests.delete(requestId);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if(this.reconnect) {
|
||||
resolve(this.attempReconnect());
|
||||
} else {
|
||||
resolve(err(false))
|
||||
}
|
||||
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async attempReconnect() {
|
||||
const attempConnection = await this.establishConnection()
|
||||
|
||||
if(attempConnection.isOk()) {
|
||||
this.reconnectSubject.next(true)
|
||||
}
|
||||
|
||||
return attempConnection
|
||||
}
|
||||
|
||||
public join() {
|
||||
if(this.connectionStateSubject.value == true) {
|
||||
this.hubConnection.invoke("Join", SessionStore.user.UserId, SessionStore.user.FullName);
|
||||
//this.hubConnection.invoke("Join", 105, "UserFirefox");
|
||||
} else {
|
||||
this.sendLaterSubject.next({method: 'SendMessage', args:["Join", 312, "Daniel"]})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
sendData<T>(input: ISignalRInput): Promise<Result<T, any>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if(this.connectionStateSubject.value == true) {
|
||||
try {
|
||||
this.pendingRequests.set(input.data.requestId, { resolve, reject: (data: any) => resolve(data) });
|
||||
|
||||
this.hubConnection.invoke(input.method, input.data)
|
||||
|
||||
this.sendDataSubject.pipe(
|
||||
filter((message) => {
|
||||
return input.data.requestId == message?.data.requestId ||
|
||||
input?.data?.roomName == message?.data.roomName && typeof input?.data?.roomName == 'string'
|
||||
|
||||
}),
|
||||
).subscribe(value => {
|
||||
resolve(ok(value.data as unknown as T))
|
||||
// console.log('Received valid value:', value);
|
||||
});
|
||||
|
||||
} catch(error) {
|
||||
resolve(err({
|
||||
type: EnumSocketError.catch
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
this.sendLaterSubject.next({method: 'SendMessage', args: input})
|
||||
return reject(err(false))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
private addMessageListener(): void {
|
||||
|
||||
const methods = ['ReceiveMessage', 'TypingMessage', 'AvailableUsers',
|
||||
'ReadAt', 'DeleteMessage', 'UpdateMessage', 'GroupAddedMembers',
|
||||
'GroupDeletedMembers', 'UserAddGroup']
|
||||
|
||||
for(const method of methods) {
|
||||
this.hubConnection.on(method, (message: any) => {
|
||||
this.sendDataSubject.next({
|
||||
method: method,
|
||||
data: message
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getConnectionState(): Observable<boolean> {
|
||||
return this.connectionStateSubject.asObservable();
|
||||
}
|
||||
|
||||
public getDisconnectTrigger(): Observable<boolean> {
|
||||
return this.disconnectSubject.asObservable();
|
||||
}
|
||||
|
||||
public getData() {
|
||||
return this.sendDataSubject.asObservable()
|
||||
}
|
||||
|
||||
public closeConnection(): void {
|
||||
this.reconnect = false
|
||||
if (this.hubConnection) {
|
||||
this.hubConnection
|
||||
.stop()
|
||||
.then(() => {
|
||||
console.log('Connection closed by user');
|
||||
this.connectionStateSubject.next(false);
|
||||
this.pendingRequests.forEach((_, requestId) => {
|
||||
const data = this.pendingRequests.get(requestId);
|
||||
|
||||
if(data) {
|
||||
const { reject } = data
|
||||
reject(err({
|
||||
type: EnumSocketError.close
|
||||
}));
|
||||
this.pendingRequests.delete(requestId);
|
||||
}
|
||||
|
||||
});
|
||||
})
|
||||
.catch(err => console.log('Error while closing connection: ' + err));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SignalR Client</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SignalR Client</h1>
|
||||
<div id="messages"></div>
|
||||
<input id="chatbox">
|
||||
<script>
|
||||
var msgObj = {
|
||||
roomId: "882fcb86-4028-4242-bb47-fca0170dcc65",
|
||||
senderId:105,
|
||||
message:"message enviada",
|
||||
messageType:1,
|
||||
canEdit:true,
|
||||
oneShot:false,
|
||||
};
|
||||
|
||||
const connection = new signalR.HubConnectionBuilder()
|
||||
.withAutomaticReconnect()
|
||||
.withUrl("https://gdapi-dev.dyndns.info/stage/api/v2/chathub")
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
connection.start().then(function () {
|
||||
connection.invoke("Join", 105, "UserFirefox");
|
||||
document.getElementById("chatbox").addEventListener("keyup", function (event) {
|
||||
if (event.key === "Enter") {
|
||||
msgObj.Message = chatbox.value;
|
||||
connection.invoke("SendMessage", msgObj);
|
||||
event.target.value = "";
|
||||
}
|
||||
});
|
||||
}).catch(function (err) {
|
||||
return console.error(err.toString());
|
||||
});
|
||||
|
||||
connection.on("ReceiveMessage", function (message) {
|
||||
console.log(message);
|
||||
const messages = document.getElementById("messages");
|
||||
messages.innerHTML += `<p>${message.message}</p>`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,129 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject, BehaviorSubject } from 'rxjs';
|
||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||
import { catchError, retryWhen, tap, delay } from 'rxjs/operators';
|
||||
import { SessionStore } from 'src/app/store/session.service';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
|
||||
export interface WebSocketMessage {
|
||||
type: string;
|
||||
payload: any;
|
||||
requestId?: string;
|
||||
}
|
||||
|
||||
interface WebSocketError {
|
||||
type: string;
|
||||
error: any;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebSocketService {
|
||||
private socket$: WebSocketSubject<WebSocketMessage>;
|
||||
private messageSubject$: Subject<WebSocketMessage>;
|
||||
private connectionStatus$: BehaviorSubject<boolean>;
|
||||
private reconnectAttempts = 0;
|
||||
private readonly maxReconnectAttempts = 5;
|
||||
|
||||
callback: {[key: string]: Function} = {}
|
||||
|
||||
constructor() {
|
||||
this.messageSubject$ = new Subject<WebSocketMessage>();
|
||||
this.connectionStatus$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
// this.connect('https://5-180-182-151.cloud-xip.com:85/ws/')
|
||||
|
||||
// this.messages$.subscribe(({payload, requestId, type}) => {
|
||||
// if(this.callback[requestId]) {
|
||||
// this.callback[requestId]({payload, requestId, type})
|
||||
// delete this.callback[requestId]
|
||||
|
||||
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
public connect(url: string) {
|
||||
this.socket$ = webSocket<WebSocketMessage>(url);
|
||||
|
||||
this.socket$.pipe(
|
||||
tap({
|
||||
|
||||
error: () => {
|
||||
this.connectionStatus$.next(false);
|
||||
}
|
||||
}),
|
||||
retryWhen(errors => errors.pipe(
|
||||
tap(() => {
|
||||
this.reconnectAttempts++;
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
throw new Error('Max reconnect attempts reached');
|
||||
}
|
||||
}),
|
||||
delay(1000)
|
||||
))
|
||||
).subscribe(
|
||||
(message) => {
|
||||
this.messageSubject$.next(message);
|
||||
|
||||
if (!this.connectionStatus$.getValue()) {
|
||||
this.connectionStatus$.next(true);
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// Send a message when the connection is established
|
||||
this.sendMessage(SessionStore.user.UserId as any).subscribe();
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
console.error('WebSocket connection error:', err);
|
||||
},
|
||||
() => {
|
||||
console.log('WebSocket connection closed');
|
||||
this.connectionStatus$.next(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public sendMessage(message: WebSocketMessage): Observable<any> {
|
||||
return new Observable<void>(observer => {
|
||||
|
||||
|
||||
|
||||
if(typeof message == 'object') {
|
||||
message.requestId = uuidv4()
|
||||
this.socket$.next(message);
|
||||
|
||||
this.callback[message.requestId] = ({payload, requestId})=> {
|
||||
observer.next(payload as any);
|
||||
observer.complete();
|
||||
}
|
||||
|
||||
} else {
|
||||
this.socket$.next(message);
|
||||
observer.next({} as any);
|
||||
observer.complete();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}).pipe(
|
||||
catchError(err => {
|
||||
console.error('Send message error:', err);
|
||||
return new Observable<never>(observer => {
|
||||
observer.error({ type: 'SEND_ERROR', error: err });
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public get messages$(): Observable<WebSocketMessage> {
|
||||
return this.messageSubject$.asObservable();
|
||||
}
|
||||
|
||||
public get connectionStatus(): Observable<boolean> {
|
||||
return this.connectionStatus$.asObservable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const SignalRInputSchema = z.object({
|
||||
method: z.string(),
|
||||
data: z.object({
|
||||
requestId: z.string(),
|
||||
}).catchall(z.unknown()), // Allows any additional properties with unknown values
|
||||
})
|
||||
|
||||
export type ISignalRInput = z.infer<typeof SignalRInputSchema>;
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericResponse, RecordingData, VoiceRecorder } from 'capacitor-voice-recorder';
|
||||
import { err, ok, Result } from 'neverthrow';
|
||||
|
||||
export enum StartRecordingResultError {
|
||||
NoSpeaker,
|
||||
NeedPermission,
|
||||
alreadyRecording
|
||||
}
|
||||
|
||||
export enum StopRecordingResultError {
|
||||
haventStartYet,
|
||||
NoValue,
|
||||
UnknownError
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SpeakerService {
|
||||
|
||||
|
||||
recording = false;
|
||||
|
||||
constructor() { }
|
||||
|
||||
|
||||
async startRecording(): Promise<Result<true, StartRecordingResultError>> {
|
||||
// Request audio recording permission
|
||||
await VoiceRecorder.requestAudioRecordingPermission();
|
||||
|
||||
// Check if the device can record audio
|
||||
const canRecord = await VoiceRecorder.canDeviceVoiceRecord();
|
||||
if (!canRecord.value) {
|
||||
return err(StartRecordingResultError.NoSpeaker);
|
||||
}
|
||||
|
||||
// Check if the app has permission to record audio
|
||||
const hasPermission = await VoiceRecorder.requestAudioRecordingPermission();
|
||||
if (!hasPermission.value) {
|
||||
return err(StartRecordingResultError.NeedPermission);
|
||||
}
|
||||
|
||||
// Check if recording is already in progress
|
||||
if (this.recording) {
|
||||
return err(StartRecordingResultError.alreadyRecording)
|
||||
}
|
||||
|
||||
// Start recording
|
||||
this.recording = true;
|
||||
VoiceRecorder.startRecording();
|
||||
return ok(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Example of a poorly structured startRecording method (commented out).
|
||||
*
|
||||
* - This example demonstrates improper chaining of promises and lack of proper error handling.
|
||||
* - Avoid using this approach for better readability and maintainability.
|
||||
*/
|
||||
// bad code example
|
||||
// async startRecording() {
|
||||
// VoiceRecorder.requestAudioRecordingPermission();
|
||||
// if (await VoiceRecorder.canDeviceVoiceRecord().then((result: GenericResponse) => { return result.value })) {
|
||||
// if (await VoiceRecorder.requestAudioRecordingPermission().then((result: GenericResponse) => { return result.value })) {
|
||||
// //if(await this.hasAudioRecordingPermission()){
|
||||
// if (this.recording) {
|
||||
// return;
|
||||
// }
|
||||
// this.recording = true;
|
||||
// VoiceRecorder.startRecording()
|
||||
// //}
|
||||
// }
|
||||
// else {
|
||||
// return err('need Permission');
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// return err('no speaker');
|
||||
// }
|
||||
// }
|
||||
|
||||
async stopRecording(): Promise<Result<RecordingData, StopRecordingResultError>> {
|
||||
if (!this.recording) {
|
||||
return err(StopRecordingResultError.haventStartYet);
|
||||
}
|
||||
|
||||
this.recording = false;
|
||||
|
||||
try {
|
||||
const result = await VoiceRecorder.stopRecording();
|
||||
|
||||
if (result.value && result.value.recordDataBase64) {
|
||||
const recordData = result.value.recordDataBase64;
|
||||
return ok(result);
|
||||
} else {
|
||||
return err(StopRecordingResultError.NoValue);
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle any unexpected errors that might occur during stopRecording
|
||||
return err(StopRecordingResultError.UnknownError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export enum StartRecordingResultError {
|
||||
NoSpeaker,
|
||||
NeedPermission,
|
||||
alreadyRecording
|
||||
}
|
||||
|
||||
export enum StopRecordingResultError {
|
||||
haventStartYet,
|
||||
NoValue,
|
||||
UnknownError
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import { Router } from '@angular/router';
|
||||
import { SessionStore } from '../store/session.service';
|
||||
import { environment } from "src/environments/environment";
|
||||
import { PermissionService } from '../services/permission.service';
|
||||
import { NetworkServiceService } from 'src/app/services/network-service.service';
|
||||
import { RochetChatConnectorService } from 'src/app/services/chat/rochet-chat-connector.service';
|
||||
import { NetworkServiceService, ConnectionStatus } from 'src/app/services/network-service.service';
|
||||
// import { RochetChatConnectorService } from 'src/app/services/chat/rochet-chat-connector.service';
|
||||
|
||||
@Injectable()
|
||||
export class ChatTokenInterceptor implements HttpInterceptor {
|
||||
@@ -29,8 +29,7 @@ export class ChatTokenInterceptor implements HttpInterceptor {
|
||||
|
||||
private excludedDomains = ['Login',environment.apiURL, 'http://localhost:8019'];// Add other domains as needed
|
||||
|
||||
constructor(private http: HttpClient, private router: Router, private p: PermissionService, private NetworkServiceService: NetworkServiceService,
|
||||
private RochetChatConnectorService: RochetChatConnectorService) { }
|
||||
constructor(private http: HttpClient, private router: Router, private p: PermissionService, private NetworkServiceService: NetworkServiceService) { }
|
||||
|
||||
intercept(
|
||||
request: HttpRequest<any>,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { SearchList } from 'src/app/models/search-document';
|
||||
import { NewEventPage } from 'src/app/pages/agenda/new-event/new-event.page';
|
||||
import { SearchPage } from 'src/app/pages/search/search.page';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
import { ChatService } from 'src/app/services/chat.service';
|
||||
import { FileLoaderService } from 'src/app/services/file/file-loader.service';
|
||||
import { FileToBase64Service } from 'src/app/services/file/file-to-base64.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
@@ -33,7 +32,6 @@ export class ChatOptionsFeaturesPage implements OnInit {
|
||||
private navParams: NavParams,
|
||||
private fileLoaderService: FileLoaderService,
|
||||
private fileToBase64Service: FileToBase64Service,
|
||||
private chatService: ChatService,
|
||||
public ThemeService: ThemeService
|
||||
) {
|
||||
|
||||
@@ -121,9 +119,7 @@ export class ChatOptionsFeaturesPage implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
this.chatService.sendMessage(body).subscribe(res=> {
|
||||
//
|
||||
});
|
||||
|
||||
//
|
||||
};
|
||||
}
|
||||
@@ -145,10 +141,7 @@ export class ChatOptionsFeaturesPage implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
this.chatService.sendMessage(body).subscribe(res=> {
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/* getGroupContacts(room:any){
|
||||
|
||||
@@ -168,7 +168,7 @@ export class EditProfilePage implements OnInit {
|
||||
@XTracerAsync({name:'edit-profile/takePicture', bugPrint: true})
|
||||
async uploadPicture(source: CameraSource, tracing?: TracingType) {
|
||||
|
||||
const capturedImage = await this.CameraService.takePicture({
|
||||
const capturedImage = await this.CameraService.takePictureAgenda({
|
||||
width: 250,
|
||||
height: 250,
|
||||
quality: 100,
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ModalController, NavParams } from '@ionic/angular';
|
||||
import { ChatSystemService } from 'src/app/services/chat/chat-system.service';
|
||||
import { ThemeService } from 'src/app/services/theme.service'
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-set-room-owner',
|
||||
templateUrl: './set-room-owner.page.html',
|
||||
styleUrls: ['./set-room-owner.page.scss'],
|
||||
})
|
||||
export class SetRoomOwnerPage implements OnInit {
|
||||
|
||||
textSearch:string = "";
|
||||
roomId:any;
|
||||
members:any;
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
private navParams: NavParams,
|
||||
public ThemeService: ThemeService,
|
||||
private toastService: ToastService,
|
||||
public ChatSystemService: ChatSystemService,
|
||||
) {
|
||||
this.roomId = this.navParams.get('roomId');
|
||||
this.members = this.navParams.get('members');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// this.chatService.refreshtoken();
|
||||
}
|
||||
|
||||
async close(){
|
||||
this.modalController.dismiss();
|
||||
}
|
||||
|
||||
onChange(event){
|
||||
this.textSearch = event.detail.value;
|
||||
}
|
||||
|
||||
separateLetter(record, recordIndex, records){
|
||||
if(recordIndex == 0){
|
||||
return record.name[0];
|
||||
}
|
||||
|
||||
let first_prev = records[recordIndex - 1].name[0];
|
||||
let first_current = record.name[0];
|
||||
|
||||
if(first_prev != first_current){
|
||||
return first_current;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async setRoomOwner(user:any){
|
||||
|
||||
let res:any;
|
||||
try {
|
||||
res = await this.ChatSystemService.addRoomOwner(this.roomId, user._id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
|
||||
if(res.error){
|
||||
if(res.error.error == 'error-user-already-owner'){
|
||||
this.toastService._badRequest('Este utilizador já é administrador');
|
||||
}
|
||||
else{
|
||||
this.toastService._badRequest('Não foi possível completar a ação, por favor tente novamente.');
|
||||
}
|
||||
}
|
||||
else{
|
||||
this.modalController.dismiss('success');
|
||||
}
|
||||
|
||||
/*
|
||||
let body = {
|
||||
"roomId": this.roomId,
|
||||
"userId": user._id
|
||||
}
|
||||
|
||||
this.chatService.addGroupOwner(body).subscribe((res)=>{
|
||||
this.close();
|
||||
this.toastService._successMessage('Operação realizada com sucesso');
|
||||
}, (e) => {
|
||||
if(e.error.errorType == 'error-user-already-owner'){
|
||||
this.toastService._badRequest('Este utilizador já é administrador');
|
||||
}
|
||||
else{
|
||||
this.toastService._badRequest('Não foi possível completar a ação, por favor tente novamente.');
|
||||
}
|
||||
}); */
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { AES, SHA1, enc } from "crypto-js";
|
||||
import { environment } from 'src/environments/environment'
|
||||
|
||||
|
||||
function prefix() {
|
||||
return environment.version.lastCommitNumber + environment.id+"-";
|
||||
}
|
||||
|
||||
export function GET({key, instance}) {
|
||||
if(environment.storageProduction) {
|
||||
|
||||
try {
|
||||
const newKey = prefix() + SHA1(key).toString()
|
||||
const cipherText = localStorage.getItem(newKey)
|
||||
const bytes = AES.decrypt(cipherText, newKey)
|
||||
var decryptedData = bytes.toString(enc.Utf8);
|
||||
const restoredData = JSON.parse(decryptedData)
|
||||
|
||||
Object.assign(instance, restoredData);
|
||||
|
||||
return restoredData
|
||||
|
||||
} catch(error) {
|
||||
console.log(error)
|
||||
return {}
|
||||
}
|
||||
|
||||
} else {
|
||||
const newKey = prefix() + key
|
||||
const restoredData = JSON.parse(localStorage.getItem(newKey))
|
||||
Object.assign(instance, restoredData);
|
||||
|
||||
return restoredData
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function SAVE({key, instance, dataToSave}) {
|
||||
if(environment.storageProduction) {
|
||||
const newKey = prefix() + SHA1(key).toString()
|
||||
const stringifyData = JSON.stringify(dataToSave)
|
||||
|
||||
const cipherText = AES.encrypt(stringifyData, newKey).toString();
|
||||
|
||||
localStorage.setItem(newKey, cipherText)
|
||||
|
||||
} else {
|
||||
const stringifyData = JSON.stringify(dataToSave)
|
||||
const newKey = prefix() + key
|
||||
|
||||
localStorage.setItem(newKey, stringifyData)
|
||||
}
|
||||
}
|
||||
|
||||
export function DELETE({key, instance}) {
|
||||
if(environment.storageProduction) {
|
||||
const newKey = prefix() + SHA1(key).toString()
|
||||
localStorage.removeItem(newKey)
|
||||
|
||||
} else {
|
||||
const newKey = prefix() + key
|
||||
localStorage.removeItem(newKey)
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
//import { models } from 'beast-orm'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { models } from 'src/plugin/src'
|
||||
const { ArrayField, JsonField} = models.indexedDB.fields
|
||||
|
||||
export class MessageModel extends models.Model {
|
||||
channels = ArrayField()
|
||||
mentions = ArrayField()
|
||||
msg = models.CharField()
|
||||
sendAttempt = models.IntegerField()
|
||||
rid = models.CharField()
|
||||
t = models.CharField({default:'', blank: true})
|
||||
ts = JsonField({blank:true})
|
||||
u = JsonField()
|
||||
_id = models.CharField({blank:true})
|
||||
origin = models.CharField({blank:true})
|
||||
_updatedAt = models.IntegerField()
|
||||
messageSend = models.BooleanField()
|
||||
offline = models.BooleanField()
|
||||
hasFile = models.BooleanField({blank:true})
|
||||
viewed = ArrayField({blank:true})
|
||||
received = ArrayField({blank:true})
|
||||
localReference = models.CharField({blank:true, unique: true})
|
||||
attachments = ArrayField({blank:true})
|
||||
file = JsonField({blank:true})
|
||||
UploadAttachmentsTemp = models.IntegerField()
|
||||
|
||||
async getAttachments() {
|
||||
console.log('this[id]', this['id'])
|
||||
const _attachments = await attachments.filter({messageId: this['id']}).execute()
|
||||
return _attachments[0]
|
||||
}
|
||||
}
|
||||
|
||||
export class attachments extends models.Model {
|
||||
messageId = models.IntegerField()
|
||||
attachments = ArrayField({blank:true})
|
||||
file = JsonField({blank:true})
|
||||
}
|
||||
|
||||
export class DeleteMessageModel extends models.Model {
|
||||
messageId = models.CharField()
|
||||
rid = models.CharField()
|
||||
u = JsonField()
|
||||
needToReceiveBy = ArrayField()
|
||||
}
|
||||
|
||||
models.register({
|
||||
databaseName: 'chat-storage'+environment.version.lastCommitNumber + environment.id + Number(environment.storageProduction),
|
||||
type: 'indexedDB',
|
||||
version: 11,
|
||||
models: [MessageModel, DeleteMessageModel, attachments]
|
||||
})
|
||||
|
||||
|
||||
|
||||
// // acçoes
|
||||
export class ActionModel extends models.Model{
|
||||
static $tableName = 'ActionModel11'
|
||||
|
||||
ProcessId = models.IntegerField({unique: true})
|
||||
Description = models.CharField()
|
||||
Detail = models.CharField()
|
||||
DateBegin = models.CharField()
|
||||
DateEnd = models.CharField()
|
||||
ActionType = models.CharField()
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class PublicationFolderModel extends models.Model{
|
||||
|
||||
DateBegin = models.CharField()
|
||||
Description = models.CharField()
|
||||
Detail = models.CharField()
|
||||
}
|
||||
|
||||
export class PublicationDetailsModel extends models.Model{
|
||||
|
||||
DateIndex = models.CharField()
|
||||
DocumentId = models.IntegerField({unique: true})
|
||||
ProcessId = models.CharField()
|
||||
Title = models.CharField()
|
||||
Message = models.CharField()
|
||||
DatePublication = models.CharField()
|
||||
OriginalFileName = models.CharField()
|
||||
FileBase64 = models.CharField()
|
||||
FileExtension = models.CharField()
|
||||
OrganicEntityId = models.IntegerField()
|
||||
}
|
||||
|
||||
|
||||
export class WebtrixUserModel extends models.Model {
|
||||
|
||||
FullName = models.CharField()
|
||||
Role = models.CharField()
|
||||
}
|
||||
|
||||
models.register({
|
||||
databaseName: 'webtrix'+environment.version.lastCommitNumber + environment.id + Number(environment.storageProduction),
|
||||
type: 'indexedDB',
|
||||
version: 1,
|
||||
models: [WebtrixUserModel]
|
||||
})
|
||||
|
||||
|
||||
models.register({
|
||||
databaseName: 'actions'+environment.version.lastCommitNumber + environment.id + Number(environment.storageProduction),
|
||||
type: 'indexedDB',
|
||||
version: 14,
|
||||
models: [PublicationDetailsModel, ActionModel, PublicationFolderModel]
|
||||
})
|
||||
|
||||
|
||||
|
||||
window["MessageModel"] = MessageModel
|
||||
@@ -1,390 +0,0 @@
|
||||
interface Ts {
|
||||
$date: any;
|
||||
}
|
||||
|
||||
interface U {
|
||||
_id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UpdatedAt {
|
||||
$date: any;
|
||||
}
|
||||
|
||||
interface Attachment {
|
||||
ts: Date;
|
||||
title_link_download: boolean;
|
||||
}
|
||||
|
||||
export interface File {
|
||||
type: string;
|
||||
guid: string;
|
||||
image_url: string;
|
||||
subject: string;
|
||||
start_date?: Date;
|
||||
end_date?: Date;
|
||||
venue: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface EditedAt {
|
||||
$date: number;
|
||||
}
|
||||
|
||||
interface EditedBy {
|
||||
_id: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface Ts2 {
|
||||
$date: number;
|
||||
}
|
||||
|
||||
interface U2 {
|
||||
_id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UpdatedAt2 {
|
||||
$date: number;
|
||||
}
|
||||
|
||||
interface FirstUnread {
|
||||
_id: string;
|
||||
rid: string;
|
||||
msg: string;
|
||||
ts: Ts2;
|
||||
u: U2;
|
||||
_updatedAt: string;
|
||||
mentions: any[];
|
||||
channels: any[];
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
customFields:any;
|
||||
_id: string;
|
||||
id: null | string;
|
||||
rid: string;
|
||||
msg: string;
|
||||
ts: Ts;
|
||||
sendAttempt?: number
|
||||
u: U;
|
||||
from: 'Offline'|'History'|'stream'| 'send'
|
||||
t: string;
|
||||
origin: 'history' | 'stream' | 'local'
|
||||
_updatedAt: '';
|
||||
mentions: any[];
|
||||
channels: any[];
|
||||
attachments: Attachment[];
|
||||
file: File;
|
||||
editedAt: EditedAt;
|
||||
editedBy: EditedBy;
|
||||
urls: any[];
|
||||
temporaryData: object
|
||||
localReference?: string,
|
||||
viewed: string[],
|
||||
received: string[],
|
||||
delate: boolean,
|
||||
delateRequest: boolean
|
||||
attachmentsModelData: {
|
||||
fileBase64: string
|
||||
}
|
||||
hasFile: boolean
|
||||
UploadAttachmentsTemp: number
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export interface Lm {
|
||||
$date: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export interface LastMessage {
|
||||
_id: string;
|
||||
rid: string;
|
||||
msg: string;
|
||||
ts: Ts;
|
||||
u: U;
|
||||
_updatedAt: UpdatedAt2;
|
||||
mentions: any[];
|
||||
channels: any[];
|
||||
file: File;
|
||||
attachments: Attachment[];
|
||||
}
|
||||
|
||||
export interface CustomFields {
|
||||
}
|
||||
|
||||
export interface Update {
|
||||
_id: string;
|
||||
t: string;
|
||||
usernames: string[];
|
||||
usersCount: number;
|
||||
uids: string[];
|
||||
default: boolean;
|
||||
ro: boolean;
|
||||
sysMes: boolean;
|
||||
_updatedAt: UpdatedAt;
|
||||
lm: Lm;
|
||||
lastMessage: LastMessage;
|
||||
name: string;
|
||||
fname: string;
|
||||
u: U2;
|
||||
customFields: CustomFields;
|
||||
}
|
||||
|
||||
export interface DeletedAt {
|
||||
$date: any;
|
||||
}
|
||||
|
||||
export interface Remove {
|
||||
_id: string;
|
||||
_deletedAt: DeletedAt;
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
update: Update[];
|
||||
remove: Remove[];
|
||||
}
|
||||
|
||||
export interface Rooms {
|
||||
msg: string;
|
||||
id: string;
|
||||
result: Result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface ChatMessage {
|
||||
msg: string;
|
||||
id: string;
|
||||
result: Message
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export interface chatHistory {
|
||||
msg: string;
|
||||
id: string;
|
||||
result: {
|
||||
messages: Message[];
|
||||
firstUnread: FirstUnread;
|
||||
unreadNotLoaded: number;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
interface Ts {
|
||||
$date: any;
|
||||
}
|
||||
|
||||
interface U {
|
||||
_id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UpdatedAt {
|
||||
$date: any;
|
||||
}
|
||||
|
||||
interface Attachment {
|
||||
ts: Date;
|
||||
title_link_download: boolean;
|
||||
}
|
||||
|
||||
export interface File {
|
||||
type: string;
|
||||
guid: string;
|
||||
image_url: string;
|
||||
subject: string;
|
||||
start_date?: Date;
|
||||
end_date?: Date;
|
||||
venue: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface EditedAt {
|
||||
$date: number;
|
||||
}
|
||||
|
||||
interface EditedBy {
|
||||
_id: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface Ts2 {
|
||||
$date: number;
|
||||
}
|
||||
|
||||
interface U2 {
|
||||
_id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UpdatedAt2 {
|
||||
$date: number;
|
||||
}
|
||||
|
||||
interface FirstUnread {
|
||||
_id: string;
|
||||
rid: string;
|
||||
msg: string;
|
||||
ts: Ts2;
|
||||
u: U2;
|
||||
_updatedAt: string;
|
||||
mentions: any[];
|
||||
channels: any[];
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
customFields:any;
|
||||
_id: string;
|
||||
rid: string;
|
||||
msg: string;
|
||||
ts: Ts;
|
||||
u: U;
|
||||
t: string;
|
||||
_updatedAt: '';
|
||||
mentions: any[];
|
||||
channels: any[];
|
||||
attachments: Attachment[];
|
||||
file: File;
|
||||
editedAt: EditedAt;
|
||||
editedBy: EditedBy;
|
||||
urls: any[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export interface Lm {
|
||||
$date: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export interface LastMessage {
|
||||
_id: string;
|
||||
rid: string;
|
||||
msg: string;
|
||||
ts: Ts;
|
||||
u: U;
|
||||
_updatedAt: UpdatedAt2;
|
||||
mentions: any[];
|
||||
channels: any[];
|
||||
file: File;
|
||||
attachments: Attachment[];
|
||||
}
|
||||
|
||||
export interface CustomFields {
|
||||
}
|
||||
|
||||
export interface Update {
|
||||
_id: string;
|
||||
t: string;
|
||||
usernames: string[];
|
||||
usersCount: number;
|
||||
uids: string[];
|
||||
default: boolean;
|
||||
ro: boolean;
|
||||
sysMes: boolean;
|
||||
_updatedAt: UpdatedAt;
|
||||
lm: Lm;
|
||||
lastMessage: LastMessage;
|
||||
name: string;
|
||||
fname: string;
|
||||
u: U2;
|
||||
customFields: CustomFields;
|
||||
}
|
||||
|
||||
export interface DeletedAt {
|
||||
$date: any;
|
||||
}
|
||||
|
||||
export interface Remove {
|
||||
_id: string;
|
||||
_deletedAt: DeletedAt;
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
update: Update[];
|
||||
remove: Remove[];
|
||||
}
|
||||
|
||||
export interface Rooms {
|
||||
msg: string;
|
||||
id: string;
|
||||
result: Result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface ChatMessage {
|
||||
msg: string;
|
||||
id: string;
|
||||
result: Message
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export interface chatHistory {
|
||||
msg: string;
|
||||
id: string;
|
||||
result: {
|
||||
messages: Message[];
|
||||
firstUnread: FirstUnread;
|
||||
unreadNotLoaded: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface chatUser {
|
||||
_id: string;
|
||||
createdAt: Date;
|
||||
emails: {
|
||||
address: string;
|
||||
verified: boolean;
|
||||
}
|
||||
type: string;
|
||||
status: 'online' | 'offline' | 'away' | 'busy' ;
|
||||
active: boolean;
|
||||
_updatedAt: Date;
|
||||
roles: string[];
|
||||
name: string;
|
||||
lastLogin: Date;
|
||||
statusConnection: string;
|
||||
utcOffset: number;
|
||||
username: string;
|
||||
__rooms: string[];
|
||||
requirePasswordChange?: boolean;
|
||||
settings: {
|
||||
preferences: {
|
||||
language: string;
|
||||
};
|
||||
};
|
||||
nickname: string;
|
||||
statusText: string;
|
||||
banners: any;
|
||||
statusDefault: string;
|
||||
language: string;
|
||||
avatarOrigin: string;
|
||||
avatarETag?: any;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { SearchDocument } from './search-document';
|
||||
|
||||
describe('SearchDocument', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new SearchDocument()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2017",
|
||||
//"target": "es5",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
],
|
||||
"inlineSources": true,
|
||||
"sourceRoot": "/"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"strictInjectionParameters": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts" // Include all TypeScript files in the current directory and subdirectories
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SignalRService } from 'src/app/infra/socket/signalR/signal-r.service';
|
||||
import { ChatServiceService } from 'src/app/module/chat/domain/chat-service.service'
|
||||
import { skip, switchMap } from 'rxjs/operators';
|
||||
import { SessionStore } from 'src/app/store/session.service';
|
||||
import { Subject, timer } from 'rxjs';
|
||||
import { UserTypingLocalRepository } from './data/repository/typing/user-typing-local-data-source.service';
|
||||
import { UserTypingRemoteRepositoryService } from './data/repository/typing/user-typing-live-data-source.service';
|
||||
import { RoomService } from 'src/app/module/chat/domain/service/room.service'
|
||||
import { HttpListenToMessageLoadHistoryAdapter } from './domain/adapter';
|
||||
import { ISignalRService } from 'src/app/infra/socket/adapter';
|
||||
import { HttpModule } from 'src/app/infra/http/http.module';
|
||||
import { HttpListenToMessageLoadHistoryUseCase } from 'src/app/core/chat/usecase/message/http-listen-to-message-load-history-by-roomId-use-case';
|
||||
import { IMessageLocalRepository } from 'src/app/core/chat/repository/message/message-local-repository';
|
||||
import { MessageLocalDataSourceService } from './data/repository/message/message-local-data-source.service';
|
||||
import { MessageRemoteDataSourceService } from './data/repository/message/message-remote-data-source.service';
|
||||
import { IMessageRemoteRepository } from 'src/app/core/chat/repository/message/message-remote-repository';
|
||||
import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository';
|
||||
import { MessageSocketRepositoryService } from './data/repository/message/message-live-signalr-data-source.service';
|
||||
import { MemberListLocalRepository } from './data/repository/member/member-list-local-repository.service';
|
||||
import { IMemberLocalRepository } from 'src/app/core/chat/repository/member/member-local-repository';
|
||||
import { MemberListRemoteRepository } from './data/repository/member/member-list-remote-repository.service';
|
||||
import { IMemberRemoteRepository } from 'src/app/core/chat/repository/member/member-remote-repository';
|
||||
import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository';
|
||||
import { RoomLocalRepository } from './data/repository/room/room-local-repository.service';
|
||||
import { RoomRemoteDataSourceService } from './data/repository/room/room-remote-repository.service';
|
||||
import { IRoomRemoteRepository } from 'src/app/core/chat/repository/room/room-remote-repository';
|
||||
import { RoomSocketRepositoryService } from './data/repository/room/room-socket-repository.service';
|
||||
import { IRoomSocketRepository } from 'src/app/core/chat/repository/room/room-socket-repository';
|
||||
import { IAttachmentLocalRepository } from 'src/app/core/chat/repository/typing/typing-local-repository';
|
||||
import { AttachmentLocalDataSource } from './data/repository/attachment/attachment-local-repository.service';
|
||||
import { IAttachmentRemoteRepository } from 'src/app/core/chat/repository/attachment/attachment-remote-repository';
|
||||
import { AttachmentRemoteDataSourceService } from './data/repository/attachment/attachment-remote-repository.service';
|
||||
import { IDistributionLocalRepository } from 'src/app/core/chat/repository/distribution/distribution-local-repository';
|
||||
import { DistributionLocalRepository } from './data/repository/destribution/destribution-local-repository';
|
||||
import { DistributionService } from './domain/service/distribution.service'
|
||||
import { BoldLocalRepository } from './data/repository/bold/bold-local-repository';
|
||||
import { IBoldLocalRepository } from 'src/app/core/chat/repository/bold/bold-local-repository';
|
||||
import { RoomLastMessageService } from 'src/app/module/chat/domain/service/room-last-message.service'
|
||||
import { IUserPhotoLocalRepository } from 'src/app/core/chat/repository/user-photo/user-photo-local-repository';
|
||||
import { UserPhotoLocalRepository } from './data/repository/user-foto/user-photo-local-repository.service';
|
||||
import { IUserPhotoRemoteRepository } from 'src/app/core/chat/repository/user-photo/user-photo-remote-repository';
|
||||
import { UserPhotoRemoteRepositoryService } from './data/repository/user-foto/user-photo-remote-repository.service';
|
||||
@NgModule({
|
||||
imports: [HttpModule],
|
||||
providers: [
|
||||
{
|
||||
provide: ISignalRService,
|
||||
useClass: SignalRService, // or MockDataService
|
||||
},
|
||||
{
|
||||
provide: HttpListenToMessageLoadHistoryAdapter,
|
||||
useClass: HttpListenToMessageLoadHistoryUseCase, // or MockDataService
|
||||
},
|
||||
// message repository
|
||||
{
|
||||
provide: IMessageLocalRepository,
|
||||
useClass: MessageLocalDataSourceService
|
||||
},
|
||||
{
|
||||
provide: IMessageRemoteRepository,
|
||||
useClass: MessageRemoteDataSourceService
|
||||
},
|
||||
{
|
||||
provide: IMessageSocketRepository,
|
||||
useClass: MessageSocketRepositoryService
|
||||
},
|
||||
// member repository
|
||||
{
|
||||
provide: IMemberLocalRepository,
|
||||
useClass: MemberListLocalRepository
|
||||
},
|
||||
{
|
||||
provide: IMemberRemoteRepository,
|
||||
useClass: MemberListRemoteRepository
|
||||
},
|
||||
// room repository
|
||||
{
|
||||
provide: IRoomLocalRepository,
|
||||
useClass: RoomLocalRepository
|
||||
},
|
||||
{
|
||||
provide: IRoomRemoteRepository,
|
||||
useClass: RoomRemoteDataSourceService
|
||||
},
|
||||
{
|
||||
provide: IRoomSocketRepository,
|
||||
useClass: RoomSocketRepositoryService
|
||||
},
|
||||
// attachment
|
||||
{
|
||||
provide: IAttachmentLocalRepository,
|
||||
useClass: AttachmentLocalDataSource
|
||||
},
|
||||
{
|
||||
provide: IAttachmentRemoteRepository,
|
||||
useClass: AttachmentRemoteDataSourceService
|
||||
},
|
||||
//
|
||||
{
|
||||
provide: IDistributionLocalRepository,
|
||||
useClass: DistributionLocalRepository
|
||||
},
|
||||
{
|
||||
provide: IBoldLocalRepository,
|
||||
useClass: BoldLocalRepository
|
||||
},
|
||||
// user-photo
|
||||
{
|
||||
provide: IUserPhotoLocalRepository,
|
||||
useClass: UserPhotoLocalRepository
|
||||
},
|
||||
//
|
||||
{
|
||||
provide: IUserPhotoRemoteRepository,
|
||||
useClass: UserPhotoRemoteRepositoryService
|
||||
}
|
||||
],
|
||||
declarations: [],
|
||||
schemas: [],
|
||||
entryComponents: []
|
||||
})
|
||||
export class ChatModule {
|
||||
|
||||
typingCallback: {[key: string]: Subject<any> } = {}
|
||||
|
||||
constructor(
|
||||
private SignalRService: SignalRService,
|
||||
private ChatServiceService: ChatServiceService,
|
||||
private signalR: SignalRService,
|
||||
private localDataSource: UserTypingLocalRepository,
|
||||
private UserTypingRemoteRepositoryService: UserTypingRemoteRepositoryService,
|
||||
private RoomService: RoomService,
|
||||
private DistributionService: DistributionService,
|
||||
private RoomLastMessageService: RoomLastMessageService
|
||||
) {
|
||||
|
||||
this.RoomService.init()
|
||||
this.syncMessage()
|
||||
this.listenToTyping()
|
||||
}
|
||||
|
||||
async listenToTyping() {
|
||||
this.UserTypingRemoteRepositoryService.listenToTyping().subscribe(async(e) => {
|
||||
|
||||
// this.memoryDataSource.dispatch(removeUserTyping({data: {...e} as any}))
|
||||
// this.memoryDataSource.dispatch(addUserTyping({data: {...e} as any}))
|
||||
//
|
||||
const value = await this.localDataSource.addUserTyping(e);
|
||||
|
||||
const id = e.roomId + '@' + e.userName
|
||||
if(!this.typingCallback[id]) {
|
||||
this.typingCallback[id] = new Subject()
|
||||
this.typingCallback[id].pipe(
|
||||
switchMap(() => timer(2000)),
|
||||
).subscribe(() => {
|
||||
// console.log('111111==============')
|
||||
// this.memoryDataSource.dispatch(removeUserTyping({data: {...e} as any}))
|
||||
this.localDataSource.removeUserTyping(e)
|
||||
})
|
||||
} else {
|
||||
this.typingCallback[id].next()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
async syncMessage() {
|
||||
const connection = this.SignalRService.getConnectionState()
|
||||
|
||||
connection.pipe(
|
||||
skip(1) // Skip the first value
|
||||
).subscribe((value: boolean)=> {
|
||||
if(value) {
|
||||
// on reconnect
|
||||
this.ChatServiceService.chatSync();
|
||||
}
|
||||
});
|
||||
|
||||
connection.subscribe((value: boolean) => {
|
||||
if(value) {
|
||||
// on connect
|
||||
this.ChatServiceService.sendLocalMessages()
|
||||
}
|
||||
})
|
||||
|
||||
// on page reload sync
|
||||
if(!(!SessionStore.user.Inactivity || !SessionStore.exist)) {
|
||||
this.ChatServiceService.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
import { DistributionTable } from "src/app/infra/database/dexie/instance/chat/schema/destribution";
|
||||
import { DistributionOutPutDTO } from "src/app/module/chat/domain/service/distribution.service";
|
||||
|
||||
export function distributionListDetermineChanges(____serverDistributions: DistributionOutPutDTO[], localDistributions: DistributionTable[], messageId: string) {
|
||||
|
||||
const serverDistribution = ____serverDistributions.map( e=> {
|
||||
return {
|
||||
...e,
|
||||
$messageIdMemberId: messageId + e.memberId
|
||||
}
|
||||
})
|
||||
|
||||
const serverDistributionMap = new Map(serverDistribution.map(distribution => [distribution.$messageIdMemberId, distribution]));
|
||||
const localDistributionMap = new Map(localDistributions.map(distribution => [distribution.$messageIdMemberId, distribution]));
|
||||
|
||||
const distributionToInsert = serverDistribution.filter(distribution => !localDistributionMap.has(distribution.$messageIdMemberId));
|
||||
const distributionToUpdate = serverDistribution.filter(distribution => {
|
||||
const localDistribution = localDistributionMap.get(distribution.$messageIdMemberId);
|
||||
return localDistribution && (
|
||||
distribution.deliverAt !== localDistribution.deliverAt ||
|
||||
distribution.readAt !== localDistribution.readAt
|
||||
)
|
||||
});
|
||||
|
||||
const distributionToDelete = localDistributions.filter(distribution => !serverDistributionMap.has(distribution.$messageIdMemberId));
|
||||
|
||||
return { distributionToInsert, distributionToUpdate, distributionToDelete };
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { MessageTable } from "src/app/infra/database/dexie/instance/chat/schema/message";
|
||||
|
||||
export function messageListDetermineChanges(serverList: MessageTable[], localList: MessageTable[]) {
|
||||
|
||||
localList = localList.filter(e => e.id)
|
||||
// Convert lists to dictionaries for easier comparison
|
||||
const localDict = localList.reduce((acc, item) => {
|
||||
acc[item.id] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const serverDict = serverList.reduce((acc, item) => {
|
||||
acc[item.id] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Identify added and deleted items
|
||||
const addedItems = serverList.filter(item => !localDict[item.id]);
|
||||
const deletedItems = localList.filter(item => !serverDict[item.id]);
|
||||
|
||||
// Identify changed items
|
||||
const changedItems = serverList.filter(item => {
|
||||
const localItem = localDict[item.id];
|
||||
|
||||
if(localItem?.$id) {
|
||||
item.$id = localItem.$id
|
||||
}
|
||||
|
||||
return localItem && (item.editedAt !== localItem.editedAt || item.sentAt != item.sentAt || item.reactions.some((r, index) => {
|
||||
const localReaction = localItem.reactions[index];
|
||||
return !localReaction || r.reactedAt !== localReaction.reactedAt;
|
||||
}));
|
||||
});
|
||||
|
||||
return {
|
||||
addedItems,
|
||||
deletedItems,
|
||||
changedItems
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { RoomTable } from "src/app/infra/database/dexie/instance/chat/schema/room";
|
||||
import { RoomByIdOutputDTO } from "src/app/module/chat/domain/use-case/room/room-get-by-id-use-case.service";
|
||||
import { RoomListItemOutPutDTO } from "src/app/module/chat/domain/use-case/room/room-get-list-use-case.service";
|
||||
|
||||
export function roomByIdDetermineChanges(serverResponse: RoomByIdOutputDTO, localRooms: RoomTable[]) {
|
||||
|
||||
const localRoomMap = new Map(localRooms.map(room => [room.id, room]));
|
||||
|
||||
let roomsToInsert:RoomByIdOutputDTO | undefined;
|
||||
let roomsToUpdate: RoomByIdOutputDTO | undefined;
|
||||
|
||||
const fond = localRooms.filter(room => !localRoomMap.has(serverResponse.data.id));
|
||||
if(!fond) {
|
||||
roomsToInsert = serverResponse
|
||||
}
|
||||
|
||||
const needToUpdate = localRooms.filter(room => {
|
||||
const localRoom = localRoomMap.get(room.id);
|
||||
return localRoom && (
|
||||
room.roomName !== serverResponse.data.roomName ||
|
||||
room.createdBy.wxUserId !== serverResponse.data.createdBy.wxUserId ||
|
||||
room.createdAt !== serverResponse.data.createdAt ||
|
||||
room.expirationDate !== serverResponse.data.expirationDate // ||
|
||||
// room.chatRoom.roomType !== localRoom.roomType
|
||||
);
|
||||
});
|
||||
|
||||
if(needToUpdate) {
|
||||
roomsToUpdate = serverResponse
|
||||
}
|
||||
|
||||
return { roomsToInsert, roomsToUpdate };
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { RoomType } from "src/app/core/chat/entity/group";
|
||||
import { RoomTable } from "src/app/infra/database/dexie/instance/chat/schema/room";
|
||||
import { RoomListItemOutPutDTO } from "src/app/module/chat/domain/use-case/room/room-get-list-use-case.service";
|
||||
|
||||
export function roomListDetermineChanges(serverRooms: RoomListItemOutPutDTO[], localRooms: RoomTable[]) {
|
||||
const serverRoomMap = new Map(serverRooms.map(room => [room.chatRoom.id, room]));
|
||||
const localRoomMap = new Map(localRooms.map(room => [room.id, room]));
|
||||
|
||||
const roomsToInsert = serverRooms.filter(room => !localRoomMap.has(room.chatRoom.id));
|
||||
const roomsToUpdate = serverRooms.filter(room => {
|
||||
const localRoom = localRoomMap.get(room.chatRoom.id);
|
||||
|
||||
return localRoom && (
|
||||
room.chatRoom.roomName !== localRoom.roomName && room.chatRoom.roomType == RoomType.Group ||
|
||||
room.chatRoom.createdBy.wxUserId !== localRoom.createdBy.wxUserId ||
|
||||
room.chatRoom.createdAt !== localRoom.createdAt ||
|
||||
room.chatRoom.expirationDate !== localRoom.expirationDate // ||
|
||||
// room.chatRoom.messages?.[0]?.id !== localRoom.messages?.[0]?.id
|
||||
// room.chatRoom.roomType !== localRoom.roomType
|
||||
);
|
||||
});
|
||||
const roomsToDelete = localRooms.filter(room => !serverRoomMap.has(room.id));
|
||||
|
||||
return { roomsToInsert, roomsToUpdate, roomsToDelete };
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { MemberTable } from "src/app/infra/database/dexie/instance/chat/schema/members";
|
||||
import { RoomByIdMemberItemOutputDTO } from "src/app/module/chat/domain/use-case/room/room-get-by-id-use-case.service";
|
||||
|
||||
export function roomMemberListDetermineChanges(____serverRooms: RoomByIdMemberItemOutputDTO[], localRooms: MemberTable[], roomId: string) {
|
||||
|
||||
const PServerRooms: (RoomByIdMemberItemOutputDTO & {$roomIdUserId: string})[] = ____serverRooms.map( e=> {
|
||||
|
||||
return {
|
||||
...e,
|
||||
$roomIdUserId: roomId + e.user.wxUserId
|
||||
}
|
||||
})
|
||||
|
||||
const serverRoomMap = new Map(PServerRooms.map(room => [room.$roomIdUserId, room]));
|
||||
const localRoomMap = new Map(localRooms.map(room => [room.$roomIdUserId, room]));
|
||||
|
||||
const membersToInsert = PServerRooms.filter(room => !localRoomMap.has(room.$roomIdUserId));
|
||||
const membersToUpdate = PServerRooms.filter(room => {
|
||||
const localRoom = localRoomMap.get(room.$roomIdUserId);
|
||||
return localRoom && (
|
||||
room.user.wxUserId !== localRoom.wxUserId ||
|
||||
room.user.userPhoto !== localRoom.userPhoto ||
|
||||
room.joinAt !== localRoom.joinAt,
|
||||
room.isAdmin !== localRoom.isAdmin
|
||||
)
|
||||
});
|
||||
|
||||
const membersToDelete = localRooms.filter(room => !serverRoomMap.has(room.$roomIdUserId));
|
||||
|
||||
return { membersToInsert, membersToUpdate, membersToDelete };
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { IAttachmentLocalRepository } from 'src/app/core/chat/repository/typing/typing-local-repository';
|
||||
import { AttachmentTable, AttachmentTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/attachment';
|
||||
import { chatDatabase } from 'src/app/infra/database/dexie/service';
|
||||
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AttachmentLocalDataSource extends DexieRepository<AttachmentTable, AttachmentTable> implements IAttachmentLocalRepository {
|
||||
|
||||
messageSubject = new Subject();
|
||||
|
||||
constructor() {
|
||||
super(chatDatabase.attachment, AttachmentTableSchema)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { Injectable, Input } from '@angular/core';
|
||||
import { IAttachmentRemoteRepository } from 'src/app/core/chat/repository/attachment/attachment-remote-repository';
|
||||
import { HttpService } from 'src/app/services/http.service';
|
||||
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AttachmentRemoteDataSourceService implements IAttachmentRemoteRepository {
|
||||
private baseUrl = 'https://gdapi-dev.dyndns.info/stage/api/v2/Chat'; // Your base URL
|
||||
|
||||
constructor(
|
||||
private httpService: HttpService
|
||||
) { }
|
||||
|
||||
async getAttachment(id: string | number): DataSourceReturn<Blob> {
|
||||
return await this.httpService.get(`${this.baseUrl}/attachment/${id}`, { responseType: 'blob' });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { from } from "rxjs";
|
||||
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
|
||||
import { Dexie, EntityTable, liveQuery, Observable } from 'Dexie';
|
||||
import { BoldTable, BoldTableSchema } from "src/app/infra/database/dexie/instance/chat/schema/bold";
|
||||
import { chatDatabase } from "src/app/infra/database/dexie/service";
|
||||
import { IBoldLocalRepository } from "src/app/core/chat/repository/bold/bold-local-repository";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BoldLocalRepository extends DexieRepository<BoldTable, BoldTable> implements IBoldLocalRepository {
|
||||
constructor() {
|
||||
super(chatDatabase.bold, BoldTableSchema)
|
||||
}
|
||||
|
||||
open() {
|
||||
return chatDatabase.open()
|
||||
}
|
||||
|
||||
listen() {
|
||||
return from(liveQuery(() => chatDatabase.bold.toArray()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { DistributionTable, DistributionTableSchema } from "src/app/infra/database/dexie/instance/chat/schema/destribution";
|
||||
import { chatDatabase } from "src/app/infra/database/dexie/service";
|
||||
import { DexieRepository } from "src/app/infra/repository/dexie/dexie-repository.service";
|
||||
|
||||
|
||||
export class DistributionLocalRepository extends DexieRepository<DistributionTable, DistributionTable> {
|
||||
constructor() {
|
||||
super(chatDatabase.distribution, DistributionTableSchema)
|
||||
|
||||
chatDatabase.distribution.hook("creating", function (primKey, obj, transaction) {
|
||||
obj.$messageIdMemberId = `${obj.messageId}${obj.memberId}`
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Dexie, EntityTable, liveQuery, Observable } from 'Dexie';
|
||||
import { z } from 'zod';
|
||||
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
|
||||
import { ok } from 'neverthrow';
|
||||
import { err, Result } from 'neverthrow';
|
||||
import { MemberListUPdateStatusInputDTO } from '../../../domain/use-case/socket/member-list-update-status-use-case.service';
|
||||
import { from } from 'rxjs';
|
||||
import { MemberTable, MemberTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/members';
|
||||
import { chatDatabase } from 'src/app/infra/database/dexie/service';
|
||||
import { IDirectMemberInput, IGetMemberLive, IMemberLocalRepository } from 'src/app/core/chat/repository/member/member-local-repository';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MemberListLocalRepository extends DexieRepository<MemberTable, MemberTable> implements IMemberLocalRepository {
|
||||
|
||||
constructor() {
|
||||
super(chatDatabase.members, MemberTableSchema)
|
||||
}
|
||||
|
||||
|
||||
async directMember({roomId, currentUserId}:IDirectMemberInput) {
|
||||
try {
|
||||
let a = await chatDatabase.members.where('roomId')
|
||||
.equals(roomId)
|
||||
.and(message => message.wxUserId !== currentUserId)
|
||||
.first()
|
||||
|
||||
return ok(a as MemberTable)
|
||||
} catch (e) {
|
||||
return err(e)
|
||||
}
|
||||
}
|
||||
|
||||
async addMember(data: MemberTable) {
|
||||
data.$roomIdUserId = data.roomId + data.wxUserId
|
||||
return this.insert(data)
|
||||
}
|
||||
|
||||
async updateMemberRole(data: MemberTable) {
|
||||
try {
|
||||
const result = await chatDatabase.members.where({
|
||||
wxUserId: data.wxUserId,
|
||||
roomId: data.roomId,
|
||||
}).modify(data);
|
||||
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
async updateMembersStatus(data: MemberListUPdateStatusInputDTO): Promise<Result<true, any>> {
|
||||
try {
|
||||
await chatDatabase.members.toCollection().modify({ status: 'offline' });
|
||||
for (const item of data) {
|
||||
const wxUserId = item.value.userId; // Extract wxUserId
|
||||
await chatDatabase.members.where('wxUserId').equals(wxUserId).modify({ status: 'online' });
|
||||
}
|
||||
return ok(true)
|
||||
} catch (error) {
|
||||
console.error("Error updating user statuses:", error);
|
||||
return err(error)
|
||||
}
|
||||
}
|
||||
|
||||
allMemberOnline(roomId:string) {
|
||||
return liveQuery(async () => {
|
||||
|
||||
const allMessages = await chatDatabase.members
|
||||
.where('roomId')
|
||||
.equals(roomId)
|
||||
.toArray();
|
||||
|
||||
return allMessages.every(message => message?.status === "online");
|
||||
});
|
||||
}
|
||||
|
||||
async removeMemberFromRoom($roomIdUserId): Promise<Result<any ,any>> {
|
||||
try {
|
||||
const member = await chatDatabase.members.where({ $roomIdUserId: $roomIdUserId }).first();
|
||||
if (member) {
|
||||
const result = ok(await chatDatabase.members.delete($roomIdUserId));
|
||||
return result
|
||||
} else {
|
||||
return err('not Found')
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getMemberLive(data: IGetMemberLive) {
|
||||
const $roomIdUserId = data.roomId + data.wxUserId
|
||||
return liveQuery(() => chatDatabase.members.get($roomIdUserId));
|
||||
}
|
||||
|
||||
async getRoomMemberById(roomId: any) {
|
||||
return await chatDatabase.members.where({roomId}).toArray()
|
||||
}
|
||||
getRoomMemberByIdLive(roomId: any) {
|
||||
return from (liveQuery(() => chatDatabase.members.where({roomId}).toArray()))
|
||||
}
|
||||
|
||||
getRoomMemberNoneAdminByIdLive(roomId: any) {
|
||||
return liveQuery(async() => {
|
||||
const members = await chatDatabase.members.where({roomId}).toArray()
|
||||
return members.filter(e => e.isAdmin != true)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Result } from 'neverthrow';
|
||||
import { ValidateSchema } from 'src/app/services/decorators/validate-schema.decorator';
|
||||
import { HttpService } from 'src/app/services/http.service';
|
||||
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
|
||||
import { IMemberRemoteRepository } from 'src/app/core/chat/repository/member/member-remote-repository';
|
||||
import { AddMemberToRoomInputDTO, AddMemberToRoomInputDTOSchema } from '../../../domain/use-case/member/member-add-use-case.service';
|
||||
import { MemberSetAdminDTO } from '../../../domain/use-case/member/member-admin-use-case.service';
|
||||
import { UserRemoveListInputDTOSchema, UserRemoveListInputDTO } from '../../../domain/use-case/room/room-leave-by-id-use-case.service';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MemberListRemoteRepository implements IMemberRemoteRepository {
|
||||
|
||||
private baseUrl = 'https://gdapi-dev.dyndns.info/stage/api/v2/Chat'; // Your base URL
|
||||
|
||||
constructor(private httpService: HttpService) { }
|
||||
|
||||
|
||||
@ValidateSchema(AddMemberToRoomInputDTOSchema)
|
||||
async addMemberToRoom(data: AddMemberToRoomInputDTO): DataSourceReturn<AddMemberToRoomInputDTO> {
|
||||
return await this.httpService.post<any>(`${this.baseUrl}/Room/${data.id}/Member`, { members:data.members });
|
||||
}
|
||||
|
||||
|
||||
@ValidateSchema(UserRemoveListInputDTOSchema)
|
||||
async removeMemberFromRoom(data: UserRemoveListInputDTO): Promise<Result<any ,any>> {
|
||||
return await this.httpService.delete<any>(`${this.baseUrl}/Room/${data.id}/Member`, {members:data.members});
|
||||
}
|
||||
|
||||
async setAmin(data: MemberSetAdminDTO): Promise<Result<any ,any>> {
|
||||
return await this.httpService.patch<any>(`${this.baseUrl}/Room/${data.roomId}/Member/${data.memberId}/admin`);
|
||||
}
|
||||
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { InstanceId } from '../../../domain/chat-service.service';
|
||||
import { MessageUpdateInput } from '../../../domain/use-case/message/message-update-by-id-use-case.service';
|
||||
import { MessageReactionInput } from '../../../domain/use-case/message/message-reaction-by-id-use-case.service';
|
||||
import { SignalRService } from 'src/app/infra/socket/signalR/signal-r.service';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { SocketMessage } from 'src/app/infra/socket/signalR/signalR';
|
||||
import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository';
|
||||
import { MessageCreateOutPutDataDTO, MessageInputDTO } from '../../../domain/use-case/message/message-create-use-case.service';
|
||||
import { MessageMarkAsReadInput } from '../../../domain/use-case/message/message-mark-as-read-use-case.service';
|
||||
import { MessageOutPutDataDTO } from 'src/app/core/chat/repository/dto/messageOutputDTO';
|
||||
import { MessageDeleteInputDTO } from '../../../domain/use-case/message/message-delete-by-id-live-use-case.service';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
interface sendDeliverAt {
|
||||
memberId: number,
|
||||
messageId:string,
|
||||
roomId: string,
|
||||
requestId: string
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageSocketRepositoryService implements IMessageSocketRepository {
|
||||
|
||||
private sendDataSubject: BehaviorSubject<{method: string, data: any}> = new BehaviorSubject<{method: string, data: any}>(null);
|
||||
|
||||
constructor(
|
||||
private socket: SignalRService
|
||||
) {}
|
||||
|
||||
connect() {
|
||||
return this.socket.establishConnection();
|
||||
}
|
||||
|
||||
async join() {
|
||||
return await this.socket.join()
|
||||
}
|
||||
|
||||
|
||||
async sendGroupMessage(data: MessageInputDTO) {
|
||||
|
||||
if(!data.requestId) {
|
||||
data.requestId = InstanceId +'@'+ uuidv4();
|
||||
}
|
||||
|
||||
const result = await this.socket.sendData<MessageCreateOutPutDataDTO>({
|
||||
method: 'sendMessage',
|
||||
data: data,
|
||||
})
|
||||
|
||||
this.sendDataSubject.next({
|
||||
method: 'sendMessage',
|
||||
data: data,
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async sendDirectMessage(data: MessageInputDTO) {
|
||||
|
||||
if(!data.requestId) {
|
||||
data.requestId = InstanceId +'@'+ uuidv4();
|
||||
}
|
||||
const result = await this.socket.sendData<MessageOutPutDataDTO>({
|
||||
method: 'SendDirectMessage',
|
||||
data: data as any,
|
||||
})
|
||||
|
||||
this.sendDataSubject.next({
|
||||
method: 'SendDirectMessage',
|
||||
data: data,
|
||||
})
|
||||
return result;
|
||||
}
|
||||
|
||||
async sendDeliverAt(data: sendDeliverAt) {
|
||||
const result = await this.socket.sendData<any>({
|
||||
method: 'DeliverAt',
|
||||
data: data as any,
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async sendReadAt(data: MessageMarkAsReadInput) {
|
||||
const result = await this.socket.sendData<any>({
|
||||
method: 'ReadAt',
|
||||
data: data as any,
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async sendDelete(data: MessageDeleteInputDTO) {
|
||||
const result = await this.socket.sendData<any>({
|
||||
method: 'ReadAt',
|
||||
data: data,
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
listenToMessages() {
|
||||
return this.socket.getData().pipe(
|
||||
filter((e) : e is SocketMessage<MessageOutPutDataDTO>=> e?.method == 'ReceiveMessage'
|
||||
),
|
||||
map((e)=> e.data)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
listenToDeleteMessages() {
|
||||
return this.socket.getData().pipe(
|
||||
filter((e) : e is SocketMessage<MessageOutPutDataDTO>=> e?.method == 'DeleteMessage'
|
||||
),
|
||||
map((e)=> e.data)
|
||||
)
|
||||
}
|
||||
|
||||
listenToUpdateMessages() {
|
||||
return this.socket.getData().pipe(
|
||||
filter((e) : e is SocketMessage<MessageOutPutDataDTO>=> e?.method == 'UpdateMessage'
|
||||
),
|
||||
map((e)=> e.data)
|
||||
)
|
||||
}
|
||||
|
||||
listenToSendMessage() {
|
||||
return this.sendDataSubject.pipe(
|
||||
filter((e) : e is SocketMessage<MessageInputDTO>=> {
|
||||
console.log(e, e?.method == 'sendMessage' || e?.method == 'SendDirectMessage')
|
||||
|
||||
return e?.method == 'sendMessage' || e?.method == 'SendDirectMessage'
|
||||
}
|
||||
))
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
reactToMessageSocket(data: MessageReactionInput) {
|
||||
this.socket.sendData({
|
||||
method: 'ReactMessage',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
updateMessage(input: MessageUpdateInput) {
|
||||
this.socket.sendData({
|
||||
method: 'EditMessage',
|
||||
data: input,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
sendMessageDelete(data: MessageDeleteInputDTO) {
|
||||
|
||||
data['requestId'] = InstanceId +'@'+ uuidv4();
|
||||
|
||||
const result = this.socket.sendData<any>({
|
||||
method: 'DeleteMessage',
|
||||
data: data,
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { liveQuery } from 'Dexie';
|
||||
import { MessageEntity } from '../../../../../core/chat/entity/message';
|
||||
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
|
||||
import { Observable as DexieObservable, PromiseExtended } from 'Dexie';
|
||||
import { DexieMessageTable, MessageTable, MessageTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/message';
|
||||
import { chatDatabase } from 'src/app/infra/database/dexie/service';
|
||||
import { IMessageLocalRepository } from 'src/app/core/chat/repository/message/message-local-repository';
|
||||
import { BehaviorSubject, combineLatest, from, Observable } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageLocalDataSourceService extends DexieRepository<MessageTable, MessageEntity, DexieMessageTable> implements IMessageLocalRepository {
|
||||
|
||||
private creatingSubject : BehaviorSubject<MessageTable> = new BehaviorSubject<MessageTable>(null);
|
||||
private lastTimestamp = 0;
|
||||
|
||||
constructor() {
|
||||
super(chatDatabase.message, MessageTableSchema, chatDatabase)
|
||||
|
||||
this.setAllSenderToFalse();
|
||||
this.onCreatingHook()
|
||||
}
|
||||
|
||||
private onCreatingHook() {
|
||||
chatDatabase.message.hook('creating', (primaryKey, obj, transaction) => {
|
||||
|
||||
let now = Date.now();
|
||||
|
||||
// If the current time is the same as the last, increment
|
||||
if (now <= this.lastTimestamp) {
|
||||
obj.$createAt = this.lastTimestamp + 1;
|
||||
this.lastTimestamp = this.lastTimestamp + 1;
|
||||
} else {
|
||||
this.lastTimestamp = now;
|
||||
obj.$createAt = now;
|
||||
}
|
||||
|
||||
if(obj.id) {
|
||||
obj.$id = obj.id
|
||||
} else {
|
||||
obj.$id = 'Local-'+uuidv4()
|
||||
}
|
||||
|
||||
this.creatingSubject.next(obj)
|
||||
});
|
||||
}
|
||||
|
||||
onCreateObservable() {
|
||||
return this.creatingSubject.asObservable()
|
||||
}
|
||||
|
||||
async setAllSenderToFalse() {
|
||||
|
||||
// this.createTransaction(async (table) => {
|
||||
// const result = await this.find({sending: true })
|
||||
// if(result.isOk()) {
|
||||
// for(const message of result.value) {
|
||||
// await this.update(message.$id, { sending: false })
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
try {
|
||||
await chatDatabase.transaction('rw', chatDatabase.message, async () => {
|
||||
// Perform the update operation within the transaction
|
||||
await chatDatabase.message.toCollection().modify({ sending: false });
|
||||
});
|
||||
// console.log('All messages updated successfully.');
|
||||
} catch (error) {
|
||||
console.error('Error updating messages:', error);
|
||||
}
|
||||
}
|
||||
|
||||
getItems(roomId: string): PromiseExtended<MessageEntity[]> {
|
||||
return chatDatabase.message.where('roomId').equals(roomId).sortBy('sentAt') as any
|
||||
}
|
||||
|
||||
getItemsLive(roomId: string): DexieObservable<MessageEntity[]> {
|
||||
return liveQuery(() => chatDatabase.message.where('roomId').equals(roomId).sortBy('sentAt') as any)
|
||||
}
|
||||
|
||||
async getOfflineMessages () {
|
||||
try {
|
||||
const allMessages = await chatDatabase.message
|
||||
.filter(msg => typeof msg.id !== 'string' && msg.sending == false)
|
||||
.toArray();
|
||||
|
||||
return allMessages as MessageEntity[];
|
||||
} catch (error) {
|
||||
console.error('Error fetching messages:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
getLastMessageForRooms(roomIds: string[]): Observable<any[]> {
|
||||
const observables = roomIds.map(roomId =>
|
||||
from (liveQuery(async() =>{
|
||||
const messages = await chatDatabase.message
|
||||
.where('roomId')
|
||||
.equals(roomId)
|
||||
.reverse()
|
||||
.sortBy('timestamp')
|
||||
|
||||
return messages[0] || null; // Return the first item (latest message) or null if no message
|
||||
})).pipe(
|
||||
map((message) => ({ roomId, message: message || null })) // Attach roomId to the result
|
||||
)
|
||||
);
|
||||
|
||||
return combineLatest(observables); // Combine all observables into one array of results
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpService } from 'src/app/services/http.service';
|
||||
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
|
||||
import { SignalRService } from 'src/app/infra/socket/signalR/signal-r.service';
|
||||
import { HttpAdapter } from 'src/app/infra/http/adapter';
|
||||
import { IMessageGetAllByRoomIdOutPut } from 'src/app/core/chat/usecase/message/message-get-all-by-room-Id';
|
||||
import { IMessageRemoteRepository } from 'src/app/core/chat/repository/message/message-remote-repository';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageRemoteDataSourceService implements IMessageRemoteRepository {
|
||||
|
||||
private baseUrl = 'https://gdapi-dev.dyndns.info/stage/api/v2/Chat'; // Your base URL
|
||||
|
||||
constructor(
|
||||
private httpService: HttpService,
|
||||
private socket: SignalRService,
|
||||
private http: HttpAdapter
|
||||
) {}
|
||||
|
||||
|
||||
// @APIReturn(MessageOutPutDTOSchema, 'get/Messages')
|
||||
async getMessagesFromRoom(id: string): DataSourceReturn<IMessageGetAllByRoomIdOutPut> {
|
||||
|
||||
var a = await this.http.get<IMessageGetAllByRoomIdOutPut>(`${this.baseUrl}/Room/${id}/Messages`)
|
||||
|
||||
return a.map((e) => {
|
||||
return e.data
|
||||
})
|
||||
|
||||
// return await this.httpService.get(`${this.baseUrl}/Room/${id}/Messages`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { liveQuery, Observable } from 'Dexie';
|
||||
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
|
||||
import { from } from 'rxjs';
|
||||
import { RoomTable, RoomTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/room';
|
||||
import { chatDatabase } from 'src/app/infra/database/dexie/service';
|
||||
import { IRoomLocalRepository } from 'src/app/core/chat/repository/room/room-local-repository';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RoomLocalRepository extends DexieRepository<RoomTable, RoomTable> implements IRoomLocalRepository {
|
||||
|
||||
constructor() {
|
||||
super(chatDatabase.room, RoomTableSchema)
|
||||
|
||||
chatDatabase.room.hook('updating', (modifications, primKey, oldValue, transaction) => {
|
||||
|
||||
// if((modifications as Partial<RoomTable>).messages?.[0].requestId == oldValue.messages?.[0].requestId) {
|
||||
// (modifications as Partial<RoomTable>).messages[0].sentAt = oldValue.messages?.[0]?.sentAt;
|
||||
// } else if ((modifications as Partial<RoomTable>).messages?.[0].id == oldValue.messages?.[0].id) {
|
||||
// (modifications as Partial<RoomTable>).messages[0].sentAt = oldValue.messages?.[0]?.sentAt
|
||||
// }
|
||||
|
||||
return modifications
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
getItemsLive() {
|
||||
return from (liveQuery(() => chatDatabase.room.toArray()));
|
||||
}
|
||||
|
||||
getRoomByIdLive(id: any) {
|
||||
return from(liveQuery(() => chatDatabase.room.get(id)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Result } from 'neverthrow';
|
||||
import { HttpService } from 'src/app/services/http.service';
|
||||
import { AddMemberToRoomInputDTO } from '../../../domain/use-case/member/member-add-use-case.service';
|
||||
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
|
||||
import { SessionStore } from 'src/app/store/session.service';
|
||||
import { SignalRService } from 'src/app/infra/socket/signalR/signal-r.service';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { CreateRoomInputDTO, RoomOutPutDTO } from '../../../domain/use-case/room/room-create-use-case.service';
|
||||
import { IRoomRemoteRepository } from 'src/app/core/chat/repository/room/room-remote-repository';
|
||||
import { RoomByIdOutputDTO } from 'src/app/module/chat/domain/use-case/room/room-get-by-id-use-case.service';
|
||||
import { RoomUpdateInputDTO, RoomUpdateOutputDTO } from 'src/app/module/chat/domain/use-case/room/room-update-by-id-use-case.service';
|
||||
import { RoomListOutPutDTO } from '../../../domain/use-case/room/room-get-list-use-case.service';
|
||||
import { z } from 'zod';
|
||||
import { HttpAdapter } from 'src/app/infra/http/adapter';
|
||||
|
||||
const RoomByIdInputDTOSchema = z.string()
|
||||
type RoomByIdInputDTO = z.infer<typeof RoomByIdInputDTOSchema>
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RoomRemoteDataSourceService implements IRoomRemoteRepository {
|
||||
|
||||
private baseUrl = 'https://gdapi-dev.dyndns.info/stage/api/v2/Chat'; // Your base URL
|
||||
|
||||
constructor(
|
||||
private httpService: HttpService,
|
||||
private socket: SignalRService,
|
||||
private Http: HttpAdapter
|
||||
) {}
|
||||
|
||||
|
||||
//@ValidateSchema(CreateRoomInputDTOSchema)
|
||||
//@APIReturn(RoomOutPutDTOSchema, 'post/Room')
|
||||
async createRoom(data: CreateRoomInputDTO): DataSourceReturn<RoomOutPutDTO> {
|
||||
return await this.httpService.post<RoomOutPutDTO>(`${this.baseUrl}/Room`, data);
|
||||
}
|
||||
|
||||
|
||||
//@APIReturn(RoomListOutPutDTOSchema, 'get/Room')
|
||||
async getRoomList(): Promise<DataSourceReturn<RoomListOutPutDTO>> {
|
||||
const result = await this.Http.get<RoomListOutPutDTO>(`${this.baseUrl}/Room?userId=${SessionStore.user.UserId}`);
|
||||
|
||||
return result.map((e)=> e.data)
|
||||
}
|
||||
|
||||
//@ValidateSchema(RoomByIdInputDTOSchema)
|
||||
//@APIReturn(RoomByIdOutputDTOSchema,'get/Room/${id}')
|
||||
async getRoom(id: RoomByIdInputDTO): DataSourceReturn<RoomByIdOutputDTO> {
|
||||
const result = await this.Http.get(`${this.baseUrl}/Room/${id}`);
|
||||
|
||||
return result.map((e)=> e.data)
|
||||
}
|
||||
|
||||
//@ValidateSchema(RoomUpdateInputDTOSchema)
|
||||
//@APIReturn(RoomByIdOutputDTOSchema,'update/Room/${id}')
|
||||
async updateRoom(data: RoomUpdateInputDTO): Promise<DataSourceReturn<RoomUpdateOutputDTO>> {
|
||||
const id = data.roomId
|
||||
delete data.roomId
|
||||
return await this.httpService.put<any>(`${this.baseUrl}/Room/${id}`, data);
|
||||
}
|
||||
|
||||
async deleteRoom(id: string): Promise<Result<any ,any>> {
|
||||
return await this.httpService.delete<any>(`${this.baseUrl}/Room/${id}`, {});
|
||||
}
|
||||
|
||||
|
||||
async addMemberToRoomSocket(data: AddMemberToRoomInputDTO) {
|
||||
|
||||
const result = await this.socket.sendData({
|
||||
method: 'AddRoomMember',
|
||||
data: {
|
||||
requestId: uuidv4(),
|
||||
roomId: data.id,
|
||||
members: data.members
|
||||
}
|
||||
})
|
||||
|
||||
console.log({result})
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SignalRService } from 'src/app/infra/socket/signalR/signal-r.service';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { z } from 'zod';
|
||||
import { SocketMessage } from 'src/app/infra/socket/signalR/signalR';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { IRoomSocketRepository } from 'src/app/core/chat/repository/room/room-socket-repository';
|
||||
import { CreateRoomInputDTO } from '../../../domain/use-case/room/room-create-use-case.service';
|
||||
|
||||
const listenToDeleteRoomInputSchema = z.object({
|
||||
roomId: z.string()
|
||||
})
|
||||
|
||||
|
||||
export const RoomSocketOutPutDTOSchema = z.object({
|
||||
id: z.string(),
|
||||
roomName: z.string(),
|
||||
createdBy: z.any().nullable(),
|
||||
createdAt: z.string(),
|
||||
expirationDate: z.string().nullable(),
|
||||
roomType: z.any()
|
||||
});
|
||||
|
||||
export type RoomSocketOutPutDTO = z.infer<typeof RoomSocketOutPutDTOSchema>
|
||||
|
||||
|
||||
export type ListenToDeleteRoomInput = z.infer<typeof listenToDeleteRoomInputSchema>
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RoomSocketRepositoryService implements IRoomSocketRepository {
|
||||
|
||||
constructor(
|
||||
private socket: SignalRService
|
||||
) { }
|
||||
|
||||
|
||||
async CreateGroup(data: CreateRoomInputDTO) {
|
||||
const result = await this.socket.sendData<RoomSocketOutPutDTO>({
|
||||
method: 'CreateGroup',
|
||||
data: {
|
||||
...data,
|
||||
requestId: uuidv4()
|
||||
} as any,
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
listenToCreateRoom() {
|
||||
return this.socket.getData().pipe(
|
||||
filter((data) => data?.method == 'UserAddGroup')
|
||||
)
|
||||
}
|
||||
|
||||
listenToDeleteRoom() {
|
||||
return this.socket.getData<any>().pipe(
|
||||
filter((data): data is SocketMessage<ListenToDeleteRoomInput> =>
|
||||
data?.method === 'UserRemoveGroup'
|
||||
),
|
||||
map((e) => e.data)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SignalRService } from 'src/app/infra/socket/signalR/signal-r.service';
|
||||
import { SessionStore } from 'src/app/store/session.service';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { SocketMessage } from 'src/app/infra/socket/signalR/signalR';
|
||||
import { ITypingRemoteRepository } from 'src/app/core/chat/repository/typing/typing-remote-repository';
|
||||
import { z } from "zod"
|
||||
import { InstanceId } from '../../../domain/chat-service.service';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export const UserTypingDTOSchema = z.object({
|
||||
requestId: z.string(),
|
||||
roomId: z.string(),
|
||||
userId: z.number(),
|
||||
userName: z.string()
|
||||
})
|
||||
export type UserTypingDTO = z.infer<typeof UserTypingDTOSchema>
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserTypingRemoteRepositoryService implements ITypingRemoteRepository {
|
||||
|
||||
constructor(
|
||||
private socket: SignalRService
|
||||
) { }
|
||||
|
||||
sendTyping(roomId: string) {
|
||||
return this.socket.sendData({
|
||||
method: 'Typing',
|
||||
data: {
|
||||
roomId,
|
||||
UserName:SessionStore.user.FullName,
|
||||
userId:SessionStore.user.UserId,
|
||||
requestId: InstanceId +'@'+ uuidv4(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
listenToTyping() {
|
||||
return this.socket.getData().pipe(
|
||||
filter((e) : e is SocketMessage<UserTypingDTO>=> e?.method == 'TypingMessage'
|
||||
),
|
||||
map((e)=> e.data)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { z } from 'zod';
|
||||
import { Dexie, EntityTable, liveQuery, Observable } from 'Dexie';
|
||||
import { err, ok } from 'neverthrow';
|
||||
import { chatDatabase } from 'src/app/infra/database/dexie/service';
|
||||
import { TypingTable, TypingTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/typing';
|
||||
import { ITypingLocalRepository } from 'src/app/core/chat/repository/attachment/attachment-local-repository';
|
||||
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserTypingLocalRepository extends DexieRepository<TypingTable, TypingTable> implements ITypingLocalRepository {
|
||||
|
||||
constructor() {
|
||||
super(chatDatabase.typing, TypingTableSchema)
|
||||
this.clear();
|
||||
}
|
||||
|
||||
|
||||
async clear() {
|
||||
try {
|
||||
const result = await chatDatabase.typing.clear()
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
async addUserTyping(data: TypingTable) {
|
||||
|
||||
data.id = data.roomId + '@' + data.userName
|
||||
try {
|
||||
const result = await chatDatabase.typing.add(data)
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
async removeUserTyping(data: TypingTable) {
|
||||
|
||||
const id = data.roomId + '@' + data.userName
|
||||
try {
|
||||
const result = await chatDatabase.typing.delete(id)
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getUserTypingLive() {
|
||||
return liveQuery(() => chatDatabase.typing.toArray());
|
||||
}
|
||||
|
||||
getUserTypingLiveByRoomId(roomId: string) {
|
||||
return liveQuery(() => chatDatabase.typing
|
||||
.where('roomId')
|
||||
.equals(roomId)
|
||||
.sortBy('id')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import Dexie, { PromiseExtended } from 'Dexie';
|
||||
import { IUserPhotoLocalRepository } from 'src/app/core/chat/repository/user-photo/user-photo-local-repository';
|
||||
import { UserPhotoTable, UserPhotoTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/user-foto';
|
||||
import { chatDatabase } from 'src/app/infra/database/dexie/service';
|
||||
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserPhotoLocalRepository extends DexieRepository<UserPhotoTable, UserPhotoTable> implements IUserPhotoLocalRepository {
|
||||
constructor() {
|
||||
super(chatDatabase.userPhoto, UserPhotoTableSchema)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user