-
Notifications
You must be signed in to change notification settings - Fork 122
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
0 parents
commit 3a25b87
Showing
40 changed files
with
31,188 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# To-Do App | ||
|
||
This is a sample to-do application for use in the PXL containers course. | ||
|
||
## Tech Stack | ||
|
||
The stack represents a combination of a JavaScript-based backend (Node.js with Express), a modern frontend framework (React), database technologies (SQLite and MySQL), and containerization (Docker), making it a comprehensive example for full-stack development and deployment. | ||
|
||
The technology stack includes: | ||
|
||
- __Node.js__: The backend application is built using Node.js, a JavaScript runtime environment that executes JavaScript code outside a web browser. | ||
|
||
- __Express.js__: This is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. It is used to create the server and handle HTTP requests. | ||
|
||
- __React.js__: The frontend is built using React, a JavaScript library for building user interfaces. It is used to create the interactive UI components for the application. | ||
|
||
- __SQLite__: This is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. SQLite is used as one of the database options for the application. | ||
|
||
- __MySQL__: This is a popular open-source relational database management system. In this application, MySQL is used as an alternative database option to SQLite. | ||
|
||
- __Docker__: The application is designed to be containerized using Docker, which allows for isolating the application in containers to simplify deployment and scalability. | ||
|
||
- __Bootstrap & Font Awesome__: For styling, the application uses Bootstrap, a front-end framework for developing responsive and mobile-first websites, and Font Awesome for icons. | ||
|
||
- __Jest__: This is a JavaScript testing framework used for writing tests for the application. | ||
|
||
- __Prettier__ & __ESLint__: These are code formatting and linting tools to ensure code quality and consistency. | ||
|
||
## Components | ||
|
||
- `package.json`: Defines the Node.js application's dependencies, scripts, and other configuration details. | ||
- `src/index.js`: The main entry point for the Node.js application. It sets up an Express server, initializes database connections, and defines routes for CRUD operations on 'todo' items. | ||
- Persistence Layer (`src/persistence`): | ||
- `index.js`: Determines whether to use MySQL or SQLite based on the environment configuration. | ||
- `mysql.js` & `sqlite.js`: Implement database operations for MySQL and SQLite, respectively. | ||
- Routes (`src/routes`): Contains Express route handlers for adding, deleting, retrieving, and updating 'todo' items. | ||
- Frontend (`src/static`): | ||
- index.html: The main HTML file for the frontend. | ||
- app.js: A React-based frontend script that interacts with the backend to display and manage 'todo' items. | ||
|
||
This application serves as a practical example for Docker users to understand how to containerize a simple web application using Docker. | ||
|
||
## Persistence Layer | ||
|
||
### Current Implementation in `index.js` | ||
|
||
The `index.js` file in the `src/persistence` directory currently checks for the presence of a MySQL configuration in the environment variables. If MySQL-related environment variables are set, it uses MySQL; otherwise, it defaults to SQLite. The relevant code is: | ||
|
||
```javascript | ||
if (process.env.MYSQL_HOST) module.exports = require('./mysql'); | ||
else module.exports = require('./sqlite'); | ||
``` | ||
|
||
### Adaptations You Might Need | ||
|
||
1. **Environment Variables**: Ensure that the environment variables for MySQL (like `MYSQL_HOST`, `MYSQL_USER`, `MYSQL_PASSWORD`, etc.) are correctly set in your deployment environment when you want to use MySQL. If these variables are not set, the application will default to using SQLite. | ||
|
||
2. **Database Configuration Files**: The `mysql.js` and `sqlite.js` files contain the specific configurations and initialization code for each database. | ||
|
||
By adapting these aspects of the `index.js` file and related configurations, you can control which database the application uses based on your deployment environment or other conditions. |
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,33 @@ | ||
{ | ||
"name": "101-app", | ||
"version": "1.0.0", | ||
"main": "index.js", | ||
"license": "MIT", | ||
"scripts": { | ||
"prettify": "prettier -l --write \"**/*.js\"", | ||
"test": "jest", | ||
"dev": "nodemon -L src/index.js" | ||
}, | ||
"dependencies": { | ||
"express": "^4.18.2", | ||
"mysql2": "^2.3.3", | ||
"sqlite3": "^5.1.2", | ||
"uuid": "^9.0.0", | ||
"wait-port": "^1.0.4" | ||
}, | ||
"resolutions": { | ||
"ansi-regex": "5.0.1" | ||
}, | ||
"prettier": { | ||
"trailingComma": "all", | ||
"tabWidth": 4, | ||
"useTabs": false, | ||
"semi": true, | ||
"singleQuote": true | ||
}, | ||
"devDependencies": { | ||
"jest": "^29.3.1", | ||
"nodemon": "^2.0.20", | ||
"prettier": "^2.7.1" | ||
} | ||
} |
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,65 @@ | ||
const db = require('../../src/persistence/sqlite'); | ||
const fs = require('fs'); | ||
const location = process.env.SQLITE_DB_LOCATION || '/etc/todos/todo.db'; | ||
|
||
const ITEM = { | ||
id: '7aef3d7c-d301-4846-8358-2a91ec9d6be3', | ||
name: 'Test', | ||
completed: false, | ||
}; | ||
|
||
beforeEach(() => { | ||
if (fs.existsSync(location)) { | ||
fs.unlinkSync(location); | ||
} | ||
}); | ||
|
||
test('it initializes correctly', async () => { | ||
await db.init(); | ||
}); | ||
|
||
test('it can store and retrieve items', async () => { | ||
await db.init(); | ||
|
||
await db.storeItem(ITEM); | ||
|
||
const items = await db.getItems(); | ||
expect(items.length).toBe(1); | ||
expect(items[0]).toEqual(ITEM); | ||
}); | ||
|
||
test('it can update an existing item', async () => { | ||
await db.init(); | ||
|
||
const initialItems = await db.getItems(); | ||
expect(initialItems.length).toBe(0); | ||
|
||
await db.storeItem(ITEM); | ||
|
||
await db.updateItem( | ||
ITEM.id, | ||
Object.assign({}, ITEM, { completed: !ITEM.completed }), | ||
); | ||
|
||
const items = await db.getItems(); | ||
expect(items.length).toBe(1); | ||
expect(items[0].completed).toBe(!ITEM.completed); | ||
}); | ||
|
||
test('it can remove an existing item', async () => { | ||
await db.init(); | ||
await db.storeItem(ITEM); | ||
|
||
await db.removeItem(ITEM.id); | ||
|
||
const items = await db.getItems(); | ||
expect(items.length).toBe(0); | ||
}); | ||
|
||
test('it can get a single item', async () => { | ||
await db.init(); | ||
await db.storeItem(ITEM); | ||
|
||
const item = await db.getItem(ITEM.id); | ||
expect(item).toEqual(ITEM); | ||
}); |
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,30 @@ | ||
const db = require('../../src/persistence'); | ||
const addItem = require('../../src/routes/addItem'); | ||
const ITEM = { id: 12345 }; | ||
const {v4 : uuid} = require('uuid'); | ||
|
||
jest.mock('uuid', () => ({ v4: jest.fn() })); | ||
|
||
jest.mock('../../src/persistence', () => ({ | ||
removeItem: jest.fn(), | ||
storeItem: jest.fn(), | ||
getItem: jest.fn(), | ||
})); | ||
|
||
test('it stores item correctly', async () => { | ||
const id = 'something-not-a-uuid'; | ||
const name = 'A sample item'; | ||
const req = { body: { name } }; | ||
const res = { send: jest.fn() }; | ||
|
||
uuid.mockReturnValue(id); | ||
|
||
await addItem(req, res); | ||
|
||
const expectedItem = { id, name, completed: false }; | ||
|
||
expect(db.storeItem.mock.calls.length).toBe(1); | ||
expect(db.storeItem.mock.calls[0][0]).toEqual(expectedItem); | ||
expect(res.send.mock.calls[0].length).toBe(1); | ||
expect(res.send.mock.calls[0][0]).toEqual(expectedItem); | ||
}); |
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,20 @@ | ||
const db = require('../../src/persistence'); | ||
const deleteItem = require('../../src/routes/deleteItem'); | ||
const ITEM = { id: 12345 }; | ||
|
||
jest.mock('../../src/persistence', () => ({ | ||
removeItem: jest.fn(), | ||
getItem: jest.fn(), | ||
})); | ||
|
||
test('it removes item correctly', async () => { | ||
const req = { params: { id: 12345 } }; | ||
const res = { sendStatus: jest.fn() }; | ||
|
||
await deleteItem(req, res); | ||
|
||
expect(db.removeItem.mock.calls.length).toBe(1); | ||
expect(db.removeItem.mock.calls[0][0]).toBe(req.params.id); | ||
expect(res.sendStatus.mock.calls[0].length).toBe(1); | ||
expect(res.sendStatus.mock.calls[0][0]).toBe(200); | ||
}); |
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,19 @@ | ||
const db = require('../../src/persistence'); | ||
const getItems = require('../../src/routes/getItems'); | ||
const ITEMS = [{ id: 12345 }]; | ||
|
||
jest.mock('../../src/persistence', () => ({ | ||
getItems: jest.fn(), | ||
})); | ||
|
||
test('it gets items correctly', async () => { | ||
const req = {}; | ||
const res = { send: jest.fn() }; | ||
db.getItems.mockReturnValue(Promise.resolve(ITEMS)); | ||
|
||
await getItems(req, res); | ||
|
||
expect(db.getItems.mock.calls.length).toBe(1); | ||
expect(res.send.mock.calls[0].length).toBe(1); | ||
expect(res.send.mock.calls[0][0]).toEqual(ITEMS); | ||
}); |
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,33 @@ | ||
const db = require('../../src/persistence'); | ||
const updateItem = require('../../src/routes/updateItem'); | ||
const ITEM = { id: 12345 }; | ||
|
||
jest.mock('../../src/persistence', () => ({ | ||
getItem: jest.fn(), | ||
updateItem: jest.fn(), | ||
})); | ||
|
||
test('it updates items correctly', async () => { | ||
const req = { | ||
params: { id: 1234 }, | ||
body: { name: 'New title', completed: false }, | ||
}; | ||
const res = { send: jest.fn() }; | ||
|
||
db.getItem.mockReturnValue(Promise.resolve(ITEM)); | ||
|
||
await updateItem(req, res); | ||
|
||
expect(db.updateItem.mock.calls.length).toBe(1); | ||
expect(db.updateItem.mock.calls[0][0]).toBe(req.params.id); | ||
expect(db.updateItem.mock.calls[0][1]).toEqual({ | ||
name: 'New title', | ||
completed: false, | ||
}); | ||
|
||
expect(db.getItem.mock.calls.length).toBe(1); | ||
expect(db.getItem.mock.calls[0][0]).toBe(req.params.id); | ||
|
||
expect(res.send.mock.calls[0].length).toBe(1); | ||
expect(res.send.mock.calls[0][0]).toEqual(ITEM); | ||
}); |
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 @@ | ||
const express = require('express'); | ||
const app = express(); | ||
const db = require('./persistence'); | ||
const getItems = require('./routes/getItems'); | ||
const addItem = require('./routes/addItem'); | ||
const updateItem = require('./routes/updateItem'); | ||
const deleteItem = require('./routes/deleteItem'); | ||
|
||
app.use(express.json()); | ||
app.use(express.static(__dirname + '/static')); | ||
|
||
app.get('/items', getItems); | ||
app.post('/items', addItem); | ||
app.put('/items/:id', updateItem); | ||
app.delete('/items/:id', deleteItem); | ||
|
||
db.init().then(() => { | ||
app.listen(3000, () => console.log('Listening on port 3000')); | ||
}).catch((err) => { | ||
console.error(err); | ||
process.exit(1); | ||
}); | ||
|
||
const gracefulShutdown = () => { | ||
db.teardown() | ||
.catch(() => {}) | ||
.then(() => process.exit()); | ||
}; | ||
|
||
process.on('SIGINT', gracefulShutdown); | ||
process.on('SIGTERM', gracefulShutdown); | ||
process.on('SIGUSR2', gracefulShutdown); // Sent by nodemon |
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,2 @@ | ||
if (process.env.MYSQL_HOST) module.exports = require('./mysql'); | ||
else module.exports = require('./sqlite'); |
Oops, something went wrong.