-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
125 changed files
with
11,684 additions
and
2,730 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
name: "Auto Merge Approved PRs" | ||
|
||
on: | ||
pull_request_review: | ||
types: [submitted] | ||
|
||
jobs: | ||
auto_merge: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: "Check Approvals" | ||
id: check | ||
uses: actions/github-script@v6 | ||
with: | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
result-encoding: string | ||
script: | | ||
const pull_number = context.payload.pull_request.number; | ||
const reviews = await github.rest.pulls.listReviews({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
pull_number: pull_number, | ||
}); | ||
const approvals = reviews.data.filter(review => review.state === 'APPROVED'); | ||
const approvalCount = approvals.length; | ||
core.info(`PR #${pull_number} has ${approvalCount} approval(s).`); | ||
return approvalCount >= 2; | ||
- name: "Merge PR" | ||
if: ${{ steps.check.outputs.result == 'true' }} | ||
uses: actions/github-script@v6 | ||
with: | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
script: | | ||
const pull_number = context.payload.pull_request.number; | ||
// PR ์ ๋ณด ๋ฐ ๋ณํฉ ๊ฐ๋ฅ ์ํ ํ์ธ | ||
let pr; | ||
for (let i = 0; i < 5; i++) { // ์ต๋ 5ํ ์ฌ์๋ | ||
const { data } = await github.rest.pulls.get({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
pull_number: pull_number, | ||
}); | ||
pr = data; | ||
if (pr.state !== 'open') { | ||
core.info(`PR #${pull_number} is not open (state: ${pr.state}).`); | ||
return; | ||
} | ||
if (pr.mergeable === true) { | ||
break; // ๋ณํฉ ๊ฐ๋ฅํ๋ฉด ๋ฃจํ ์ข ๋ฃ | ||
} else if (pr.mergeable === false) { | ||
core.info(`PR #${pull_number} cannot be merged due to conflicts or other issues.`); | ||
return; | ||
} else { | ||
// mergeable์ด null์ธ ๊ฒฝ์ฐ (๊ณ์ฐ ์ค), ์ ์ ๋๊ธฐ ํ ์ฌ์๋ | ||
await new Promise(resolve => setTimeout(resolve, 2000)); // 2์ด ๋๊ธฐ | ||
} | ||
} | ||
if (pr.mergeable !== true) { | ||
core.info(`PR #${pull_number} mergeable status is unknown after retries.`); | ||
return; | ||
} | ||
// PR ๋ณํฉ ์๋ | ||
try { | ||
await github.rest.pulls.merge({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
pull_number: pull_number, | ||
}); | ||
core.info(`PR #${pull_number} has been merged successfully.`); | ||
} catch (error) { | ||
core.info(`Failed to merge PR #${pull_number}: ${error.message}`); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
name: Deploy on Server | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
deploy: | ||
runs-on: [self-hosted, boost-was] # ๋ผ๋ฒจ์ ํด๋นํ๋ runner๋ก ์คํ | ||
|
||
steps: | ||
# 1. ๋ ํฌ์งํ ๋ฆฌ ํด๋ก | ||
- name: Checkout Repository | ||
uses: actions/checkout@v4 | ||
|
||
# 2. Docker Compose๋ก ์๋น์ค ๋น๋ ๋ฐ ์ฌ์์ | ||
- name: Build and Deploy Docker Images | ||
env: | ||
NODE_ENV: production | ||
MONGODB_URI: ${{ secrets.MONGODB_URI }} | ||
run: | | ||
docker-compose up -d --build | ||
# 3. Clean up Old Images | ||
- name: Remove Dangling Images | ||
run: docker image prune -f |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
name: Lint and Test | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
- dev | ||
pull_request: | ||
branches: | ||
- main | ||
- dev | ||
|
||
jobs: | ||
lint_and_test: | ||
name: Lint and Test | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
# Checkout the repository | ||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
|
||
# Install pnpm | ||
- name: Install pnpm | ||
uses: pnpm/action-setup@v4 | ||
with: | ||
version: 9 | ||
run_install: true | ||
|
||
# Set up Node.js | ||
- name: Set up Node.js | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: "20" | ||
cache: "pnpm" | ||
|
||
# Run lint | ||
- name: Run lint | ||
run: pnpm eslint . | ||
|
||
# Run tests | ||
- name: Run tests | ||
run: pnpm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
/node_modules | ||
**/node_modules | ||
|
||
/dist | ||
*/dist | ||
/build | ||
.DS_Store | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
{ | ||
"semi": true, | ||
"trailingComma": "all", | ||
"singleQuote": true, | ||
"arrowParens": "always", | ||
"printWidth": 100, | ||
"tabWidth": 2, | ||
"arrowParens": "always", | ||
"endOfLine": "auto", | ||
"bracketSpacing": true | ||
} | ||
"jsxSingleQuote": false, | ||
"singleQuote": false, | ||
|
||
"plugins": ["@pandabox/prettier-plugin"], | ||
"pandaFirstProps": ["as", "className", "layerStyle", "textStyle"], | ||
"pandaStylePropsFirst": true, | ||
"pandaSortOtherProps": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"editor.formatOnSave": true, | ||
"editor.defaultFormatter": "esbenp.prettier-vscode", | ||
"eslint.workingDirectories": [ | ||
{ | ||
"pattern": "./packages/*/" | ||
} | ||
], | ||
"eslint.useFlatConfig": true, | ||
"eslint.validate": ["javascript", "typescript", "javascriptreact", "html", "typescriptreact"], | ||
"eslint.enable": true, | ||
"editor.codeActionsOnSave": { | ||
"source.fixAll.eslint": "always" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { LinkedList } from "./LinkedList"; | ||
import { NodeId, Node } from "./Node"; | ||
import { RemoteInsertOperation, RemoteDeleteOperation, SerializedProps } from "./Interfaces"; | ||
|
||
export class CRDT { | ||
clock: number; | ||
client: number; | ||
textLinkedList: LinkedList; | ||
|
||
constructor(client: number) { | ||
this.clock = 0; // ์ด CRDT์ ๋ ผ๋ฆฌ์ ์๊ฐ ์ค์ | ||
this.client = client; | ||
this.textLinkedList = new LinkedList(); | ||
} | ||
|
||
/** | ||
* ๋ก์ปฌ์์ ์ฝ์ ์ฐ์ฐ์ ์ํํ๊ณ , ์๊ฒฉ์ ์ ํํ ์ฐ์ฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. | ||
* @param index ์ฝ์ ํ ์ธ๋ฑ์ค | ||
* @param value ์ฝ์ ํ ๊ฐ | ||
* @returns ์๊ฒฉ์ ์ ํํ ์ฝ์ ์ฐ์ฐ ๊ฐ์ฒด | ||
*/ | ||
localInsert(index: number, value: string): RemoteInsertOperation { | ||
const id = new NodeId((this.clock += 1), this.client); | ||
const remoteInsertion = this.textLinkedList.insertAtIndex(index, value, id); | ||
return { node: remoteInsertion.node }; | ||
} | ||
|
||
/** | ||
* ๋ก์ปฌ์์ ์ญ์ ์ฐ์ฐ์ ์ํํ๊ณ , ์๊ฒฉ์ ์ ํํ ์ฐ์ฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. | ||
* @param index ์ญ์ ํ ์ธ๋ฑ์ค | ||
* @returns ์๊ฒฉ์ ์ ํํ ์ญ์ ์ฐ์ฐ ๊ฐ์ฒด | ||
*/ | ||
localDelete(index: number): RemoteDeleteOperation { | ||
// ์ ํจํ ์ธ๋ฑ์ค์ธ์ง ํ์ธ | ||
if (index < 0 || index >= this.textLinkedList.spread().length) { | ||
throw new Error(`์ ํจํ์ง ์์ ์ธ๋ฑ์ค์ ๋๋ค: ${index}`); | ||
} | ||
|
||
// ์ญ์ ํ ๋ ธ๋ ์ฐพ๊ธฐ | ||
const nodeToDelete = this.textLinkedList.findByIndex(index); | ||
if (!nodeToDelete) { | ||
throw new Error(`์ญ์ ํ ๋ ธ๋๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ์ธ๋ฑ์ค: ${index}`); | ||
} | ||
|
||
// ์ญ์ ์ฐ์ฐ ๊ฐ์ฒด ์์ฑ | ||
const operation: RemoteDeleteOperation = { | ||
targetId: nodeToDelete.id, | ||
clock: this.clock + 1, | ||
}; | ||
|
||
// ๋ก์ปฌ ์ญ์ ์ํ | ||
this.textLinkedList.deleteNode(nodeToDelete.id); | ||
|
||
// ํด๋ก ์ ๋ฐ์ดํธ | ||
this.clock += 1; | ||
|
||
return operation; | ||
} | ||
|
||
/** | ||
* ์๊ฒฉ์์ ์ฝ์ ์ฐ์ฐ์ ์์ ํ์ ๋ ์ฒ๋ฆฌํฉ๋๋ค. | ||
* @param operation ์๊ฒฉ ์ฝ์ ์ฐ์ฐ ๊ฐ์ฒด | ||
*/ | ||
remoteInsert(operation: RemoteInsertOperation): void { | ||
const newNodeId = new NodeId(operation.node.id.clock, operation.node.id.client); | ||
const newNode = new Node(operation.node.value, newNodeId); | ||
newNode.next = operation.node.next; | ||
newNode.prev = operation.node.prev; | ||
this.textLinkedList.insertById(newNode); | ||
// ๋๊ธฐํ ๋ ผ๋ฆฌ์ ์๊ฐ | ||
if (this.clock <= newNode.id.clock) { | ||
this.clock = newNode.id.clock + 1; | ||
} | ||
} | ||
|
||
/** | ||
* ์๊ฒฉ์์ ์ญ์ ์ฐ์ฐ์ ์์ ํ์๋ ์ฒ๋ฆฌํฉ๋๋ค. | ||
* @param operation ์๊ฒฉ ์ญ์ ์ฐ์ฐ ๊ฐ์ฒด | ||
*/ | ||
remoteDelete(operation: RemoteDeleteOperation): void { | ||
const { targetId, clock } = operation; | ||
if (targetId) { | ||
this.textLinkedList.deleteNode(targetId); | ||
} | ||
// ๋๊ธฐํ ๋ ผ๋ฆฌ์ ์๊ฐ | ||
if (this.clock <= clock) { | ||
this.clock = clock + 1; | ||
} | ||
} | ||
|
||
/** | ||
* ํ์ฌ ํ ์คํธ๋ฅผ ๋ฌธ์์ด๋ก ๋ฐํํฉ๋๋ค. | ||
* @returns ํ์ฌ ํ ์คํธ | ||
*/ | ||
read(): string { | ||
return this.textLinkedList.stringify(); | ||
} | ||
|
||
/** | ||
* ํ์ฌ ํ ์คํธ๋ฅผ ๋ฐฐ์ด๋ก ๋ฐํํฉ๋๋ค. | ||
* @returns ํ์ฌ ํ ์คํธ ๋ฐฐ์ด | ||
*/ | ||
spread(): string[] { | ||
return this.textLinkedList.spread(); | ||
} | ||
|
||
/** | ||
* textLinkedList๋ฅผ ๋ฐํํ๋ getter ๋ฉ์๋ | ||
* @returns LinkedList ์ธ์คํด์ค | ||
*/ | ||
public getTextLinkedList(): LinkedList { | ||
return this.textLinkedList; | ||
} | ||
|
||
/** | ||
* CRDT์ ์ํ๋ฅผ ์ง๋ ฌํ ๊ฐ๋ฅํ ๊ฐ์ฒด๋ก ๋ฐํํฉ๋๋ค. | ||
* @returns ์ง๋ ฌํ ๊ฐ๋ฅํ CRDT ์ํ | ||
*/ | ||
serialize(): SerializedProps { | ||
return { | ||
clock: this.clock, | ||
client: this.client, | ||
textLinkedList: { | ||
head: this.textLinkedList.head, | ||
nodeMap: this.textLinkedList.nodeMap, | ||
}, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { NodeId, Node } from "./Node"; | ||
|
||
export interface InsertOperation { | ||
node: Node; | ||
} | ||
|
||
export interface DeleteOperation { | ||
targetId: NodeId | null; | ||
clock: number; | ||
} | ||
export interface RemoteInsertOperation { | ||
node: Node; | ||
} | ||
|
||
export interface RemoteDeleteOperation { | ||
targetId: NodeId | null; | ||
clock: number; | ||
} | ||
|
||
export interface CursorPosition { | ||
clientId: number; | ||
position: number; | ||
} | ||
|
||
export interface SerializedProps { | ||
clock: number; | ||
client: number; | ||
textLinkedList: { | ||
head: NodeId | null; | ||
nodeMap: { [key: string]: Node }; | ||
}; | ||
} |
Oops, something went wrong.