merge chat

This commit is contained in:
Peter Maquiran
2024-09-13 12:27:39 +01:00
471 changed files with 17419 additions and 53900 deletions
-3
View File
@@ -1,3 +0,0 @@
import { Animation, AnimationController } from '@ionic/angular';
/* export const enterAnimation = (base) */
-30
View File
@@ -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
+3 -10
View File
@@ -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
View File
@@ -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]
-8
View File
@@ -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';
}
-29
View File
@@ -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
View File
@@ -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
// }
}
+30
View File
@@ -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)
}
}
+72
View File
@@ -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
}
}
+33
View File
@@ -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'
}
}
+116
View File
@@ -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>
}
@@ -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
}
}
+6 -45
View File
@@ -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]
},
+1 -1
View File
@@ -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>
+8 -8
View File
@@ -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) {
@@ -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', () => {
+37 -1
View File
@@ -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'
+43
View File
@@ -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)
}
}
}
+14
View File
@@ -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>>
}
+17
View File
@@ -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 {}
+128
View File
@@ -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';
}
+17
View File
@@ -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>>
+28
View File
@@ -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
}))
}
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from './adapter';
// export * from './types';
+56
View File
@@ -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
}
+14
View File
@@ -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()
}
}
+207
View File
@@ -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));
}
}
}
+50
View File
@@ -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>
+129
View File
@@ -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();
}
}
+10
View File
@@ -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>;
+105
View File
@@ -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);
}
}
}
+11
View File
@@ -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.');
}
}); */
}
}
-64
View File
@@ -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)
}
}
-116
View File
@@ -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
-390
View File
@@ -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;
}
-7
View File
@@ -1,7 +0,0 @@
import { SearchDocument } from './search-document';
describe('SearchDocument', () => {
it('should create an instance', () => {
expect(new SearchDocument()).toBeTruthy();
});
});
+31
View File
@@ -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
]
}
+192
View File
@@ -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)
}
}
@@ -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`);
}
}
@@ -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