E2E Test and setup

This commit is contained in:
james fitzsimons 2025-11-23 15:14:48 +00:00
parent 9645f411f3
commit 50d7f41248
6 changed files with 111 additions and 3058 deletions

Binary file not shown.

View file

@ -15,13 +15,88 @@ export interface NoteCreate {
title: string;
content: string;
folder_id: number | null;
encrypted: boolean;
}
// Derive key from password
async function deriveKey(password: string) {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
"raw",
enc.encode(password),
"PBKDF2",
false,
["deriveKey"],
);
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: enc.encode("your-app-salt"), // Store this somewhere consistent
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"],
);
}
// Encrypt content
async function encryptNote(content: string, key: CryptoKey) {
const enc = new TextEncoder();
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
enc.encode(content),
);
// Return IV + encrypted data as base64
const combined = new Uint8Array(iv.length + encrypted.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encrypted), iv.length);
return btoa(String.fromCharCode(...combined));
}
// Decrypt content
async function decryptNote(encrypted: string, key: CryptoKey) {
const combined = Uint8Array.from(atob(encrypted), (c) => c.charCodeAt(0));
const iv = combined.slice(0, 12);
const data = combined.slice(12);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
data,
);
return new TextDecoder().decode(decrypted);
}
const createNote = async (note: NoteCreate) => {
if (!note.encrypted) {
return axios.post(`${API_URL}/notes`, note);
} else {
var key = await deriveKey("Test");
var eNote = await encryptNote(note.content, key);
console.log(eNote);
var unENote = await decryptNote(eNote, key);
console.log(unENote);
}
};
export const notesApi = {
list: () => axios.get(`${API_URL}/notes`),
get: (id: number) => axios.get(`${API_URL}/notes/${id}`),
create: (note: NoteCreate) => axios.post(`${API_URL}/notes`, note),
update: (id: number, note: Partial) =>
create: (note: NoteCreate) => createNote(note),
update: (id: number, note: Partial<Note>) =>
axios.patch(`${API_URL}/notes/${id}`, note),
delete: (id: number) => axios.delete(`${API_URL}/notes/${id}`),
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,8 +2,8 @@ import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./main.css";
import "./assets/fontawesome/js/fontawesome.min.js";
import "./assets/fontawesome/js/duotone-regular.js";
import "./assets/fontawesome/js/fontawesome.js ";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>

View file

@ -21,7 +21,13 @@ import {
} from "../api/folders";
import { NoteCreate, notesApi } from "../api/notes";
import "../main.css";
import { DndContext, DragEndEvent } from "@dnd-kit/core";
import {
DndContext,
DragEndEvent,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import "@mdxeditor/editor/style.css";
import { DroppableFolder } from "../components/sidebar/DroppableFolder";
@ -50,7 +56,13 @@ function Home() {
const [newFolder, setNewFolder] = useState(false);
const [newFolderText, setNewFolderText] = useState("");
const [selectedFolder, setSelectedFolder] = useState<number | null>(null);
const [encrypted, setEncrypted] = useState(false);
const pointer = useSensor(PointerSensor, {
activationConstraint: {
distance: 30,
},
});
const sensors = useSensors(pointer);
useEffect(() => {
loadFolderTree();
}, []);
@ -62,7 +74,12 @@ function Home() {
const handleCreate = async () => {
if (!title.trim()) return;
const newNote: NoteCreate = { title, content, folder_id: selectedFolder };
const newNote: NoteCreate = {
title,
content,
folder_id: selectedFolder,
encrypted,
};
await notesApi.create(newNote);
setTitle("");
setContent("#");
@ -96,6 +113,7 @@ function Home() {
};
const selectNote = (note: NoteRead) => {
console.log("setting note.... " + note.id);
setSelectedNote(note);
setTitle(note.title);
setContent(note.content);
@ -155,7 +173,7 @@ function Home() {
};
return (
<DndContext onDragEnd={handleDragEnd} autoScroll={false}>
<DndContext onDragEnd={handleDragEnd} autoScroll={false} sensors={sensors}>
<div className="flex bg-ctp-base min-h-screen text-ctp-text">
<div
className="bg-ctp-mantle border-r-ctp-surface2 border-r overflow-hidden"
@ -292,6 +310,11 @@ function Home() {
) : (
<button onClick={handleCreate}>Create Note</button>
)}
<input
type="checkbox"
checked={encrypted}
onChange={() => setEncrypted(!encrypted)}
/>
</div>
</div>
</div>