Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added app.db
Binary file not shown.
30 changes: 30 additions & 0 deletions backend/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import sqlite3 from 'sqlite3';
import { open, type Database } from 'sqlite';

// Use verbose mode for better stack traces during development
sqlite3.verbose();

let dbInstance: Promise<Database<sqlite3.Database, sqlite3.Statement>> | null = null;

export async function getDb(): Promise<Database<sqlite3.Database, sqlite3.Statement>> {
if (!dbInstance) {
dbInstance = open({
filename: 'app.db',
driver: sqlite3.Database,
});
}
return dbInstance;
}

export async function initSchema(): Promise<void> {
const db = await getDb();
await db.exec(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL,
visible BOOLEAN NOT NULL DEFAULT TRUE,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
`);
}
44 changes: 44 additions & 0 deletions backend/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,60 @@
import express from 'express';
import { initSchema, getDb } from './db';

const args = process.argv.slice(2);
const PORT = args[0] == "--port" ? args[1] : 80;
main();

async function main() {
const app = express();
app.use(express.json());

await initSchema();

app.get('/tryme', (_, res) => {
res.send('Hello from the backend!')
})

app.get('/notes', async (_, res) => {
const db = await getDb();
const notes = await db.all(
'SELECT id, title, body, created_at FROM notes WHERE visible = true ORDER BY id DESC'
);
res.json(notes);
})

app.post('/notes', async (req, res) => {
const { title, body } = req.body ?? {};
console.log('posting note');
console.log(req);
console.log(JSON.stringify(req.body));
if (typeof title !== 'string' || typeof body !== 'string') {
res.status(400).json({ error: 'title and body are required strings' });
return;
}
const db = await getDb();
const result = await db.run(
'INSERT INTO notes (title, body) VALUES (?, ?)',
title,
body
);
res.status(201).json({ id: result.lastID, title, body });
})

app.put('/delete-note', async (req, res) => {
const { id } = req.body ?? {};
if (typeof id !== 'number') {
res.status(400).json({ error: 'id is required number' });
return;
}
const db = await getDb();
await db.run(
'UPDATE notes SET visible = false WHERE id = ?',
id
);
res.status(200).send();
})

app.listen(PORT, () => {
console.log(`Example app listening on port ${PORT}`)
});
Expand Down
80 changes: 77 additions & 3 deletions frontend/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,87 @@
import React from 'react';
import { Button, Card, CardContent, ListItemText, ListItem, List, TextField, Typography } from '@mui/material';
import { useEffect, useState } from 'react';
import ReactDOM from 'react-dom/client';


const Notes = () => {
const [title, setTitle] = useState<string>('');
const [body, setBody] = useState<string>('');
const [notes, setNotes] = useState<{ id: number, title: string, body: string, visible: boolean }[]>([]);

const getNotes = async () => {
const response = await fetch("/api/notes");
const data = await response.json();
setNotes(data);
}

const addNote = async (title: string, body: string) => {
const response = await fetch("/api/notes", {
method: "POST",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, body }),
});
const data = await response.json();
setNotes([...notes, data]);
}

const deleteNote = async (id: number) => {
await fetch("/api/delete-note", {
method: "PUT",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id }),
});
setNotes(notes.filter(note => note.id !== id));
}

useEffect(() => {
getNotes();
}, []);

return (
<div>
<Card style={{ width: '300px', margin: '10px' }}>
<CardContent>
<Typography variant="h6">Add a Note</Typography>
<TextField label="Title" value={title} onChange={(e) => setTitle(e.target.value)} />
<TextField label="Body" value={body} onChange={(e) => setBody(e.target.value)} />
<Button variant="contained" onClick={() => addNote(title, body)}>Add a Note</Button>
</CardContent>
</Card>
<List style={{ width: '300px', margin: '10px' }}>
{notes?.length > 0 ? notes?.map(note => (
<ListItem key={note.id}>
<ListItemText primary={note.title} secondary={note.body} />
<Button variant="contained" onClick={() => deleteNote(note.id)}>Delete</Button>
</ListItem>
)) : <ListItem><ListItemText primary="No notes found" /></ListItem>}
</List>

</div>
)
}
const heading =
<div>
<h1>Hello World!</h1>
<p>This is an example page.</p>
<Notes />
</div>

const rootNode = document.getElementById('app');
ReactDOM.createRoot(rootNode!).render(heading);

// fetch("/api/notes").then(v => v.json()).then(notes => console.log(`notes: ${JSON.stringify(notes)}`));
fetch("/api/tryme").then(v => v.text()).then(txt => console.log(`Message from the backend: "${txt}"`));





// {notes?.length > 0 ? notes?.map(note => (
// <div key={note.id}>
// <h2>{note.title}</h2>
// <p>{note.body}</p>
// <button onClick={() => deleteNote(note.id)}>Delete</button>
// </div>
// )) : <p>No notes found</p>}
35 changes: 35 additions & 0 deletions notesApp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Notes app. You can
* Add notes
* Remove notes
* View your notes from other computers in the same network
Notes have a title and body




1. persistent data storage - id, title, body, visible
2. add notes => plus button where they can add a note - title, body and press save
3. view notes => get request to retrieve all notes where visible is true -> pagination may be required
4. delete notes => trash icon inline with note that user can click, see confirmation, once confirmed,
make put request to update visible to false

sql lite

api endpoints
- notes
- get => retrieve notes
- put => update note
- post => create notes



on page load make a get call to notes display results
add button to create notes
- open a modal where user can add title, body, and save
- on click of save make post request then invalidate (refetch the notes)


each notes will be a card that has a delete button
- open confirmatin modal on delete make put to change visibility then refetch notes


4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
"author": "",
"license": "ISC",
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/material": "^7.3.4",
"@types/express": "^5.0.3",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@vitejs/plugin-react-swc": "^4.1.0",
"better-sqlite3": "^12.4.1",
"express": "^5.1.0",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7",
Expand Down