Skip to content

Commit

Permalink
Implemented Commit Revision
Browse files Browse the repository at this point in the history
  • Loading branch information
R1c4rdCo5t4 committed Jun 24, 2024
1 parent 71bb057 commit 4d17d34
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 25 deletions.
6 changes: 4 additions & 2 deletions code/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import AuthProvider from '@/contexts/auth/AuthContext';
import Profile from '@ui/pages/profile/Profile';
import Landing from '@ui/pages/landing/Landing';
import Search from '@ui/pages/search/Search';
import DocumentCommits from '@ui/pages/document/components/commit-history/CommitHistory';
import CommitHistory from '@ui/pages/document/components/commit-history/CommitHistory';
import Commit from '@ui/pages/document/components/commit/Commit';

function App() {
return (
Expand Down Expand Up @@ -67,7 +68,8 @@ function App() {
<Routes>
<Route path="/" element={<Workspace />} />
<Route path="/:id" element={<Document />} />
<Route path="/:id/commits" element={<DocumentCommits />} />
<Route path="/:id/commits" element={<CommitHistory />} />
<Route path="/:id/commits/:commitId" element={<Commit />} />
</Routes>
</WorkspaceProvider>
}
Expand Down
17 changes: 11 additions & 6 deletions code/client/src/services/commits/commitsService.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { HttpCommunication } from '@services/communication/http/httpCommunication';
import { Commit } from '@notespace/shared/src/document/types/commits';
import { Commit, CommitData } from '@notespace/shared/src/document/types/commits';

function commitsService(http: HttpCommunication, wid: string, id: string) {
async function commit() {
return await http.post(`/workspaces/${wid}/${id}/commit`);
}

async function getCommits(): Promise<Commit[]> {
return await http.get(`/workspaces/${wid}/${id}/commits`);
}

async function rollback(commitId: string) {
return await http.post(`/workspaces/${wid}/${id}/rollback`, { commitId });
}
Expand All @@ -18,11 +14,20 @@ function commitsService(http: HttpCommunication, wid: string, id: string) {
return await http.post(`/workspaces/${wid}/${id}/fork`, { commitId });
}

async function getCommits(): Promise<Commit[]> {
return await http.get(`/workspaces/${wid}/${id}/commits`);
}

async function getCommit(commitId: string): Promise<CommitData> {
return await http.get(`/workspaces/${wid}/${id}/commits/${commitId}`);
}

return {
commit,
getCommits,
rollback,
fork,
getCommits,
getCommit,
};
}

Expand Down
2 changes: 1 addition & 1 deletion code/client/src/ui/hooks/useLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react';
import Spinner from '@ui/components/spinner/Spinner';

function useLoading() {
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(true);

const startLoading = useCallback(() => setLoading(true), []);
const stopLoading = useCallback(() => setLoading(false), []);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
.document-commits {
.commits-list {
display: flex;
flex-direction: column;
gap: 1vh;
}

.commit {
padding: 0.5vh 8vh;
border-radius: 10px;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './CommitHistory.scss';
import useCommitsService from '@services/commits/useCommitsService';
import { Link, useParams } from 'react-router-dom';
import { Link, useNavigate, useParams } from 'react-router-dom';
import useResourcesService from '@services/resource/useResourcesService';
import { useEffect, useState } from 'react';
import useLoading from '@ui/hooks/useLoading';
Expand All @@ -14,9 +14,10 @@ function CommitHistory() {
const [document, setDocument] = useState<DocumentResource>();
const [commits, setCommits] = useState<Commit[]>([]);
const { loading, spinner, startLoading, stopLoading } = useLoading();
const { id } = useParams();
const { wid, id } = useParams();
const { getResource } = useResourcesService();
const { getCommits, fork, rollback } = useCommitsService();
const navigate = useNavigate();

useEffect(() => {
async function fetchDocument() {
Expand All @@ -25,6 +26,7 @@ function CommitHistory() {
}
async function fetchCommits() {
const commits = await getCommits();
console.log(commits);
setCommits(commits);
}
startLoading();
Expand All @@ -43,7 +45,11 @@ function CommitHistory() {
<div className="commits-list">
{commits.length > 0 ? (
commits.map(commit => (
<div key={commit.id} className="commit">
<button
key={commit.id}
className="commit"
onClick={() => navigate(`/workspaces/${wid}/${id}/commits/${commit.id}`)}
>
<p>
<Link to={`/profile/${commit.author.id}`}>{commit.author.name}</Link> committed{' '}
{formatTimePassed(new Date(commit.timestamp).toLocaleString())}
Expand All @@ -58,7 +64,7 @@ function CommitHistory() {
Fork
</button>
</div>
</div>
</button>
))
) : (
<p>No commits yet</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.commit {
}
46 changes: 46 additions & 0 deletions code/client/src/ui/pages/document/components/commit/Commit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import useCommitsService from '@services/commits/useCommitsService';
import { useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import { type Commit, CommitData } from '@notespace/shared/src/document/types/commits';
import useLoading from '@ui/hooks/useLoading';
import { formatTimePassed } from '@/utils/utils';
import useFugue from '@domain/editor/fugue/useFugue';

function Commit() {
const { loading, startLoading, stopLoading, spinner } = useLoading();
const [commit, setCommit] = useState<CommitData>();
const { getCommit } = useCommitsService();
const { commitId } = useParams();
const fugue = useFugue();

useEffect(() => {
async function fetchCommit() {
const commit = await getCommit(commitId!);
console.log(commit);
setCommit(commit);
fugue.applyOperations(commit.content, true);
stopLoading();
}
startLoading();
fetchCommit();
}, [commitId, getCommit, startLoading, stopLoading]);

return (
<div className="commit-revision">
{loading ? (
spinner
) : (
<>
<h2>Commit</h2>
<p>
Committed by <Link to={`/profile/${commit!.author.id}`}>{commit!.author.name}</Link>
&nbsp;{formatTimePassed(new Date(commit!.timestamp).toLocaleString())}
</p>
<pre>{fugue.toString()}</pre>
</>
)}
</div>
);
}

export default Commit;
7 changes: 7 additions & 0 deletions code/server/src/controllers/http/handlers/commitsHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,18 @@ function resourcesHandlers(
httpResponse.ok(res).json(commits);
};

const getCommit = async (req: Request, res: Response) => {
const { id, commitId } = req.params;
const commit = await service.getCommit(id, commitId);
httpResponse.ok(res).json(commit);
};

const router = PromiseRouter({ mergeParams: true });
router.post('/commit', enforceAuth, workspaceWritePermissions, commit);
router.post('/rollback', enforceAuth, workspaceWritePermissions, rollback);
router.post('/fork', enforceAuth, workspaceWritePermissions, fork);
router.get('/commits', getCommits);
router.get('/commits/:commitId', getCommit);

return router;
}
Expand Down
5 changes: 3 additions & 2 deletions code/server/src/databases/firestore/FirestoreCommitsDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export class FirestoreCommitsDB implements CommitsRepository {
if (!data) throw new NotFoundError(`Commit not found`);
const commitData = data[commitId];
if (!commitData) throw new NotFoundError(`Commit not found`);
return { id: commitId, content: commitData.content, timestamp: commitData.timestamp };
const { content, timestamp, author } = commitData;
return { id: commitId, content, timestamp, author };
}

async getCommits(id: string): Promise<Commit[]> {
Expand All @@ -26,7 +27,7 @@ export class FirestoreCommitsDB implements CommitsRepository {
const data = snapshot.data();
if (!data) throw new NotFoundError(`Commits not found`);
return Object.entries(data)
.map(([id, { content, timestamp }]) => ({ id, content, timestamp }))
.map(([id, { content, timestamp, author }]) => ({ id, content, timestamp, author }))
.sort((a, b) => a.timestamp - b.timestamp);
}
}
2 changes: 1 addition & 1 deletion code/server/src/databases/memory/MemoryCommitsDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class MemoryCommitsDB implements CommitsRepository {
}

async getCommits(id: string): Promise<Commit[]> {
return Object.values(this.commits[id]);
return Object.values(this.commits[id]).sort((a, b) => a.timestamp - b.timestamp);
}

async saveCommit(id: string, commit: Commit): Promise<void> {
Expand Down
29 changes: 20 additions & 9 deletions code/server/src/services/DocumentsService.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Operation } from '@notespace/shared/src/document/types/operations';
import { Databases } from '@databases/types';
import { decodeFromBase64, encodeToBase64 } from '@services/utils';
import { Author, Commit } from '@notespace/shared/src/document/types/commits';
import { Resource, ResourceType } from '@notespace/shared/src/workspace/types/resource';
import { decodeFromBase64, encodeToBase64, getRandomId } from '@services/utils';
import { Author, Commit, CommitData } from '@notespace/shared/src/document/types/commits';
import { DocumentResource, Resource, ResourceType } from '@notespace/shared/src/workspace/types/resource';

const COMMIT_ID_LENGTH = 8;

export class DocumentsService {
private readonly databases: Databases;
Expand All @@ -24,7 +26,8 @@ export class DocumentsService {
const document = await this.databases.documents.getDocument(resource.workspace, id);
// save operations in new commit
const content = encodeToBase64(document.operations);
const commit: Commit = { id: resource.id, content, timestamp: Date.now(), author };
const commitId = getRandomId(COMMIT_ID_LENGTH);
const commit: Commit = { id: commitId, content, timestamp: Date.now(), author };
await this.databases.commits.saveCommit(id, commit);
}

Expand All @@ -38,14 +41,14 @@ export class DocumentsService {
await this.databases.documents.updateDocument(resource.workspace, id, operations, true);
}

async fork(id: string, commitId: string) {
async fork(id: string, commitId: string): Promise<DocumentResource> {
// get operations from commit
const resource = await this.getDocumentResource(id);
const commit = await this.databases.commits.getCommit(id, commitId);
const operations = decodeFromBase64(commit.content) as Operation[];

// create new document with operations
const newResource: Resource = {
const newResource: DocumentResource = {
...resource,
id,
name: `${resource.name}-forked`,
Expand All @@ -62,16 +65,24 @@ export class DocumentsService {
return newResource;
}

async getCommits(id: string) {
async getCommits(id: string): Promise<Commit[]> {
// check if document exists
await this.getDocumentResource(id);
// get all commits of a document
return await this.databases.commits.getCommits(id);
}

private async getDocumentResource(id: string) {
async getCommit(id: string, commitId: string): Promise<CommitData> {
// check if document exists
await this.getDocumentResource(id);
// get commit of a document
const commit = await this.databases.commits.getCommit(id, commitId);
return { ...commit, content: decodeFromBase64(commit.content) as Operation[] };
}

private async getDocumentResource(id: string): Promise<DocumentResource> {
const resource = await this.databases.resources.getResource(id);
if (resource.type !== ResourceType.DOCUMENT) throw new Error('Resource is not a document');
return resource;
return resource as DocumentResource;
}
}
6 changes: 6 additions & 0 deletions code/server/src/services/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { InvalidParameterError } from '@domain/errors/errors';
import { randomBytes } from 'crypto';

const MIN_NAME_LENGTH = 2;
const MIN_ID_LENGTH = 16;
Expand Down Expand Up @@ -37,3 +38,8 @@ export function decodeFromBase64(base64String: string): any {
const jsonString = Buffer.from(base64String, 'base64').toString('utf8');
return JSON.parse(jsonString);
}

export function getRandomId(length: number): string {
const randomBuffer = randomBytes(length);
return randomBuffer.toString('hex');
}
6 changes: 6 additions & 0 deletions code/shared/src/document/types/commits.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { Operation } from "./operations";

export type Commit = {
id: string;
content: string;
timestamp: number;
author: Author;
};

export type CommitData = Omit<Commit, "content"> & {
content: Operation[];
};

export type Author = {
id: string;
name: string;
Expand Down

0 comments on commit 4d17d34

Please sign in to comment.