docker-compose up and running :)
This commit is contained in:
parent
86254bf7eb
commit
d9f9aaedc5
38
.env
Normal file
38
.env
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Environment variables declared in this file are automatically made available to Prisma.
|
||||||
|
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
|
||||||
|
|
||||||
|
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
||||||
|
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||||
|
POSTGRES_USER=root
|
||||||
|
POSTGRES_PASSWORD=irina76
|
||||||
|
POSTGRES_DB=imk_db
|
||||||
|
DATABASE_URL="postgresql://postgres:postgres@postgres:5432/imk_db?schema=public"
|
||||||
|
#DATABASE_URL="postgresql://root:irina76@localhost:5432/imk?schema=public"
|
||||||
|
JWT_SECRET=some-secret
|
||||||
|
AWS_REGION=EU2
|
||||||
|
AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6
|
||||||
|
AWS_SECRET_ACCESS_KEY=6d4723e14c0d799b89948c24dbe983e4
|
||||||
|
AWS_S3_BUCKET_NAME=imk-data
|
||||||
|
AWS_ENDPOINT_URL=https://eu2.contabostorage.com
|
||||||
|
|
||||||
|
#Email Configuration
|
||||||
|
# SMTP_HOST=smtp.gmail.com
|
||||||
|
# SMTP_PORT=587
|
||||||
|
# SMTP_USER=taratur@gmail.com
|
||||||
|
# SMTP_PASS=dziy nccc svgg bovb
|
||||||
|
# EMAIL_FROM=taratur@gmail.com
|
||||||
|
|
||||||
|
SMTP_HOST=imk.mk
|
||||||
|
SMTP_PORT=465
|
||||||
|
SMTP_USER=mailer@imk.mk
|
||||||
|
SMTP_PASS=76Avtostoperski76
|
||||||
|
SMTP_FROM=mailer@imk.mk
|
||||||
|
# FRONTEND_URL=https://imk.mk
|
||||||
|
EMAIL_FROM=mailer@yandex.com
|
||||||
|
|
||||||
|
ADMIN_EMAIL=taratur@gmail.com
|
||||||
|
|
||||||
|
# default app ADMIN
|
||||||
|
DEFAULT_ADMIN_EMAIL=taratur@gmail.com
|
||||||
|
DEFAULT_ADMIN_PASSWORD=irina7654321
|
||||||
|
DEFAULT_ADMIN_NAME=admin
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,4 +7,6 @@ frontend/node_modules
|
|||||||
frontend/dist
|
frontend/dist
|
||||||
frontend/.vite
|
frontend/.vite
|
||||||
node_modules
|
node_modules
|
||||||
|
pestgres_data/
|
||||||
|
redis_data/
|
||||||
|
|
||||||
|
|||||||
35
_.env
Normal file
35
_.env
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Environment variables declared in this file are automatically made available to Prisma.
|
||||||
|
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
|
||||||
|
|
||||||
|
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
||||||
|
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||||
|
|
||||||
|
DATABASE_URL="postgresql://root:irina76@localhost:5433/imk2?schema=public"
|
||||||
|
JWT_SECRET=some-secret
|
||||||
|
AWS_REGION=EU2
|
||||||
|
AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6
|
||||||
|
AWS_SECRET_ACCESS_KEY=6d4723e14c0d799b89948c24dbe983e4
|
||||||
|
AWS_S3_BUCKET_NAME=imk-data
|
||||||
|
AWS_ENDPOINT_URL=https://eu2.contabostorage.com
|
||||||
|
|
||||||
|
#Email Configuration
|
||||||
|
# SMTP_HOST=smtp.gmail.com
|
||||||
|
# SMTP_PORT=587
|
||||||
|
# SMTP_USER=taratur@gmail.com
|
||||||
|
# SMTP_PASS=dziy nccc svgg bovb
|
||||||
|
# EMAIL_FROM=taratur@gmail.com
|
||||||
|
|
||||||
|
SMTP_HOST=imk.mk
|
||||||
|
SMTP_PORT=465
|
||||||
|
SMTP_USER=mailer@imk.mk
|
||||||
|
SMTP_PASS=76Avtostoperski76
|
||||||
|
SMTP_FROM=mailer@imk.mk
|
||||||
|
# FRONTEND_URL=https://imk.mk
|
||||||
|
EMAIL_FROM=mailer@yandex.com
|
||||||
|
|
||||||
|
ADMIN_EMAIL=taratur@gmail.com
|
||||||
|
|
||||||
|
# default app ADMIN
|
||||||
|
DEFAULT_ADMIN_EMAIL=taratur@gmail.com
|
||||||
|
DEFAULT_ADMIN_PASSWORD=irina7654321
|
||||||
|
DEFAULT_ADMIN_NAME=admin
|
||||||
108
_docker-compose.yml
Normal file
108
_docker-compose.yml
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: imk-postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: imk_db
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
networks:
|
||||||
|
- imk_copy_imk_network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: imk-backend
|
||||||
|
# environment:
|
||||||
|
# - NODE_ENV=development
|
||||||
|
# - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/imk_db
|
||||||
|
# - AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6
|
||||||
|
# - AWS_ENDPOINT_URL=https://eu2.contabostorage.com
|
||||||
|
# - AWS_REGION=EU2
|
||||||
|
# - AWS_S3_BUCKET_NAME=imk-data
|
||||||
|
# - AWS_SECRET_ACCESS_KEY=6d4723e14c0d799b89948c24dbe983e4
|
||||||
|
# - DEFAULT_ADMIN_EMAIL=taratur@gmail.com
|
||||||
|
# - DEFAULT_ADMIN_NAME=admin
|
||||||
|
# - DEFAULT_ADMIN_PASSWORD=irina7654321
|
||||||
|
# - EMAIL_FROM=mailer@yandex.com
|
||||||
|
# - JWT_SECRET=some-secret
|
||||||
|
# - PORT=3000
|
||||||
|
# - SMTP_HOST=imk.mk
|
||||||
|
# - SMTP_PASS=76Avtostoperski76
|
||||||
|
# - SMTP_PORT=465
|
||||||
|
# - SMTP_USER=mailer@imk.mk
|
||||||
|
# ports:
|
||||||
|
# - "3000:3000"
|
||||||
|
# depends_on:
|
||||||
|
# postgres:
|
||||||
|
# condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
# - PORT=3000
|
||||||
|
- DATABASE_URL=postgresql://postgres:postgres@imk-postgres:5432/postgres?schema=public
|
||||||
|
- AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6
|
||||||
|
- AWS_ENDPOINT_URL=https://eu2.contabostorage.com
|
||||||
|
- AWS_REGION=EU2
|
||||||
|
- AWS_S3_BUCKET_NAME=imk-data
|
||||||
|
- AWS_SECRET_ACCESS_KEY=6d4723e14c0d799b89948c24dbe983e4
|
||||||
|
- DEFAULT_ADMIN_EMAIL=taratur@gmail.com
|
||||||
|
- DEFAULT_ADMIN_NAME=admin
|
||||||
|
- DEFAULT_ADMIN_PASSWORD=irina7654321
|
||||||
|
- EMAIL_FROM=mailer@yandex.com
|
||||||
|
- JWT_SECRET=some-secret
|
||||||
|
- PORT=3000
|
||||||
|
- SMTP_HOST=imk.mk
|
||||||
|
- SMTP_PASS=76Avtostoperski76
|
||||||
|
- SMTP_PORT=465
|
||||||
|
- SMTP_USER=mailer@imk.mk
|
||||||
|
# env_file:
|
||||||
|
# - .env
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 15s
|
||||||
|
networks:
|
||||||
|
- imk_copy_imk_network
|
||||||
|
volumes:
|
||||||
|
- ./backend:/usr/src/app
|
||||||
|
- /usr/src/app/node_modules
|
||||||
|
command: sh -c "npm run prisma:generate && npm run prisma:migrate:deploy && npm run start:dev"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
container_name: imk-redis
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
networks:
|
||||||
|
- imk_copy_imk_network
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
imk_copy_imk_network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
@ -3,8 +3,11 @@
|
|||||||
|
|
||||||
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
||||||
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||||
|
POSTGRES_USER=root
|
||||||
DATABASE_URL="postgresql://root:irina76@localhost:5432/imk?schema=public"
|
POSTGRES_PASSWORD=irina76
|
||||||
|
POSTGRES_DB=imk_db
|
||||||
|
DATABASE_URL="postgresql://postgres:postgres@postgres:5432/imk_db?schema=public"
|
||||||
|
#DATABASE_URL="postgresql://root:irina76@localhost:5432/imk?schema=public"
|
||||||
JWT_SECRET=some-secret
|
JWT_SECRET=some-secret
|
||||||
AWS_REGION=EU2
|
AWS_REGION=EU2
|
||||||
AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6
|
AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6
|
||||||
@ -27,10 +30,8 @@ SMTP_FROM=mailer@imk.mk
|
|||||||
# FRONTEND_URL=https://imk.mk
|
# FRONTEND_URL=https://imk.mk
|
||||||
EMAIL_FROM=mailer@yandex.com
|
EMAIL_FROM=mailer@yandex.com
|
||||||
|
|
||||||
|
|
||||||
ADMIN_EMAIL=taratur@gmail.com
|
ADMIN_EMAIL=taratur@gmail.com
|
||||||
|
|
||||||
|
|
||||||
# default app ADMIN
|
# default app ADMIN
|
||||||
DEFAULT_ADMIN_EMAIL=taratur@gmail.com
|
DEFAULT_ADMIN_EMAIL=taratur@gmail.com
|
||||||
DEFAULT_ADMIN_PASSWORD=irina7654321
|
DEFAULT_ADMIN_PASSWORD=irina7654321
|
||||||
|
|||||||
42
backend/Dockerfile
Normal file
42
backend/Dockerfile
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
FROM node:18.19.1-alpine3.18
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Install necessary tools and dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
python3 \
|
||||||
|
make \
|
||||||
|
g++ \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
COPY prisma ./prisma/
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
# RUN npm ci --only=production
|
||||||
|
|
||||||
|
# Generate Prisma client
|
||||||
|
RUN npx prisma generate
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Add healthcheck
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD wget -q --spider http://localhost:3000/health || exit 1
|
||||||
|
|
||||||
|
# Set Node options
|
||||||
|
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||||
|
|
||||||
|
# Start the application directly with node
|
||||||
|
CMD ["node", "dist/main.js"]
|
||||||
4867
backend/package-lock.json
generated
4867
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,19 +6,22 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"prebuild": "rimraf dist",
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "nest start --watch",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "nest start --debug --watch",
|
||||||
"start:prod": "node dist/main",
|
"start:prod": "node dist/main.js",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
"prisma:seed": "ts-node prisma/seed.ts"
|
"prisma:generate": "prisma generate",
|
||||||
|
"prisma:migrate:dev": "prisma migrate dev",
|
||||||
|
"prisma:migrate:deploy": "prisma migrate deploy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.679.0",
|
"@aws-sdk/client-s3": "^3.679.0",
|
||||||
@ -32,7 +35,7 @@
|
|||||||
"@nestjs/passport": "^10.0.3",
|
"@nestjs/passport": "^10.0.3",
|
||||||
"@nestjs/platform-express": "^10.4.6",
|
"@nestjs/platform-express": "^10.4.6",
|
||||||
"@nestjs/typeorm": "^10.0.2",
|
"@nestjs/typeorm": "^10.0.2",
|
||||||
"@prisma/client": "^5.12.1",
|
"@prisma/client": "^6.5.0",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
@ -44,8 +47,8 @@
|
|||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rimraf": "^5.0.0",
|
||||||
"typeorm": "^0.3.20"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.0.0",
|
"@nestjs/cli": "^10.0.0",
|
||||||
@ -65,7 +68,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"prisma": "^5.12.1",
|
"prisma": "^6.5.0",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
@ -74,9 +77,7 @@
|
|||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.1.3"
|
"typescript": "^5.1.3"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {},
|
||||||
"seed": "ts-node prisma/seed.ts"
|
|
||||||
},
|
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
"js",
|
"js",
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
||||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
||||||
return new (P || (P = Promise))(function (resolve, reject) {
|
|
||||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
||||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
||||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
||||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
||||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
||||||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
||||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
||||||
function step(op) {
|
|
||||||
if (f) throw new TypeError("Generator is already executing.");
|
|
||||||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
||||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
||||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
||||||
switch (op[0]) {
|
|
||||||
case 0: case 1: t = op; break;
|
|
||||||
case 4: _.label++; return { value: op[1], done: false };
|
|
||||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
||||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
||||||
default:
|
|
||||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
||||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
||||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
||||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
||||||
if (t[2]) _.ops.pop();
|
|
||||||
_.trys.pop(); continue;
|
|
||||||
}
|
|
||||||
op = body.call(thisArg, _);
|
|
||||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
||||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
var client_1 = require("@prisma/client");
|
|
||||||
var bcrypt = require("bcrypt");
|
|
||||||
var prisma = new client_1.PrismaClient();
|
|
||||||
function main() {
|
|
||||||
return __awaiter(this, void 0, void 0, function () {
|
|
||||||
var hashedPassword, admin;
|
|
||||||
return __generator(this, function (_a) {
|
|
||||||
switch (_a.label) {
|
|
||||||
case 0: return [4 /*yield*/, bcrypt.hash('admin123', 10)];
|
|
||||||
case 1:
|
|
||||||
hashedPassword = _a.sent();
|
|
||||||
return [4 /*yield*/, prisma.user.upsert({
|
|
||||||
where: { email: 'admin@example.com' },
|
|
||||||
update: {},
|
|
||||||
create: {
|
|
||||||
email: 'admin@example.com',
|
|
||||||
name: 'Admin User',
|
|
||||||
password: hashedPassword,
|
|
||||||
isAdmin: true,
|
|
||||||
},
|
|
||||||
})];
|
|
||||||
case 2:
|
|
||||||
admin = _a.sent();
|
|
||||||
console.log({ admin: admin });
|
|
||||||
return [2 /*return*/];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
main()
|
|
||||||
.catch(function (e) {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
})
|
|
||||||
.finally(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
||||||
return __generator(this, function (_a) {
|
|
||||||
switch (_a.label) {
|
|
||||||
case 0: return [4 /*yield*/, prisma.$disconnect()];
|
|
||||||
case 1:
|
|
||||||
_a.sent();
|
|
||||||
return [2 /*return*/];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}); });
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
// backend/prisma/seed.ts
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
import * as bcrypt from "bcrypt";
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const defaultAdminEmail = process.env.DEFAULT_ADMIN_EMAIL || "admin@imk.com";
|
|
||||||
const defaultAdminPassword =
|
|
||||||
process.env.DEFAULT_ADMIN_PASSWORD || "admin123456";
|
|
||||||
const defaultAdminName = process.env.DEFAULT_ADMIN_NAME || "System Admin";
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if admin already exists
|
|
||||||
const existingAdmin = await prisma.user.findUnique({
|
|
||||||
where: { email: defaultAdminEmail },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!existingAdmin) {
|
|
||||||
// Hash the password
|
|
||||||
const hashedPassword = await bcrypt.hash(defaultAdminPassword, 10);
|
|
||||||
|
|
||||||
// Create the admin user
|
|
||||||
const admin = await prisma.user.create({
|
|
||||||
data: {
|
|
||||||
email: defaultAdminEmail,
|
|
||||||
name: defaultAdminName,
|
|
||||||
password: hashedPassword,
|
|
||||||
isAdmin: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Default admin user created:", {
|
|
||||||
id: admin.id,
|
|
||||||
email: admin.email,
|
|
||||||
name: admin.name,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log("Default admin user already exists");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error creating default admin:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
})
|
|
||||||
.finally(async () => {
|
|
||||||
await prisma.$disconnect();
|
|
||||||
});
|
|
||||||
@ -17,6 +17,7 @@ import { DocumentsController } from "./documents/documents.controller";
|
|||||||
import { JwtModule } from "@nestjs/jwt";
|
import { JwtModule } from "@nestjs/jwt";
|
||||||
import { EmailModule } from "./email/email.module";
|
import { EmailModule } from "./email/email.module";
|
||||||
import { InitModule } from "./init/init.module";
|
import { InitModule } from "./init/init.module";
|
||||||
|
import { HealthController } from './health/health.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -35,7 +36,7 @@ import { InitModule } from "./init/init.module";
|
|||||||
EmailModule,
|
EmailModule,
|
||||||
InitModule,
|
InitModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController, AuthController, DocumentsController],
|
controllers: [AppController, AuthController, DocumentsController, HealthController],
|
||||||
providers: [
|
providers: [
|
||||||
AppService,
|
AppService,
|
||||||
UploadService,
|
UploadService,
|
||||||
|
|||||||
18
backend/src/health/health.controller.spec.ts
Normal file
18
backend/src/health/health.controller.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { HealthController } from './health.controller';
|
||||||
|
|
||||||
|
describe('HealthController', () => {
|
||||||
|
let controller: HealthController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [HealthController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<HealthController>(HealthController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
9
backend/src/health/health.controller.ts
Normal file
9
backend/src/health/health.controller.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Controller, Get } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Controller("health")
|
||||||
|
export class HealthController {
|
||||||
|
@Get()
|
||||||
|
check() {
|
||||||
|
return { status: "ok" };
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,5 +8,6 @@ import { InitService } from "./init.service";
|
|||||||
imports: [PrismaModule, ConfigModule],
|
imports: [PrismaModule, ConfigModule],
|
||||||
providers: [InitService],
|
providers: [InitService],
|
||||||
controllers: [InitController],
|
controllers: [InitController],
|
||||||
|
exports: [InitService],
|
||||||
})
|
})
|
||||||
export class InitModule {}
|
export class InitModule {}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
|
||||||
import { PrismaService } from "../prisma/prisma.service";
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import * as bcrypt from "bcrypt";
|
import * as bcrypt from "bcrypt";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InitService {
|
export class InitService implements OnModuleInit {
|
||||||
private readonly logger = new Logger(InitService.name);
|
private readonly logger = new Logger(InitService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -12,23 +12,30 @@ export class InitService {
|
|||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
this.logger.log("Initializing application and creating default admin...");
|
||||||
|
await this.initializeSystem();
|
||||||
|
}
|
||||||
|
|
||||||
async initializeSystem() {
|
async initializeSystem() {
|
||||||
this.logger.log("Starting system initialization...");
|
this.logger.log("Starting system initialization...");
|
||||||
|
|
||||||
const defaultAdminEmail =
|
const defaultAdminEmail =
|
||||||
this.configService.get("DEFAULT_ADMIN_EMAIL") || "taratur@gmail";
|
this.configService.get("DEFAULT_ADMIN_EMAIL") || "taratur@gmail.com";
|
||||||
const defaultAdminPassword =
|
const defaultAdminPassword =
|
||||||
this.configService.get("DEFAULT_ADMIN_PASSWORD") || "irina7654321";
|
this.configService.get("DEFAULT_ADMIN_PASSWORD") || "irina7654321";
|
||||||
const defaultAdminName =
|
const defaultAdminName =
|
||||||
this.configService.get("DEFAULT_ADMIN_NAME") || "System Admin";
|
this.configService.get("DEFAULT_ADMIN_NAME") || "System Admin";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
this.logger.log("Checking for existing admin user...");
|
||||||
// Check if admin already exists
|
// Check if admin already exists
|
||||||
const existingAdmin = await this.prisma.user.findUnique({
|
const existingAdmin = await this.prisma.user.findUnique({
|
||||||
where: { email: defaultAdminEmail },
|
where: { email: defaultAdminEmail },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingAdmin) {
|
if (!existingAdmin) {
|
||||||
|
this.logger.log("No admin found. Creating default admin user...");
|
||||||
// Hash the password
|
// Hash the password
|
||||||
const hashedPassword = await bcrypt.hash(defaultAdminPassword, 10);
|
const hashedPassword = await bcrypt.hash(defaultAdminPassword, 10);
|
||||||
|
|
||||||
@ -42,7 +49,12 @@ export class InitService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.log("Default admin user created successfully");
|
this.logger.log("Default admin user created successfully", {
|
||||||
|
id: admin.id,
|
||||||
|
email: admin.email,
|
||||||
|
name: admin.name,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "System initialized successfully",
|
message: "System initialized successfully",
|
||||||
@ -54,6 +66,12 @@ export class InitService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.log("Default admin user already exists", {
|
||||||
|
id: existingAdmin.id,
|
||||||
|
email: existingAdmin.email,
|
||||||
|
name: existingAdmin.name,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "System already initialized",
|
message: "System already initialized",
|
||||||
|
|||||||
@ -1,46 +1,43 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
// src/main.ts
|
||||||
import { AppModule } from './app.module';
|
import { NestFactory } from "@nestjs/core";
|
||||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
import { AppModule } from "./app.module";
|
||||||
|
import { Logger, ValidationPipe } from "@nestjs/common";
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const logger = new Logger('Bootstrap');
|
const logger = new Logger("Bootstrap");
|
||||||
logger.log('Starting application...');
|
|
||||||
|
try {
|
||||||
|
logger.log("Starting application...");
|
||||||
|
|
||||||
const app = await NestFactory.create(AppModule, {
|
const app = await NestFactory.create(AppModule, {
|
||||||
logger: ['error', 'warn', 'log', 'debug', 'verbose'], // Enable all log levels
|
logger: ["error", "warn", "log", "debug", "verbose"],
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log('Configuring application...');
|
// Enable CORS
|
||||||
|
app.enableCors({
|
||||||
|
origin: true,
|
||||||
|
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
|
||||||
|
credentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
// Enable validation with detailed error messages
|
// Global pipes
|
||||||
app.useGlobalPipes(new ValidationPipe({
|
app.useGlobalPipes(
|
||||||
|
new ValidationPipe({
|
||||||
transform: true,
|
transform: true,
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
forbidNonWhitelisted: true,
|
}),
|
||||||
enableDebugMessages: true, // Add detailed validation error messages
|
);
|
||||||
validationError: {
|
|
||||||
target: false,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Enable CORS with credentials
|
|
||||||
app.enableCors({
|
|
||||||
origin: true, // or specify your frontend URL
|
|
||||||
credentials: true,
|
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
||||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
logger.log(`Starting server on port ${port}...`);
|
|
||||||
|
|
||||||
await app.listen(port);
|
logger.log(`Attempting to start server on port ${port}...`);
|
||||||
|
await app.listen(port, "0.0.0.0");
|
||||||
|
|
||||||
logger.log(`Application is running on: ${await app.getUrl()}`);
|
logger.log(`Application is running on: ${await app.getUrl()}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to start application:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, promise) => {
|
|
||||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
15
backend/temp.sql
Normal file
15
backend/temp.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
INSERT INTO "User" (
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
password,
|
||||||
|
"isAdmin",
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt"
|
||||||
|
) VALUES (
|
||||||
|
'taratur@gmail.com',
|
||||||
|
'admin',
|
||||||
|
'$2b$10$b/cbgtQqMp.a.mbhQoyPa.60GyVGJDpW3jHesWHZtmL7jZr2cpXxm',
|
||||||
|
true,
|
||||||
|
CURRENT_TIMESTAMP,
|
||||||
|
CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
91
docker-compose.yml
Normal file
91
docker-compose.yml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
container_name: imk-backend
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
- DATABASE_URL=postgresql://postgres:postgres@imk-postgres:5432/postgres?schema=public
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 2G
|
||||||
|
reservations:
|
||||||
|
memory: 512M
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
networks:
|
||||||
|
- app_network
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD",
|
||||||
|
"wget",
|
||||||
|
"-q",
|
||||||
|
"--spider",
|
||||||
|
"http://localhost:3000/health || exit 1",
|
||||||
|
]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 15s
|
||||||
|
restart: unless-stopped
|
||||||
|
postgres:
|
||||||
|
container_name: imk-postgres
|
||||||
|
image: postgres:14-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
PGDATA: /var/lib/postgresql/data/pgdata
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- app_network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: imk-redis
|
||||||
|
image: redis:alpine
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- app_network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app_network:
|
||||||
|
driver: bridge
|
||||||
|
name: app_network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
name: imk_postgres_data
|
||||||
|
redis_data:
|
||||||
|
name: imk_redis_data
|
||||||
14
docker-reset.md
Normal file
14
docker-reset.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Stop everything
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Remove all containers
|
||||||
|
docker rm -f $(docker ps -a -q)
|
||||||
|
|
||||||
|
# Remove all volumes
|
||||||
|
docker volume rm $(docker volume ls -q)
|
||||||
|
|
||||||
|
# Remove all images
|
||||||
|
docker rmi -f $(docker images -q)
|
||||||
|
|
||||||
|
# Start fresh
|
||||||
|
docker-compose up --build
|
||||||
253
frontend/documentation.md
Normal file
253
frontend/documentation.md
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
# IMK Platform Documentation
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
1. [Introduction](#introduction)
|
||||||
|
2. [Features](#features)
|
||||||
|
3. [Technical Stack](#technical-stack)
|
||||||
|
4. [Architecture](#architecture)
|
||||||
|
5. [User Flows](#user-flows)
|
||||||
|
6. [API Documentation](#api-documentation)
|
||||||
|
7. [Security](#security)
|
||||||
|
8. [Deployment](#deployment)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
IMK Platform is a modern web application built to manage and share documents securely. The platform provides robust user management, document handling, and secure sharing capabilities.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### User Management
|
||||||
|
- User registration and authentication
|
||||||
|
- Role-based access control (Admin, User)
|
||||||
|
- Password reset functionality
|
||||||
|
- Email notifications for account activities
|
||||||
|
- Profile management
|
||||||
|
|
||||||
|
### Document Management
|
||||||
|
- Document upload and storage
|
||||||
|
- Document sharing between users
|
||||||
|
- Document version control
|
||||||
|
- Document metadata management
|
||||||
|
- Secure document access control
|
||||||
|
|
||||||
|
### Email Notifications
|
||||||
|
- Welcome emails for new users
|
||||||
|
- Password reset notifications
|
||||||
|
- Document sharing notifications
|
||||||
|
- Password change confirmations
|
||||||
|
|
||||||
|
### Administrative Features
|
||||||
|
- User management dashboard
|
||||||
|
- Document oversight
|
||||||
|
- System monitoring
|
||||||
|
- Access control management
|
||||||
|
|
||||||
|
## Technical Stack
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- React.js with Vite
|
||||||
|
- TypeScript for type safety
|
||||||
|
- TailwindCSS for styling
|
||||||
|
- Shadcn UI components
|
||||||
|
- React Query for state management
|
||||||
|
- React Router for navigation
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- NestJS framework
|
||||||
|
- TypeScript
|
||||||
|
- Prisma ORM
|
||||||
|
- PostgreSQL database
|
||||||
|
- Node.js runtime
|
||||||
|
- JWT authentication
|
||||||
|
- Nodemailer for email services
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Frontend Architecture
|
||||||
|
- Component-based architecture
|
||||||
|
- Responsive design
|
||||||
|
- State management using React Query
|
||||||
|
- Protected routes with authentication
|
||||||
|
- Form validation and error handling
|
||||||
|
|
||||||
|
### Backend Architecture
|
||||||
|
- RESTful API design
|
||||||
|
- Modular architecture with NestJS
|
||||||
|
- Database abstraction with Prisma
|
||||||
|
- Email service integration
|
||||||
|
- JWT-based authentication
|
||||||
|
- Role-based authorization
|
||||||
|
|
||||||
|
## User Flows
|
||||||
|
|
||||||
|
### Authentication Flow
|
||||||
|
1. User Registration
|
||||||
|
- User fills registration form
|
||||||
|
- System validates input
|
||||||
|
- Welcome email sent
|
||||||
|
- User redirected to login
|
||||||
|
|
||||||
|
2. Login Flow
|
||||||
|
- User enters credentials
|
||||||
|
- System validates credentials
|
||||||
|
- JWT token issued
|
||||||
|
- User redirected to dashboard
|
||||||
|
|
||||||
|
3. Password Reset Flow
|
||||||
|
- User requests password reset
|
||||||
|
- System sends reset email
|
||||||
|
- User clicks reset link
|
||||||
|
- User sets new password
|
||||||
|
- Confirmation email sent
|
||||||
|
|
||||||
|
### Document Management Flow
|
||||||
|
1. Document Upload
|
||||||
|
- User selects document
|
||||||
|
- System validates document
|
||||||
|
- Document metadata captured
|
||||||
|
- Document stored securely
|
||||||
|
|
||||||
|
2. Document Sharing
|
||||||
|
- User selects document to share
|
||||||
|
- User selects recipient(s)
|
||||||
|
- System sends notification
|
||||||
|
- Access granted to recipient
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
### Authentication Endpoints
|
||||||
|
- POST /auth/register - User registration
|
||||||
|
- POST /auth/login - User login
|
||||||
|
- POST /auth/reset-password - Password reset request
|
||||||
|
- POST /auth/change-password - Password change
|
||||||
|
|
||||||
|
### User Endpoints
|
||||||
|
- GET /users/profile - Get user profile
|
||||||
|
- PUT /users/profile - Update user profile
|
||||||
|
- GET /users - List users (admin only)
|
||||||
|
- PUT /users/:id - Update user (admin only)
|
||||||
|
|
||||||
|
### Document Endpoints
|
||||||
|
- POST /documents - Upload document
|
||||||
|
- GET /documents - List documents
|
||||||
|
- GET /documents/:id - Get document details
|
||||||
|
- PUT /documents/:id - Update document
|
||||||
|
- DELETE /documents/:id - Delete document
|
||||||
|
- POST /documents/:id/share - Share document
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
### Authentication Security
|
||||||
|
- JWT token-based authentication
|
||||||
|
- Password hashing with bcrypt
|
||||||
|
- Rate limiting on auth endpoints
|
||||||
|
- Session management
|
||||||
|
- CSRF protection
|
||||||
|
|
||||||
|
### Data Security
|
||||||
|
- HTTPS encryption
|
||||||
|
- Input validation
|
||||||
|
- XSS protection
|
||||||
|
- SQL injection prevention
|
||||||
|
- File type validation
|
||||||
|
|
||||||
|
### Access Control
|
||||||
|
- Role-based access control
|
||||||
|
- Document-level permissions
|
||||||
|
- API endpoint protection
|
||||||
|
- Resource isolation
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Frontend Deployment
|
||||||
|
- Static site hosting
|
||||||
|
- CDN integration
|
||||||
|
- Environment configuration
|
||||||
|
- Build optimization
|
||||||
|
|
||||||
|
### Backend Deployment
|
||||||
|
- Node.js runtime environment
|
||||||
|
- Process management with PM2
|
||||||
|
- Nginx reverse proxy
|
||||||
|
- SSL/TLS configuration
|
||||||
|
- Database backup system
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
Frontend:
|
||||||
|
```env
|
||||||
|
VITE_API_URL=https://api.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Backend:
|
||||||
|
```env
|
||||||
|
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
||||||
|
SMTP_HOST=smtp.example.com
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=user@example.com
|
||||||
|
SMTP_PASS=password
|
||||||
|
EMAIL_FROM=noreply@example.com
|
||||||
|
JWT_SECRET=your-secret-key
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
1. Clone the repository
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
# Frontend
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
3. Set up environment variables
|
||||||
|
4. Start development servers:
|
||||||
|
```bash
|
||||||
|
# Frontend
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
npm run start:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Deployment
|
||||||
|
1. Build applications:
|
||||||
|
```bash
|
||||||
|
# Frontend
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
2. Configure environment variables
|
||||||
|
3. Start production servers:
|
||||||
|
```bash
|
||||||
|
# Frontend
|
||||||
|
serve -s dist
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
npm run start:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support and Maintenance
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
- Application performance monitoring
|
||||||
|
- Error tracking and logging
|
||||||
|
- Database monitoring
|
||||||
|
- Email service monitoring
|
||||||
|
|
||||||
|
### Backup and Recovery
|
||||||
|
- Database backup strategy
|
||||||
|
- Document backup system
|
||||||
|
- System configuration backup
|
||||||
|
- Recovery procedures
|
||||||
|
|
||||||
|
### Updates and Maintenance
|
||||||
|
- Regular security updates
|
||||||
|
- Dependency updates
|
||||||
|
- Performance optimization
|
||||||
|
- Feature updates
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { createContext, useContext, useState, useEffect } from 'react';
|
import { createContext, useContext, useState, useEffect } from "react";
|
||||||
import api from '../services/api';
|
import api from "../services/api";
|
||||||
|
|
||||||
const AuthContext = createContext(null);
|
const AuthContext = createContext(null);
|
||||||
|
|
||||||
@ -10,17 +10,17 @@ export const AuthProvider = ({ children }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUser = async () => {
|
const fetchUser = async () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem("token");
|
||||||
if (!token) {
|
if (!token) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await api.get('/auth/user-info'); // Updated endpoint
|
const response = await api.get("/auth/user-info"); // Updated endpoint
|
||||||
setUser(response.data);
|
setUser(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch user info:', error);
|
console.error("Failed to fetch user info:", error);
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem("token");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -28,44 +28,29 @@ export const AuthProvider = ({ children }) => {
|
|||||||
|
|
||||||
fetchUser();
|
fetchUser();
|
||||||
}, []);
|
}, []);
|
||||||
// const login = async (username, password) => {
|
|
||||||
// try {
|
|
||||||
// const response = await api.post('/auth/login', { username, password });
|
|
||||||
// const { access_token, user } = response.data; // Make sure this matches your backend response
|
|
||||||
|
|
||||||
// localStorage.setItem('token', access_token);
|
|
||||||
// setUser(user);
|
|
||||||
// return user;
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error('Login error:', error);
|
|
||||||
// throw error;
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
const login = async (username, password) => {
|
const login = async (username, password) => {
|
||||||
try {
|
try {
|
||||||
const response = await api.post('/auth/login', { username, password });
|
const response = await api.post("/auth/login", { username, password });
|
||||||
console.log('Login response:', response.data); // Debug log
|
console.log("Login response:", response.data); // Debug log
|
||||||
|
|
||||||
const { access_token } = response.data;
|
const { access_token } = response.data;
|
||||||
localStorage.setItem('token', access_token);
|
localStorage.setItem("token", access_token);
|
||||||
|
|
||||||
// Fetch user info after login
|
// Fetch user info after login
|
||||||
const userResponse = await api.get('/auth/user-info');
|
const userResponse = await api.get("/auth/user-info");
|
||||||
const userData = userResponse.data;
|
const userData = userResponse.data;
|
||||||
|
|
||||||
setUser(userData);
|
setUser(userData);
|
||||||
return userData; // Return the user data for redirect logic
|
return userData; // Return the user data for redirect logic
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login error:', error);
|
console.error("Login error:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem("token");
|
||||||
setUser(null);
|
setUser(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -79,7 +64,7 @@ export const AuthProvider = ({ children }) => {
|
|||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const context = useContext(AuthContext);
|
const context = useContext(AuthContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error('useAuth must be used within an AuthProvider');
|
throw new Error("useAuth must be used within an AuthProvider");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
261
frontend/src/services/__api.js
Normal file
261
frontend/src/services/__api.js
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
// Create axios instance with default config
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: "http://localhost:3000",
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Authentication endpoints
|
||||||
|
export const login = async (credentials) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post(
|
||||||
|
"/auth/login",
|
||||||
|
{
|
||||||
|
email: credentials.email,
|
||||||
|
password: credentials.password,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const register = async (userData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post("/auth/register", userData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logout = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.post("/auth/logout");
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Document endpoints
|
||||||
|
export const createDocument = async (documentData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post("/documents", documentData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDocument = async (documentId) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/documents/${documentId}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateDocument = async (documentId, documentData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(`/documents/${documentId}`, documentData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteDocument = async (documentId) => {
|
||||||
|
try {
|
||||||
|
const response = await api.delete(`/documents/${documentId}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAllDocuments = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get("/documents");
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shareDocument = async (documentId, userData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post(`/documents/${documentId}/share`, userData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSharedDocuments = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get("/documents/shared");
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uploadDocument = async (file) => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
const response = await api.post("/documents/upload", formData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserInfo = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get("/users/me");
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const getAllUsers = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get("/users");
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const createUser = async (user) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post("/users", user);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const resetUserPassword = async (userId, password) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(`admin/users/${userId}/reset-password`, {
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const downloadDocument = async (documentId) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/documents/${documentId}/download`, {
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetPassword = async (userId, password) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put("/auth/reset-password", {
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const forgotPassword = async (email) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post("/auth/forgot-password", {
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
throw error.response.data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add authorization header to requests if token exists
|
||||||
|
api.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default api;
|
||||||
@ -1,50 +1,55 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
|
|
||||||
|
const API_URL = "http://localhost:3000";
|
||||||
const API_URL = 'http://localhost:3000';
|
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: API_URL,
|
baseURL: API_URL,
|
||||||
// withCredentials: true,
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json, text/plain, */*",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add authorization header to all requests
|
// Add authorization header to all requests
|
||||||
api.interceptors.request.use(
|
api.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem("token");
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers['Authorization'] = `Bearer ${token}`; // Make sure this matches your backend expectation
|
config.headers["Authorization"] = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getSharedDocuments = (userId) => {
|
export const getSharedDocuments = (userId) => {
|
||||||
return api.get(`/documents/shared/${userId}`);
|
return api.get(`/documents/shared/${userId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getToken = () => localStorage.getItem("token");
|
||||||
const getToken = () => localStorage.getItem('token');
|
|
||||||
export const downloadDocument = async (key) => {
|
export const downloadDocument = async (key) => {
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`/documents/download/${encodeURIComponent(key)}`, {
|
const response = await api.get(
|
||||||
responseType: 'blob',
|
`/documents/download/${encodeURIComponent(key)}`,
|
||||||
|
{
|
||||||
|
responseType: "blob",
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/octet-stream',
|
Accept: "application/octet-stream",
|
||||||
}
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Create blob URL and trigger download
|
// Create blob URL and trigger download
|
||||||
const blob = new Blob([response.data]);
|
const blob = new Blob([response.data]);
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement("a");
|
||||||
link.href = url;
|
link.href = url;
|
||||||
|
|
||||||
// Extract filename from key
|
// Extract filename from key
|
||||||
const fileName = key.split('/').pop();
|
const fileName = key.split("/").pop();
|
||||||
link.download = fileName;
|
link.download = fileName;
|
||||||
|
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
@ -54,54 +59,58 @@ export const downloadDocument = async (key) => {
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Download error:', error);
|
console.error("Download error:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const createUser = (userData) => {
|
export const createUser = (userData) => {
|
||||||
return api.post('/admin/users', {
|
return api.post("/admin/users", {
|
||||||
name: userData.name,
|
name: userData.name,
|
||||||
email: userData.email,
|
email: userData.email,
|
||||||
password: userData.password,
|
password: userData.password,
|
||||||
isAdmin: userData.isAdmin
|
isAdmin: userData.isAdmin,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const login = (username, password) => api.post('/auth/login', { username, password });
|
export const login = (username, password) =>
|
||||||
export const shareDocument = (documentId, userId) => api.post(`/admin/documents/${documentId}/share`, { userId });
|
api.post("/auth/login", { username, password });
|
||||||
export const updateDocumentStatus = (documentId, status) => api.put(`/admin/documents/${documentId}/status`, { status });
|
export const shareDocument = (documentId, userId) =>
|
||||||
|
api.post(`/admin/documents/${documentId}/share`, { userId });
|
||||||
|
export const updateDocumentStatus = (documentId, status) =>
|
||||||
|
api.put(`/admin/documents/${documentId}/status`, { status });
|
||||||
|
|
||||||
export const uploadDocument = async (formData) => {
|
export const uploadDocument = async (formData) => {
|
||||||
try {
|
try {
|
||||||
// Debug log
|
// Debug log
|
||||||
console.log('Sending to server:', {
|
console.log("Sending to server:", {
|
||||||
title: formData.get('title'),
|
title: formData.get("title"),
|
||||||
sharedWithId: formData.get('sharedWithId'),
|
sharedWithId: formData.get("sharedWithId"),
|
||||||
uploadedById: formData.get('uploadedById')
|
uploadedById: formData.get("uploadedById"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await api.post('/admin/documents', formData, {
|
const response = await api.post("/admin/documents", formData, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
"Content-Type": "multipart/form-data",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('API Error:', error.response?.data);
|
console.error("API Error:", error.response?.data);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const getUserInfo = () => api.get('/auth/user-info');
|
export const getUserInfo = () => api.get("/auth/user-info");
|
||||||
|
|
||||||
|
export const getAllDocuments = () => api.get("/admin/documents");
|
||||||
export const getAllDocuments = () => api.get('/admin/documents');
|
|
||||||
// export const getSharedDocuments = (userId) => api.get(`/documents/shared/${userId}`);
|
// export const getSharedDocuments = (userId) => api.get(`/documents/shared/${userId}`);
|
||||||
export const getAllUsers = () => api.get('/admin/users');
|
export const getAllUsers = () => api.get("/admin/users");
|
||||||
export const resetUserPassword = (userId, newPassword) => api.post(`/admin/users/${userId}/reset-password`, { password: newPassword });
|
export const resetUserPassword = (userId, newPassword) =>
|
||||||
|
api.post(`/admin/users/${userId}/reset-password`, { password: newPassword });
|
||||||
|
|
||||||
// Password reset endpoints
|
// Password reset endpoints
|
||||||
export const forgotPassword = (email) => api.post('/auth/forgot-password', { email });
|
export const forgotPassword = (email) =>
|
||||||
export const resetPassword = (token, newPassword) => api.post('/auth/reset-password', { token, newPassword });
|
api.post("/auth/forgot-password", { email });
|
||||||
|
export const resetPassword = (token, newPassword) =>
|
||||||
|
api.post("/auth/reset-password", { token, newPassword });
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|||||||
@ -1,7 +1,17 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
})
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "http://localhost:3000",
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "imk_copy",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user