Compare commits

..

2 Commits

Author SHA1 Message Date
Primakov Alexandr Alexandrovich
cf2eb88662 deploy 2024-12-05 22:55:59 +03:00
Primakov Alexandr Alexandrovich
9723c825f7 todo list 2024-12-05 19:44:54 +03:00
14 changed files with 452 additions and 2880 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.env .env
node_modules/ node_modules/
dist/

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM 'node:20'
RUN mkdir -p /usr/src/app/dist
WORKDIR /usr/src/app
COPY package.json /usr/src/app/
COPY package-lock.json /usr/src/app/
COPY dist /usr/src/app/dist
RUN npm ci --omit=dev
EXPOSE 3003
CMD [ "npm", "run", "up:prod" ]

4
d-script/restart.sh Normal file
View File

@ -0,0 +1,4 @@
docker stop bh-mongo;
docker volume rm bh-mongo-volume;
docker volume create bh-mongo-volume;
sh d-script/up-mongo.sh;

View File

@ -5,6 +5,6 @@ docker \
--name bh-mongo \ --name bh-mongo \
-e MONGO_INITDB_ROOT_USERNAME=qqq \ -e MONGO_INITDB_ROOT_USERNAME=qqq \
-e MONGO_INITDB_ROOT_PASSWORD=qqq \ -e MONGO_INITDB_ROOT_PASSWORD=qqq \
-p 27017:27017 \ -p 8888:27017 \
-v bh-mongo-volume:/data/db \ -v bh-mongo-volume:/data/db \
mongo:8.0.3; mongo:8.0.3;

15
docker-compose.yml Normal file
View File

@ -0,0 +1,15 @@
version: "3"
services:
bh:
# build: .
image: brojs/todo/bh:$TAG
restart: always
env_file: ./.env
ports:
- 3003:3003
environment:
- PORT=${PORT}
- JWT_SECRET=${JWT_SECRET}
- MONGO_CONNECT_URL=${MONGO_CONNECT_URL}

3110
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,16 +14,13 @@
"dependencies": { "dependencies": {
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^5.0.1", "express": "^5.0.1",
"express-json-validator-middleware": "^3.0.1",
"install": "^0.13.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"npm": "^10.9.1", "mongoose": "^8.8.3",
"pbkdf2-password": "^1.2.1", "pbkdf2-password": "^1.2.1"
"ts-node": "^10.9.2"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"nodemon": "^3.1.7", "ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0", "ts-node-dev": "^2.0.0",
"typescript": "^5.7.2" "typescript": "^5.7.2"
} }

29
src/connect.ts Normal file
View File

@ -0,0 +1,29 @@
import mongoose from "mongoose";
import ItemListModel from "./model/todo-list";
import ItemModel from "./model/todo-item";
export const connect = async () => {
await mongoose.connect(process.env.MONGO_CONNECT_URL!);
console.log("Connected to database");
const lists = await ItemListModel.find();
console.log(JSON.stringify(lists, null, 4))
if (lists.length === 0) {
await ItemListModel.create({
name: "Test List",
});
const item = await ItemModel.create({
title: "Test Item",
});
lists.forEach(async (list) => {
await (list as unknown as any).addItem(item);
})
}
console.log("Database initiated");
};

View File

@ -1,8 +1,9 @@
import express, { json } from 'express'; import express, { json } from 'express';
import { handleError } from './utils/errorHandler'
import 'dotenv/config' import 'dotenv/config'
import { router } from './routes' import { router } from './routes'
import { handleError } from './utils/errorHandler'
import { connect } from './connect';
const app = express(); const app = express();
@ -13,6 +14,14 @@ app.use(json({ limit: '100kb' }))
app.use(router) app.use(router)
app.use(handleError) app.use(handleError)
app.listen(port, () => {
const start = async () => {
console.log('starting...')
await connect()
app.listen(port, () => {
console.log(`App is running on port http://localhost:${port}`); console.log(`App is running on port http://localhost:${port}`);
}) })
}
start()

24
src/model/todo-item.ts Normal file
View File

@ -0,0 +1,24 @@
import { Schema, model } from 'mongoose'
const schema = new Schema({
title: {type: String, required: true },
description: String,
created: { type: Date, default: () => new Date().toISOString() },
done: { type: Boolean, default: false },
// createdBy: { type: Schema.Types.ObjectId, required: true, ref: 'User' }
deleted: { type: Boolean, default: false }
})
schema.virtual('id').get(function () {
return this._id.toHexString()
})
schema.set('toJSON', {
virtuals: true,
versionKey: false,
transform: (doc, ret) => {
delete ret._id
}
})
export default model('Item', schema)

34
src/model/todo-list.ts Normal file
View File

@ -0,0 +1,34 @@
import { Schema, model } from 'mongoose'
const schema = new Schema({
name: String,
items: [{ type: Schema.Types.ObjectId, required: true, ref: 'Item' }],
created: { type: Date, default: () => new Date().toISOString() },
// createdBy: { type: Schema.Types.ObjectId, required: true, ref: 'User' }
deleted: { type: Boolean, default: false }
})
schema.virtual('id').get(function () {
return this._id.toHexString()
})
schema.set('toJSON', {
virtuals: true,
versionKey: false,
transform: (doc, ret) => {
delete ret._id
}
})
schema.method('addItem', async function(item) {
this.items.push(item._id)
return await this.save()
})
schema.method('removeItem', async function(item) {
this.items = this.items.filter(i => i !== item._id)
return await this.save()
})
export default model('TodoList', schema)

View File

@ -3,9 +3,11 @@ import { Router } from "express";
import pkg from '../../package.json' import pkg from '../../package.json'
import { router as usersRouter } from './users' import { router as usersRouter } from './users'
import { router as todoRouter } from './todo'
export const router = Router(); export const router = Router();
router.get('/healthcheck', (req, res) => void res.send({ ok: true, version: pkg.version })); router.get('/healthcheck', (req, res) => void res.send({ ok: true, version: pkg.version }));
router.use('/users', usersRouter) router.use('/users', usersRouter)
router.use('/todo', todoRouter)

66
src/routes/todo/index.ts Normal file
View File

@ -0,0 +1,66 @@
import { Router } from "express";
import ListsModel from '../../model/todo-list';
import ItemModel from '../../model/todo-item';
export const router = Router();
router.get('/lists', async (req, res) => {
const lists = await ListsModel.find({});
res.json(lists);
})
router.post('/list', async (req, res) => {
const { name } = req.body
const list = await ListsModel.create({
name
});
res.json(list);
})
router.delete('/item/:itemId', async (req, res) => {
const { itemId } = req.params;
const item = await ItemModel.findById(itemId);
if (!item) throw new Error('Item not found');
await ItemModel.findByIdAndDelete(itemId);
res.send(item);
})
router.get('/list/:listId', async (req, res) => {
const { listId } = req.params;
const list = await ListsModel
.findById(listId)
.populate('items')
.exec();
if (!list) throw new Error('List not found');
res.json(list);
})
router.post('/:listId/item', async (req, res) => {
const { listId } = req.params
const { title, description = '' } = req.body
const list = await ListsModel.findById(listId);
if (!list) {
throw new Error('List not found');
}
const item = await ItemModel.create({
title,
description
});
await (list as any).addItem(item);
res.send(item)
})

View File

@ -1,7 +1,5 @@
import { Router } from "express"; import { Router } from "express";
import { Validator } from "express-json-validator-middleware";
import bkfd2Password from "pbkdf2-password"; import bkfd2Password from "pbkdf2-password";
import { promisify } from 'node:util'
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
@ -12,8 +10,6 @@ const hasher = bkfd2Password();
export const router = Router(); export const router = Router();
const { validate } = new Validator({});
const user = { const user = {
type: "object", type: "object",
required: ["name"], required: ["name"],