Skip to content

Commit

Permalink
# This is a combination of 5 commits.
Browse files Browse the repository at this point in the history
# This is the 1st commit message:

Merge

# This is the commit message #2:

Update readme

# This is the commit message #3:

Only writting users' pointer are displayed

# This is the commit message #4:

Automatically collects notes older than 2 weeks

# This is the commit message #5:

typo
  • Loading branch information
jdeschena committed Aug 13, 2022
1 parent 0d77d56 commit a016358
Show file tree
Hide file tree
Showing 13 changed files with 510 additions and 184 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ When professor opens lecture notes:
* [X] Optimize live collaboration (send only stroke modification, not entire stroke every time)
* [ ] Careful about storing collaborators in memory in Server, probably better to use mongo when lots of users
* [ ] When undoing stroke erase, insert stroke back to its initial position, not as a new stroke
* [] Bug: creating file does not always reload the UI: database ordering issue, know how to fix properly, will do soon
* [X] Bug: creating file does not always reload the UI: database ordering issue, know how to fix properly, will do soon
* [ ] Load stokes in small batches, not all at once, better for user experience
* [X] Memory leak, when a user refreshes the page, the socket is not closed => leaking memory. It is reset when browser is closed but not on refresh => easy way to spam and crash the server is to spam refresh

# License

Expand Down
2 changes: 1 addition & 1 deletion client/canvas/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default class App {
this.viewManager = new ViewManager();
this.viewManager.getView().onUpdate(() => this.scheduleRender());

this.network = new NetworkConnection();
this.network = new NetworkConnection(isPremiumNote);
this.network.onUpdate(() => this.scheduleRender());

this.pointerInput = new PointerTracker();
Expand Down
13 changes: 11 additions & 2 deletions client/canvas/Network/NetworkCanvasManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ export class NetworkCanvasManager extends CanvasManager {

this.network = network;

const wloc = window.location.pathname.match(/\/note\/([\w\d_]+)/);
const docId = (wloc && wloc[1]) || "";
const pathname = window.location.pathname
const authWloc = pathname.match(/\/auth-note\/([\w\d_]+)/)
const freeWloc = pathname.match(/\/free-note\/([\w\d_]+)/)


const docId = (
(authWloc && authWloc[1]) ||
(freeWloc && freeWloc[1]) ||
""
)
//network.close()

network.emit("request document", docId);

Expand Down
38 changes: 36 additions & 2 deletions client/canvas/Network/NetworkConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Observable } from "../DesignPatterns/Observable";
import { DeserializeTool } from "../Tools/DeserializeTool";
import { Tool } from "../Tools/Tool";
import { Vector2D } from "../types";
import { authCookieName, getAuthToken, setAuthToken, disconnect } from "../../components/AuthToken.js";


const SERVER_PORT = 8080;

Expand All @@ -17,13 +19,40 @@ export class NetworkConnection extends Observable {
constructor() {
super();

this.socket = io(`${window.location.host.split(":")[0]}:${SERVER_PORT}`);
const pathname = window.location.pathname
const authWloc = pathname.match(/\/auth-note\/([\w\d_]+)/)

const isAuth = (authWloc && authWloc[1].trim() != "") || false;
console.log(isAuth)
console.log("auth above")

this.socket = io(
`${window.location.host.split(":")[0]}:${SERVER_PORT}`,
{
query: {
authToken: getAuthToken(),
isAuthSocket: isAuth
}
}
);


this.onConnect = () => {};
this.socket.on("connect_error", (err) => {
console.log(err.message)
window.location.href = "/"
})

this.socket.on("unauthorized", async () => {
this.close()
window.location.href = "/"
})

this.onConnect = () => { };
this.connected = false;

this.socket.on("connect", () => {
this.connected = true;
console.log("Connected")
this.onConnect();
});

Expand Down Expand Up @@ -84,4 +113,9 @@ export class NetworkConnection extends Observable {
updateTool(tool: Tool) {
this.socket.emit("update tool", tool ? tool.serialize() : undefined);
}

close() {
this.connected = false
this.socket.disconnect(true)
}
}
File renamed without changes.
49 changes: 32 additions & 17 deletions client/pages/explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function DirListing({ Symbol, symbolClassName, name, className, userPath, dirPat
<Symbol className={symbolClassName + " text-2xl mr-1"} />
<p className={"whitespace-nowrap mt-[0.15rem] font-bold " + (open ? "" : "")}>{name}</p>
</button>
<div className={!open && "hidden"}>{children}</div>
<div className={open ? "" : "hidden"}>{children}</div>
</div>
);
}
Expand Down Expand Up @@ -382,7 +382,10 @@ function MoveFilesModal({ visible, setVisible, files = [], selectedFiles = {}, m
function EditFileModal({ visible, setVisible, file, save }) {
// menuType is either 'note' or 'folder'
const [newName, setNewName] = useState("");
const [newNoteAccess, setNewNoteAccess] = useState(null);
const [newNoteAccess, setNewNoteAccess] = useState(file.defaultAccess || "private");

console.log(file)


const fileType = file !== undefined ? file.type : "";
const isAccessVisible = fileType == "note";
Expand All @@ -395,9 +398,13 @@ function EditFileModal({ visible, setVisible, file, save }) {

const saveEdits = () => {
const trimmedNewName = newName.trim();
if (trimmedNewName != "") {
save(file._id, newName, newNoteAccess);
let name = ""
if (trimmedNewName == "") {
name = file.name
} else {
name = trimmedNewName
}
save(file._id, name, newNoteAccess);
hideModal();
};

Expand Down Expand Up @@ -430,9 +437,9 @@ function EditFileModal({ visible, setVisible, file, save }) {
onChange={e => setNewNoteAccess(e.target.value)}
className="bg-gray-100 w-full border-[1px] px-2 border-gray-400 rounded-md"
>
<option value="edit">View &amp; edit</option>
<option value="view">View only</option>
<option value="none">None</option>
<option value="read_write">View &amp; edit</option>
<option value="read_only">View only</option>
<option value="private">No public access</option>
</select>
</div>
</div>
Expand All @@ -450,7 +457,7 @@ function EditFileModal({ visible, setVisible, file, save }) {
function CreateFileModal({ visible, setVisible, path, setFiles, reloadFiles }) {
const [menu, setMenu] = useState("note");
const [noteName, setNoteName] = useState("");
const [notePublicAccess, setNotePublicAccess] = useState("view");
const [notePublicAccess, setNotePublicAccess] = useState("private");
const [folderName, setFolderName] = useState("");

const submit = () => {
Expand All @@ -462,7 +469,7 @@ function CreateFileModal({ visible, setVisible, path, setFiles, reloadFiles }) {

setMenu("note");
setNoteName("");
setNotePublicAccess("view");
setNotePublicAccess("private");
setFolderName("");

setVisible(false);
Expand Down Expand Up @@ -514,9 +521,9 @@ function CreateFileModal({ visible, setVisible, path, setFiles, reloadFiles }) {
onChange={e => setNotePublicAccess(e.target.value)}
className="bg-gray-100 w-full border-[1px] px-2 border-gray-400 rounded-md"
>
<option value="edit">View &amp; edit</option>
<option value="view">View only</option>
<option value="none">None</option>
<option value="read_write">View &amp; edit</option>
<option value="read_only">View only</option>
<option value="private">No public access</option>
</select>
</div>
</div>
Expand Down Expand Up @@ -585,7 +592,6 @@ async function LoadFiles(callback) {

async function addFile(name, type, parentDir, setFiles, options = {}) {
const response = await fetch(GetApiPath("/api/explorer/addfile"), {
// TODO: check, shouldnt have to put the token, it should be in the header by default
method: "post",
body: JSON.stringify({ token: getAuthToken(), name, type, parentDir, options }),
headers: {
Expand Down Expand Up @@ -759,7 +765,7 @@ export default function Explorer() {
fileClickActionFactory = explorerItemClickActionFactory;
} else {
bookClickActionFactory = (f, idx) => () => setPath(path.concat([f._id]));
fileClickActionFactory = (f, idx) => () => window.open("/note/" + f.fileId, "_blank");
fileClickActionFactory = (f, idx) => () => window.open("/auth-note/" + f.fileId, "_blank");
}

const drawExplorerItem = (f, idx, _) => {
Expand Down Expand Up @@ -884,8 +890,11 @@ export default function Explorer() {
visible={editFileModal}
setVisible={setEditFileModal}
setFiles={setFilesAfterChange}
save={(id, newName, newVisibility, options) =>
save={(id, newName, newVisibility, options) => {
editFileAPICall(id, newName, setFilesAfterChange, newVisibility, options)
toggleFileSelection(false)
}

}
/>
)}
Expand All @@ -895,7 +904,10 @@ export default function Explorer() {
visible={removeFileModal}
setVisible={setRemoveFileModal}
setFiles={setFilesAfterChange}
removeFiles={() => removeFilesAPICall(Object.values(selectedFiles), setFilesAfterChange)}
removeFiles={() => {
removeFilesAPICall(Object.values(selectedFiles), setFilesAfterChange)
toggleFileSelection(false)
}}
/>
)}
{/* Move files modal */}
Expand All @@ -906,7 +918,10 @@ export default function Explorer() {
visible={moveFileModal}
setVisible={setMoveFileModal}
setFiles={setFilesAfterChange}
moveFiles={target => moveFilesAPICall(selectedFiles, target, setFilesAfterChange)}
moveFiles={target => {
moveFilesAPICall(selectedFiles, target, setFilesAfterChange)
toggleFileSelection(false)
}}
/>
)}
</main>
Expand Down
57 changes: 57 additions & 0 deletions client/pages/free-note/[id].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Head from "next/head";
import Script from "next/script";
import loadingMessages from "../../loading_messages";
import { useEffect, useState } from "react";
import { FullScreenButton } from "../../components/FullScreenButton";
import { useRouter } from "next/router";
import Canvas from "../../components/Canvas";
import { IoIosUndo } from "react-icons/io";
import { TbEraser, TbEraserOff } from "react-icons/tb";
import { FaPencilAlt } from "react-icons/fa";

export default function Note() {
const router = useRouter();
const { id } = router.query;

const [messageIdx, setMessageIdx] = useState(null);

useEffect(() => {
setMessageIdx(() => Math.floor(Math.random() * loadingMessages.length));
}, []);

const isMessageVisible = messageIdx != null;
console.log(messageIdx);
console.log(isMessageVisible);

// TODO check authentication here with a fetch

if (false) {
return (
<div className="grid h-screen place-items-center bg-gray-100 animate-pulse">
<div className="flex flex-col text-center w-96 h-48 justify-around ">
<h1 className="font-bold text-4xl">Your note is loading</h1>
<span
className={`${
isMessageVisible ? "opacity-100" : "opacity-0"
} font-light transition-opacity italic text-xl`}
>
{loadingMessages[messageIdx || 0]}
</span>
</div>
</div>
);
}

return (
<div>
<Head>
<title>{id ? `Inck/${id}` : "Inck"}</title>
<meta name="description" content="The only ink that you will ever need" />
<link rel="icon" href="/favicon.ico" />
</Head>

<Canvas />
<FullScreenButton />
</div>
);
}
2 changes: 1 addition & 1 deletion client/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function CreateNoteBtn({ className }) {
const len = 6
const id = Array(6).fill(0).map(x => chars[Math.floor(Math.random() * chars.length)]).join('')
return (
<button onClick={() => window.location = '/note/' + id} className={`${className} flex flex-row items-center justify-center bg-primary hover:bg-primary-dark duration-200 py-3 px-6 rounded-xl w-full`}>
<button onClick={() => window.location = '/free-note/' + id} className={`${className} flex flex-row items-center justify-center bg-primary hover:bg-primary-dark duration-200 py-3 px-6 rounded-xl w-full`}>
Create new note
<FaPen className='mr-1 ml-7'/>
</button>
Expand Down
10 changes: 10 additions & 0 deletions client/pages/unauthorized.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

export default function Test() {

return (
<div>
Bad credentials
</div>
)

}
47 changes: 37 additions & 10 deletions server/FileExplorer.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { FileModel } from "./Models.mjs";
import { FileModel, NoteModel } from "./Models.mjs";
import jwt from "jsonwebtoken";
import { ObjectId } from "mongodb";
import { exploreTree } from "./FsTreeExplorer.mjs";

export const PRIVATE = "private"
export const READ_WRITE = "read_write"
export const READ_ONLY = "read_only"

const VALID_VISIBILITIES = [PRIVATE, READ_ONLY, READ_WRITE]

/**
* Potential errors w/ status:
* 400: missing fields, invalid email
Expand Down Expand Up @@ -58,15 +64,24 @@ export async function createFileFn(req, res) {

// TODO: validate file name & type
// TODO: when generating file id, make sure it's not already used
const file = await FileModel.create({
const newFileId = generateRandomString(12)
const filePromise = FileModel.create({
type: req.body.type,
name: req.body.name,
parentDir: req.body.parentDir,
owner: token.userId,
fileId: generateRandomString(4),
fileId: newFileId,
defaultAccess: req.body.options && req.body.options.publicAccess
});

const notePromise = NoteModel.create({
id: newFileId,
isFreeNote: false,
})

await filePromise
await notePromise

const allFiles = await FileModel.find({
owner: token.userId,
})
Expand Down Expand Up @@ -145,18 +160,30 @@ export async function moveFilesFn(req, res) {
}
}

function validVisibility(visibility) {
return VALID_VISIBILITIES.includes(visibility)
}

export async function editFileFn(req, res) {
try {
const { id, newName, newVisibility, options } = req.body
const { id, newName, newVisibility } = req.body
if (!validVisibility(newVisibility)) {
console.log("Invalid request: visibility requested: " + newVisibility)
res.status(400).send({ error: "Invalid request" })
return
}

if (newName.trim() == "") {
console.log("Invalid new name: " + "'" + newName + "'")
res.status(400).send({ error: "Invalid request" })
}

const token = jwt.verify(req.body.token, process.env.JWT_TOKEN)
// TODO: validate file name & type
// TODO: when generating file id, make sure it's not already used


const updateObject = { name: newName }
// TODO: check visibility is in a subset of valid values
if (newVisibility != "" && newVisibility != null) {
updateObject["defaultAccess"] = newVisibility
const updateObject = {
name: newName,
defaultAccess: newVisibility
}

await FileModel.findOneAndUpdate({
Expand Down
Loading

0 comments on commit a016358

Please sign in to comment.