This commit is contained in:
tuffahni 2024-12-30 04:14:32 +00:00
parent 23225bb54b
commit c6d68334be
7 changed files with 320 additions and 8 deletions

View File

@ -3,8 +3,42 @@ services:
build: .
ports:
- "3000:3000"
# files are put in here
volumes:
- .:/usr/src/app
- ./node_modules:/usr/src/app/node_modules
command: ["yarn", "dev"]
# Database
# Database: how we want to create the database
db:
# Image of postgres in 13th version
image: postgres:13
environment: #secret
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
ports: #internally the port is 5432, but we want to use 5432 locally
- "5432:5432"
# create a file to store stuff, store inside "data" folder
volumes:
- ./data:/var/lib/postgresql/data
# PGAdmin: visualize postgres database
pgadmin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
ports:
# our local port is 3001, but the container port is 80
- "3001:80"
# creating a folder called pgadmin to store the pgadmin files
volumes:
- ./pgadmin:/root/.pgadmin
depends_on:
- db
# tell docker to create something called data (refer to above) and store all the postgres data in it
volumes:
data:
pgadmin:

View File

@ -11,10 +11,12 @@
"dependencies": {
"dotenv": "^16.4.7",
"ejs": "^3.1.10",
"express": "^4.21.2"
"express": "^4.21.2",
"pg": "^8.13.1"
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/pg": "^8.11.10",
"concurrently": "^9.1.1",
"nodemon": "^3.1.9",
"sass": "^1.83.0",

View File

@ -17,10 +17,16 @@ importers:
express:
specifier: ^4.21.2
version: 4.21.2
pg:
specifier: ^8.13.1
version: 8.13.1
devDependencies:
'@types/express':
specifier: ^5.0.0
version: 5.0.0
'@types/pg':
specifier: ^8.11.10
version: 8.11.10
concurrently:
specifier: ^9.1.1
version: 9.1.1
@ -168,6 +174,9 @@ packages:
'@types/node@22.10.2':
resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==}
'@types/pg@8.11.10':
resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==}
'@types/qs@6.9.17':
resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
@ -560,6 +569,9 @@ packages:
resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==}
engines: {node: '>= 0.4'}
obuf@1.1.2:
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@ -571,10 +583,87 @@ packages:
path-to-regexp@0.1.12:
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
pg-cloudflare@1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
pg-connection-string@2.7.0:
resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==}
pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
pg-numeric@1.0.2:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
pg-pool@3.7.0:
resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==}
peerDependencies:
pg: '>=8.0'
pg-protocol@1.7.0:
resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==}
pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
engines: {node: '>=4'}
pg-types@4.0.2:
resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==}
engines: {node: '>=10'}
pg@8.13.1:
resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
pgpass@1.0.5:
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
postgres-array@3.0.2:
resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==}
engines: {node: '>=12'}
postgres-bytea@1.0.0:
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
engines: {node: '>=0.10.0'}
postgres-bytea@3.0.0:
resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==}
engines: {node: '>= 6'}
postgres-date@1.0.7:
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
engines: {node: '>=0.10.0'}
postgres-date@2.1.0:
resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==}
engines: {node: '>=12'}
postgres-interval@1.2.0:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
postgres-interval@3.0.0:
resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==}
engines: {node: '>=12'}
postgres-range@1.1.4:
resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==}
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@ -664,6 +753,10 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
@ -755,6 +848,10 @@ packages:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -886,6 +983,12 @@ snapshots:
dependencies:
undici-types: 6.20.0
'@types/pg@8.11.10':
dependencies:
'@types/node': 22.10.2
pg-protocol: 1.7.0
pg-types: 4.0.2
'@types/qs@6.9.17': {}
'@types/range-parser@1.2.7': {}
@ -1282,6 +1385,8 @@ snapshots:
object-inspect@1.13.3: {}
obuf@1.1.2: {}
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
@ -1290,8 +1395,77 @@ snapshots:
path-to-regexp@0.1.12: {}
pg-cloudflare@1.1.1:
optional: true
pg-connection-string@2.7.0: {}
pg-int8@1.0.1: {}
pg-numeric@1.0.2: {}
pg-pool@3.7.0(pg@8.13.1):
dependencies:
pg: 8.13.1
pg-protocol@1.7.0: {}
pg-types@2.2.0:
dependencies:
pg-int8: 1.0.1
postgres-array: 2.0.0
postgres-bytea: 1.0.0
postgres-date: 1.0.7
postgres-interval: 1.2.0
pg-types@4.0.2:
dependencies:
pg-int8: 1.0.1
pg-numeric: 1.0.2
postgres-array: 3.0.2
postgres-bytea: 3.0.0
postgres-date: 2.1.0
postgres-interval: 3.0.0
postgres-range: 1.1.4
pg@8.13.1:
dependencies:
pg-connection-string: 2.7.0
pg-pool: 3.7.0(pg@8.13.1)
pg-protocol: 1.7.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.1.1
pgpass@1.0.5:
dependencies:
split2: 4.2.0
picomatch@2.3.1: {}
postgres-array@2.0.0: {}
postgres-array@3.0.2: {}
postgres-bytea@1.0.0: {}
postgres-bytea@3.0.0:
dependencies:
obuf: 1.1.2
postgres-date@1.0.7: {}
postgres-date@2.1.0: {}
postgres-interval@1.2.0:
dependencies:
xtend: 4.0.2
postgres-interval@3.0.0: {}
postgres-range@1.1.4: {}
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@ -1403,6 +1577,8 @@ snapshots:
source-map-js@1.2.1: {}
split2@4.2.0: {}
statuses@2.0.1: {}
string-width@4.2.3:
@ -1482,6 +1658,8 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
xtend@4.0.2: {}
y18n@5.0.8: {}
yargs-parser@21.1.1: {}

View File

View File

@ -0,0 +1,14 @@
import { Pool } from "pg";
console.log(process.env.DB_HOST);
const pg = new Pool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: parseInt(process.env.DB_PORT as string),
});
// export pg to outside to use
export default pg;

View File

@ -1,10 +1,17 @@
import express, { Application, Request, Response } from "express";
import dotenv from "dotenv";
dotenv.config({ path: "$(__dirname)/../env" });
dotenv.config({ path: ".env" });
import express, { Application, Request, Response } from "express";
import pg from "./database/pg";
import { PoolClient } from "pg";
const app: Application = express();
const port: number = parseInt(process.env.PORT as string) || 3000;
//Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
//Setting up for EJS
app.set("views", `./src/views`);
app.set("view engine", "ejs");
@ -14,14 +21,82 @@ app.use(express.static(`${__dirname}/../public`));
//check and send index ejs
app.get("/", (_req: Request, res: Response): void => {
const date = new Date().toLocaleString();
let todos: string[] = [];
res.render("index", {
time: date,
//Query postgres database to get all todos (async await)
pg.query("SELECT * FROM todos", async (err: Error, result: any) => {
if (err) {
console.log("Error querying todos", err);
return;
}
todos = result.rows.map((row: any) => row.todo);
}
res.render("index",{
todos,
});
});
app.post("/form-action", (req: Request, res: Response): void => {
const todo: string = req.body.todo;
//query postgres to insert todo, $1 is a placeholder for the value of todo
pg.query("INSERT INTO todos (todo) VALUES ($1)", [todo], (err: Error) => {
if (err) {
console.log("Error inserting todo", err);
return;
}
console.log(`Inserted ${todo} successfully`);
});
res.redirect("/");
});
// app.delete("/delete-todo/:todo", (req: Request, res: Response): void => {
// //delete todo from postgres
// });
app.listen(port, (): void => {
console.log(`Server is running on port ${port}`);
});
//Connect to postgres, postgres initialization
pg.connect(
(err: Error | undefined, client: PoolClient | undefined, done): void => {
if (err) {
console.log("Error connecting to pg", err);
return;
}
if (!client) {
console.log("No client found");
return;
}
// Attempt to Query (asking the database, to check it is connected), it might take us a while and we wont be able to run the rest of the program
// Async this
client.query("SELECT NOW()", async (err: Error, _result: any) => {
if (err) {
console.log("Error querying pg", err);
return;
}
//Create the table if no error (await)
//Query statement, instruction "CREATE TABLE IF NOT EXISTS todos", there is a whole table, https://www.w3schools.com/sql/default.asp
//VACHAR is a string, 255 is the max length of the string
await client.query(
`CREATE TABLE IF NOT EXISTS todos (
todo VARCHAR(255) NOT NULL
)`,
(err: Error) => {
console.log("Error creating table", err);
return;
}
);
console.log("Connected to postgres successfully");
done();
});
}
);

View File

@ -33,7 +33,13 @@
<p><i>Add something cute to my to-do list!</i></p>
<form action="/form-action" method="post">
<label for="to-do"></label>
<input name="todo" type="text" placeholder="Enter a to-do" required class="todo-input" />
<input
name="todo"
type="text"
placeholder="Enter a to-do"
required
class="todo-input"
/>
<button type="submit" class="form-submit">Add</button>
</form>
@ -44,6 +50,9 @@
<div class="inner-todo">
<h1>To do list</h1>
<ol>
<!-- rendering the todos -->
<!-- <% todos.forEach(todo => { %> <li><%= todo %></li> <% }); %> -->
<!-- click button, go to app.delete, then delete something -->
<!-- <li>Learn from Ronald</li>
<li>Watch movies</li>
<li>Sleep</li> -->