From 5fbf97198dac882c33f8a9927df411a22c4bbbe2 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Fri, 19 Jul 2024 20:50:42 +0200 Subject: [PATCH] build chatModel --- bun.lockb | Bin 11435 -> 11806 bytes package.json | 4 +- src/Model/chatModel.ts | 211 ++++++++++++++++++++++++++--------- src/Model/connectionModel.ts | 2 +- src/Model/storageModel.ts | 55 +++++---- src/Upgrade/v1.ts | 12 +- src/typeSafety.ts | 5 + 7 files changed, 207 insertions(+), 82 deletions(-) create mode 100644 src/typeSafety.ts diff --git a/bun.lockb b/bun.lockb index ae69f19f65d86f4fd716cab48bef96379fcf5132..9f4cce5b645e94a248d30d619182af8ef3726ad8 100755 GIT binary patch delta 2045 zcmcgtYfKzf6ux)5TlNL)E;}rO;Xsc<$#$+SL^q~YjcOISgzx5__zVDpx z+}F&Vb3bZ4vHM79?e6M7>mQnGIpiMg-?3@(w_T?!!Oy=s-*MoNf$W;+_~286B}8hz zA<^Gc;FBHAc6IGcwKhQ?bafH~>PatL?oQ<~mN5~dQ?L#K_m_#P$ zS@de(24r&Hsavr{WLu@a6{}UX51BFwGI?HWnmS$sGCB4RGI_j3WO9#?fsjqocn6Wm zd1J`rJd06{H6WALN8bX$7&ZQc{=LdOiS*#22zyw&9dpq4O3}NDcz*8kM0kkE?f=M~ z8laGD_}f*GJ+sm_eW_@rf9!|8_SW|IAHN)43_Cj~-tB7nW#Jk4zWts{g`xSmg$-Lj z$ouqk^O6^TeJB3xI(XG-Fobc$bUf}@0UXfF>kOY^fR&HLw6lt1k;Ig(>qmoaj7dSBWwS6A4pt@!`y{ww zUvmayzbnbhLHO2J>;HV2#R@UL;fQ#OMs9&g1 z3*}^5-O~jHb*& zQ?5{<-W}7!R`1tqlG`+0x?XVUMfNoj`pU1vn&RMKBsa&S-bbz3%f?K<OG$-(8^ zob~Djc33yEui3WYpq8LsabepFk6jt9Z`G7;Cz>S#w>}Pr?%cfhOR2 zwCCWBK*SWwlfPFi4`u>Yrnuq`lz=CgYp&|-j_>U#Wp^@E1q MzDFZCGwZShp_muDXTxqA9DmVGjJz(Ao_*s`@c4(+7l{Hi0iv}4i8Tzp|tTDT2S(QkZNst`)fE#D`jo> z*uR3cRkxxx#@ckN3ziW*zv^3rwL_1#6v+|8ungU0SjJelUFtMghVLw_NA$6atI?L? z)mdN}y3b)5^SMi;Z#OJs-LtTaoqiANVST;^htwT`W#}%i`rJ-)KE9I*&b;#+`i;0R z7j-Gk!#EHVk7$A;ctnbbb_W;<@;@zvEMk@V><0Y%L5=nA#7XsC+;*dF<_>QQ{0F~& zQkru2(@UQ1H0bftHP3c#qgt=Vi|7TQoxTAUQ)8*dOXy{wgXV!wYVm2@MI%5rEdV`q zf0@R;^d_*Beg*nyn_uH)GzRq3AHV>$2Q*$z7l1+HP+klh=dR$ zZ?$rL5c= z0_&UkD>ue0)f}hqQjv|a)OUyfa{3a%WlJD`20y;h|Jzu8`9mvGLfLdQCVw7H{-Hhd zA9QqEah1B#QQk^Np_0FzndM`N=iYggEL9&}gldSsNH@CzX}k-xKpNQ)q>4(); + objects = new Map(); + outbox = new Set(); - constructor(storageModel: StorageModel, chatId: string) { - const infoPath = storageKeys.chatInfo(chatId) - const info = storageModel.restoreStringifiable(infoPath); + // paths + get infoPath(): string { + return storageKeys.chatInfo(this.id); + } + + get messageDirPath(): string { + return storageKeys.chatMessages(this.id); + } + + get objectDirPath(): string { + return storageKeys.chatObjects(this.id); + } + + get outboxDirPath(): string { + return storageKeys.chatOutbox(this.id); + } + + getMessagePath(id: string): string { + return StorageModel.join(this.messageDirPath, id); + } + + getObjectPath(id: string): string { + return StorageModel.join(this.objectDirPath, id); + } + + // store & add + storeInfo(): void { + this.storageModel.storeStringifiable(this.infoPath, this.info); + } + + addMessage(message: ChatMessage): void { + if (!this.messages.has(message)) { + this.messages.add(message); } + const messagePath: string = this.getMessagePath(message.id); + this.storageModel.storeStringifiable(messagePath, message); + } + + addObject(object: ChatObject): void { + this.objects.set(object.id, object); + const objectPath: string = this.getObjectPath(object.id); + this.storageModel.storeStringifiable(objectPath, object); + } + + // restore + restoreInfo(): void { + const info: any = this.storageModel.restoreStringifiable(this.infoPath); + this.info == info ?? generateChatInfo("0"); + } + + restoreMessages(): void { + const messageIds: string[] = this.storageModel.list(this.messageDirPath); + if (!Array.isArray(messageIds)) return; + + for (const messageId of messageIds) { + const messagePath: string = this.getMessagePath(messageId); + const message: any = this.storageModel.restoreStringifiable(messagePath); + if (checkIsValidObject(message) == false) return; + + this.messages.add(message); + } + } + + restoreObjects(): void { + const objectIds: string[] = this.storageModel.list(this.objectDirPath); + if (!Array.isArray(objectIds)) return; + + for (const objectId of objectIds) { + const objectPath: string = this.getObjectPath(objectId); + const object: any = this.storageModel.restoreStringifiable(objectPath); + if (checkIsValidObject(object) == false) return; + if (object.id != objectId) return; + + this.objects.set(objectId, object); + } + } + + // init + constructor(storageModel: StorageModel, chatId: string) { + this.id = chatId; + this.storageModel = storageModel; + + this.restoreInfo(); + this.restoreMessages(); + this.restoreObjects(); + } } // creation methods -export function createChatMessage(channel: string, sender: string, body: string): ChatMessage { - return { - dataVersion: "v2", +export function generateChatInfo(primaryChannel: string): ChatInfo { + return { + primaryChannel, + secondaryChannels: [], + encryptionKey: "", + hasUnreadMessages: false, + }; +} - channel, - sender, - body, - dateSent: createTimestamp(), - } +export function createChatMessage( + channel: string, + sender: string, + body: string +): ChatMessage { + return { + dataVersion: "v2", + + id: v4(), + + channel, + sender, + body, + dateSent: createTimestamp(), + }; } // types export interface ChatInfo { - primaryChannel: string; - secondaryChannels: string[]; - encryptionKey: string; - - hasUnreadMessages: boolean; + primaryChannel: string; + secondaryChannels: string[]; + encryptionKey: string; + + hasUnreadMessages: boolean; } export interface ChatMessage { - dataVersion: "v2"; - - channel: string; - sender: string; - body: string; - dateSent: string; - - stringifiedObject?: string; - } - - export interface ChatObject { - dataVersion: "v2"; - - id: string; - title: string; - - contentVersions: { [key: string]: MessageObjectContent }; - } - - export interface MessageObjectContent { - dataVersion: "v2"; - - id: string; - creationDate: string; - - noteText?: string; - priority?: number; - category?: string; - status?: string; - date?: string; - time?: string; - } - \ No newline at end of file + dataVersion: "v2"; + + id: string; + + channel: string; + sender: string; + body: string; + dateSent: string; + + stringifiedObject?: string; +} + +export interface ChatObject { + dataVersion: "v2"; + + id: string; + title: string; + + contentVersions: { [key: string]: ChatObjectContent }; +} + +export interface ChatObjectContent { + dataVersion: "v2"; + + id: string; + creationDate: string; + + noteText?: string; + priority?: number; + category?: string; + status?: string; + date?: string; + time?: string; +} diff --git a/src/Model/connectionModel.ts b/src/Model/connectionModel.ts index a2bd14ef..26aa19cd 100644 --- a/src/Model/connectionModel.ts +++ b/src/Model/connectionModel.ts @@ -33,7 +33,7 @@ export default class ConnectionModel { // messaging sendChatMessage = (chatMessage: ChatMessage): boolean => { - const stringifiedBody = stringify(chatMessage); + const stringifiedBody: string = stringify(chatMessage); return this.udn.sendMessage(chatMessage.channel, stringifiedBody); }; diff --git a/src/Model/storageModel.ts b/src/Model/storageModel.ts index 17594151..1dd9ad60 100644 --- a/src/Model/storageModel.ts +++ b/src/Model/storageModel.ts @@ -2,13 +2,15 @@ import { parse, stringify } from "../utility"; +import { DATA_VERSION } from "../typeSafety"; + export default class StorageModel { storageEntryTree: StorageEntry = {}; // basic store = (key: string, value: string): void => { localStorage.setItem(key, value); - const pathComponents = this.keyToPathComponents(key); + const pathComponents: string[] = StorageModel.keyToPathComponents(key); this.updateTree(...pathComponents); }; @@ -21,9 +23,9 @@ export default class StorageModel { }; list = (key: string): string[] => { - const pathComponents = this.keyToPathComponents(key); - - let currentParent = this.storageEntryTree; + const pathComponents: string[] = StorageModel.keyToPathComponents(key); + + let currentParent: StorageEntry = this.storageEntryTree; for (const component of pathComponents) { currentParent = currentParent[component]; } @@ -32,13 +34,13 @@ export default class StorageModel { }; // stringifiable - storeStringifiable = (key: string, value: any[]): void => { - const valueString = stringify(value); + storeStringifiable = (key: string, value: any): void => { + const valueString: string = stringify(value); this.store(key, valueString); }; restoreStringifiable = (key: string): any | null => { - const valueString = this.restore(key); + const valueString: string | null = this.restore(key); if (!valueString) return null; return parse(valueString); @@ -47,14 +49,14 @@ export default class StorageModel { // init constructor() { for (const key of Object.keys(localStorage)) { - const components = this.keyToPathComponents(key); + const components: string[] = StorageModel.keyToPathComponents(key); this.updateTree(...components); } } // utility updateTree = (...pathComponents: string[]) => { - let currentParent = this.storageEntryTree; + let currentParent: StorageEntry = this.storageEntryTree; for (const pathPart of pathComponents) { if (!currentParent[pathPart]) { currentParent[pathPart] = {}; @@ -64,13 +66,22 @@ export default class StorageModel { } }; - pathComponentsToKey = (...pathComponents: string[]): string => { + static pathComponentsToKey = (...pathComponents: string[]): string => { return pathComponents.join("/"); }; - keyToPathComponents = (key: string): string[] => { + static keyToPathComponents = (key: string): string[] => { return key.split("/"); }; + + static join = (...items: string[]): string => { + let allComponents: string[] = []; + for (const item of items) { + const parts = this.keyToPathComponents(item); + allComponents.push(...parts); + } + return StorageModel.pathComponentsToKey(...allComponents); + }; } // types @@ -79,21 +90,21 @@ export type StorageEntry = { [key: string]: StorageEntry }; // keys export const storageKeys = { // connection - socketAddress: "v2/connection/socket-address", + socketAddress: `${DATA_VERSION}/connection/socket-address`, // settings - userName: "v2/settings/user-name", - firstDayOfWeek: "v2/settings/first-day-of-week", + userName: `${DATA_VERSION}/settings/user-name`, + firstDayOfWeek: `${DATA_VERSION}/settings/first-day-of-week`, // history - previousAddresses: "v2/history/previous-addresses", - previousObjectCategories: "v2/history/object-categories", - previousObjectStatuses: "v2/history/object-statuses", - previousObjectFilters: "v2/history/object-filters", + previousAddresses: `${DATA_VERSION}/history/previous-addresses`, + previousObjectCategories: `${DATA_VERSION}/history/object-categories`, + previousObjectStatuses: `${DATA_VERSION}/history/object-statuses`, + previousObjectFilters: `${DATA_VERSION}/history/object-filters`, // chat etc - chatInfo: (id: string) => `v2/chat/${id}/info`, - chatMessages: (id: string) => `v2/chat/${id}/messages`, - chatObjects: (id: string) => `v2/chat/${id}/objects`, - chatOutbox: (id: string) => `v2/chat/${id}/outbox`, + chatInfo: (id: string) => `${DATA_VERSION}/chat/${id}/info`, + chatMessages: (id: string) => `${DATA_VERSION}/chat/${id}/messages`, + chatObjects: (id: string) => `${DATA_VERSION}/chat/${id}/objects`, + chatOutbox: (id: string) => `${DATA_VERSION}/chat/${id}/outbox`, }; diff --git a/src/Upgrade/v1.ts b/src/Upgrade/v1.ts index 31d1d548..b4408522 100644 --- a/src/Upgrade/v1.ts +++ b/src/Upgrade/v1.ts @@ -1,8 +1,10 @@ import { ChatMessage, ChatObject, - MessageObjectContent, -} from "../Model/chatTypes"; + ChatObjectContent, +} from "../Model/chatModel"; + +import { v4 } from "uuid"; // ChatMessage export interface ChatMessageV1 { @@ -21,6 +23,8 @@ export function upgradeMessageFromV1( return { dataVersion: "v2", + id: v4(), + channel, sender, body, @@ -41,7 +45,7 @@ export function upgradeMessageObjectFromV1( ): ChatObject { const { id, title, contentVersions } = messageObjectV1; - const upgradedContentVersions: { [key: string]: MessageObjectContent } = {}; + const upgradedContentVersions: { [key: string]: ChatObjectContent } = {}; for (const entry of Object.entries(contentVersions)) { const [id, object] = entry; const upgraded = upgradeMessageObjectContentFromV1(object); @@ -73,7 +77,7 @@ export interface MessageObjectContentV1 { export function upgradeMessageObjectContentFromV1( messageObjectContentV1: MessageObjectContentV1 -): MessageObjectContent { +): ChatObjectContent { const { isoDateVersionCreated, id, diff --git a/src/typeSafety.ts b/src/typeSafety.ts new file mode 100644 index 00000000..c9becd2f --- /dev/null +++ b/src/typeSafety.ts @@ -0,0 +1,5 @@ +export const DATA_VERSION = "v2" + +export function checkIsValidObject(object: any): boolean { + return object.dataVersion == DATA_VERSION; +}