From eba25a71d98157b2a2a28b5331dc3d409409261a Mon Sep 17 00:00:00 2001 From: hyonun321 Date: Tue, 19 Nov 2024 17:27:21 +0900 Subject: [PATCH] =?UTF-8?q?tset=20=EB=8D=B0=EC=8A=A4=ED=81=AC=ED=83=91?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=EA=B0=80=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yeonkyu Min Co-authored-by: minjungw00 --- server/src/crdt/crdt.module.ts | 4 +- server/src/crdt/crdt.service.ts | 20 +-- server/src/crdt/schemas/document.schema.ts | 181 ++++++++++++++++++--- 3 files changed, 171 insertions(+), 34 deletions(-) diff --git a/server/src/crdt/crdt.module.ts b/server/src/crdt/crdt.module.ts index e9bd8244..1acc5997 100644 --- a/server/src/crdt/crdt.module.ts +++ b/server/src/crdt/crdt.module.ts @@ -1,11 +1,11 @@ import { Module } from "@nestjs/common"; import { CrdtService } from "./crdt.service"; import { MongooseModule } from "@nestjs/mongoose"; -import { Doc, DocumentSchema } from "./schemas/document.schema"; +import { Workspace, WorkspaceSchema } from "./schemas/document.schema"; import { CrdtGateway } from "./crdt.gateway"; @Module({ - imports: [MongooseModule.forFeature([{ name: Doc.name, schema: DocumentSchema }])], + imports: [MongooseModule.forFeature([{ name: Workspace.name, schema: WorkspaceSchema }])], providers: [CrdtService, CrdtGateway], exports: [CrdtService], }) diff --git a/server/src/crdt/crdt.service.ts b/server/src/crdt/crdt.service.ts index a175812f..a6763cc5 100644 --- a/server/src/crdt/crdt.service.ts +++ b/server/src/crdt/crdt.service.ts @@ -1,8 +1,8 @@ import { Injectable, OnModuleInit } from "@nestjs/common"; import { InjectModel } from "@nestjs/mongoose"; -import { Doc, DocumentDocument } from "./schemas/document.schema"; +import { Workspace, WorkspaceDocument } from "./schemas/document.schema"; import { Model } from "mongoose"; -import { BlockCRDT } from "@noctaCrdt/Crdt"; +import { EditorCRDT } from "@noctaCrdt/Crdt"; import { RemoteBlockDeleteOperation, RemoteCharDeleteOperation, @@ -15,16 +15,16 @@ import { Char } from "@noctaCrdt/Node"; @Injectable() export class CrdtService implements OnModuleInit { - private crdt: BlockCRDT; + private crdt: EditorCRDT; - constructor(@InjectModel(Doc.name) private documentModel: Model) { - this.crdt = new BlockCRDT(0); + constructor(@InjectModel(Workspace.name) private documentModel: Model) { + this.crdt = new EditorCRDT(0); } async onModuleInit() { try { const doc = await this.getDocument(); if (doc && doc.crdt) { - this.crdt = new BlockCRDT(0); + this.crdt = new EditorCRDT(0); try { // 저장된 CRDT 상태를 복원 this.crdt.clock = doc.crdt.clock; @@ -52,7 +52,7 @@ export class CrdtService implements OnModuleInit { reconstructedNode.prev = new CharId(node.prev.clock, node.prev.client); } - this.crdt.LinkedList.nodeMap[key] = reconstructedNode; + this.crdt.LinkedList.nodeMap[??].crdt.LinkedList.nodeMap[key] = reconstructedNode; } } catch (e) { console.error("Error reconstructing CRDT:", e); @@ -62,7 +62,7 @@ export class CrdtService implements OnModuleInit { console.error("Error during CrdtService initialization:", error); } } - async getDocument(): Promise { + async getDocument(): Promise { let doc = await this.documentModel.findOne(); if (!doc) { doc = new this.documentModel({ crdt: this.crdt.serialize() }); @@ -71,7 +71,7 @@ export class CrdtService implements OnModuleInit { return doc; } - async updateDocument(): Promise { + async updateDocument(): Promise { const serializedCRDT = this.crdt.serialize(); const doc = await this.documentModel.findOneAndUpdate( {}, @@ -100,7 +100,7 @@ export class CrdtService implements OnModuleInit { return this.crdt.read(); } - getCRDT(): BlockCRDT { + getCRDT(): EditorCRDT { return this.crdt; } } diff --git a/server/src/crdt/schemas/document.schema.ts b/server/src/crdt/schemas/document.schema.ts index 0c0c172c..13e2a78a 100644 --- a/server/src/crdt/schemas/document.schema.ts +++ b/server/src/crdt/schemas/document.schema.ts @@ -1,31 +1,168 @@ import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; import { Document } from "mongoose"; -export type DocumentDocument = Document & Doc; +// CharId Schema +@Schema({ _id: false }) +export class CharId { + @Prop({ required: true }) + clock: number; + @Prop({ required: true }) + client: number; +} + +// Char Schema +@Schema({ _id: false }) +export class Char { + @Prop({ type: CharId, required: true }) + id: CharId; + + @Prop({ required: true }) + content: string; + + @Prop({ type: () => Block, default: null }) + next: Block | null; + + @Prop({ type: () => Block, default: null }) + prev: Block | null; +} + +// TextLinkedList Schema +@Schema({ _id: false }) +export class TextLinkedList { + @Prop({ type: CharId, default: null }) + head: CharId | null; + + @Prop({ type: Map, of: Char }) + charMap: Map; +} + +// BlockId Schema +@Schema({ _id: false }) +export class BlockId { + @Prop({ required: true }) + clock: number; + + @Prop({ required: true }) + client: number; +} + +// BlockCRDT Schema +@Schema({ _id: false }) +export class BlockCRDT { + @Prop({ required: true }) + clock: number; + + @Prop({ required: true }) + client: number; + + @Prop({ required: true }) + currentCaret: number; + + @Prop({ type: TextLinkedList, required: true }) + LinkedList: TextLinkedList; +} + +// Block Schema +@Schema({ _id: false }) +export class Block { + @Prop({ type: BlockCRDT, required: true }) + crdt: BlockCRDT; + + @Prop({ type: BlockId, required: true }) + id: BlockId; + + @Prop({ + type: String, + enum: ["checkBox", "list"], + required: true, + }) + icon: string; + + @Prop({ + type: String, + enum: ["h1", "h2"], + required: true, + }) + type: string; + + @Prop({ required: true }) + indent: number; + + @Prop({ type: () => Block, default: null }) + next: Block | null; + + @Prop({ type: () => Block, default: null }) + prev: Block | null; + + @Prop({ + type: String, + enum: ["wave", "fill"], + required: true, + }) + animation: string; + + @Prop({ type: [String], default: [] }) + style: string[]; +} + +// BlockLinkedList Schema +@Schema({ _id: false }) +export class BlockLinkedList { + @Prop({ type: BlockId, default: null }) + head: BlockId | null; + + @Prop({ type: Map, of: Block }) + blockMap: Map; +} + +// EditorCRDT Schema +@Schema({ _id: false }) +export class EditorCRDT { + @Prop({ required: true }) + clock: number; + + @Prop({ required: true }) + client: number; + + @Prop({ type: Block }) + currentBlock: Block; + + @Prop({ type: BlockLinkedList, required: true }) + LinkedList: BlockLinkedList; +} + +// Page Schema +@Schema({ _id: false }) +export class Page { + @Prop({ required: true }) + id: string; + + @Prop({ required: true }) + title: string; + + @Prop({ required: true }) + icon: string; + + @Prop({ type: EditorCRDT, required: true }) + crdt: EditorCRDT; +} + +// Main Workspace Document Schema @Schema({ minimize: false }) -export class Doc { - @Prop({ type: Object, required: true }) - crdt: { - clock: number; - client: number; - LinkedList: { - head: { - clock: number; - client: number; - } | null; - nodeMap: { - [key: string]: { - id: { clock: number; client: number }; - value: string; - next: { clock: number; client: number } | null; - prev: { clock: number; client: number } | null; - }; - }; - }; - }; +export class Workspace { + @Prop({ required: true }) + id: string; + + @Prop({ type: [Page], default: [] }) + pageList: Page[]; + + @Prop({ type: Map, of: Object }) + authUser: Map; @Prop({ default: Date.now }) updatedAt: Date; } -export const DocumentSchema = SchemaFactory.createForClass(Doc); + +export type WorkspaceDocument = Document & Workspace; +export const WorkspaceSchema = SchemaFactory.createForClass(Workspace);