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/.vite
|
||||
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.
|
||||
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||
|
||||
DATABASE_URL="postgresql://root:irina76@localhost:5432/imk?schema=public"
|
||||
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
|
||||
@ -27,10 +30,8 @@ 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
|
||||
|
||||
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,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --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",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"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",
|
||||
"prisma:seed": "ts-node prisma/seed.ts"
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate:dev": "prisma migrate dev",
|
||||
"prisma:migrate:deploy": "prisma migrate deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.679.0",
|
||||
@ -32,7 +35,7 @@
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.4.6",
|
||||
"@nestjs/typeorm": "^10.0.2",
|
||||
"@prisma/client": "^5.12.1",
|
||||
"@prisma/client": "^6.5.0",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"bcrypt": "^5.1.1",
|
||||
@ -44,8 +47,8 @@
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.13.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
"typeorm": "^0.3.20"
|
||||
"rimraf": "^5.0.0",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
@ -65,7 +68,7 @@
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"prettier": "^3.0.0",
|
||||
"prisma": "^5.12.1",
|
||||
"prisma": "^6.5.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.1.0",
|
||||
@ -74,9 +77,7 @@
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "ts-node prisma/seed.ts"
|
||||
},
|
||||
"prisma": {},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"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 { EmailModule } from "./email/email.module";
|
||||
import { InitModule } from "./init/init.module";
|
||||
import { HealthController } from './health/health.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -35,7 +36,7 @@ import { InitModule } from "./init/init.module";
|
||||
EmailModule,
|
||||
InitModule,
|
||||
],
|
||||
controllers: [AppController, AuthController, DocumentsController],
|
||||
controllers: [AppController, AuthController, DocumentsController, HealthController],
|
||||
providers: [
|
||||
AppService,
|
||||
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],
|
||||
providers: [InitService],
|
||||
controllers: [InitController],
|
||||
exports: [InitService],
|
||||
})
|
||||
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 { ConfigService } from "@nestjs/config";
|
||||
import * as bcrypt from "bcrypt";
|
||||
|
||||
@Injectable()
|
||||
export class InitService {
|
||||
export class InitService implements OnModuleInit {
|
||||
private readonly logger = new Logger(InitService.name);
|
||||
|
||||
constructor(
|
||||
@ -12,23 +12,30 @@ export class InitService {
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async onModuleInit() {
|
||||
this.logger.log("Initializing application and creating default admin...");
|
||||
await this.initializeSystem();
|
||||
}
|
||||
|
||||
async initializeSystem() {
|
||||
this.logger.log("Starting system initialization...");
|
||||
|
||||
const defaultAdminEmail =
|
||||
this.configService.get("DEFAULT_ADMIN_EMAIL") || "taratur@gmail";
|
||||
this.configService.get("DEFAULT_ADMIN_EMAIL") || "taratur@gmail.com";
|
||||
const defaultAdminPassword =
|
||||
this.configService.get("DEFAULT_ADMIN_PASSWORD") || "irina7654321";
|
||||
const defaultAdminName =
|
||||
this.configService.get("DEFAULT_ADMIN_NAME") || "System Admin";
|
||||
|
||||
try {
|
||||
this.logger.log("Checking for existing admin user...");
|
||||
// Check if admin already exists
|
||||
const existingAdmin = await this.prisma.user.findUnique({
|
||||
where: { email: defaultAdminEmail },
|
||||
});
|
||||
|
||||
if (!existingAdmin) {
|
||||
this.logger.log("No admin found. Creating default admin user...");
|
||||
// Hash the password
|
||||
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 {
|
||||
success: true,
|
||||
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 {
|
||||
success: true,
|
||||
message: "System already initialized",
|
||||
|
||||
@ -1,46 +1,43 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
// src/main.ts
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { AppModule } from "./app.module";
|
||||
import { Logger, ValidationPipe } from "@nestjs/common";
|
||||
|
||||
async function bootstrap() {
|
||||
const logger = new Logger('Bootstrap');
|
||||
logger.log('Starting application...');
|
||||
const logger = new Logger("Bootstrap");
|
||||
|
||||
try {
|
||||
logger.log("Starting application...");
|
||||
|
||||
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
|
||||
app.useGlobalPipes(new ValidationPipe({
|
||||
// Global pipes
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
transform: 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;
|
||||
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()}`);
|
||||
} 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();
|
||||
|
||||
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 api from '../services/api';
|
||||
import { createContext, useContext, useState, useEffect } from "react";
|
||||
import api from "../services/api";
|
||||
|
||||
const AuthContext = createContext(null);
|
||||
|
||||
@ -10,17 +10,17 @@ export const AuthProvider = ({ children }) => {
|
||||
useEffect(() => {
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await api.get('/auth/user-info'); // Updated endpoint
|
||||
const response = await api.get("/auth/user-info"); // Updated endpoint
|
||||
setUser(response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch user info:', error);
|
||||
localStorage.removeItem('token');
|
||||
console.error("Failed to fetch user info:", error);
|
||||
localStorage.removeItem("token");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@ -28,44 +28,29 @@ export const AuthProvider = ({ children }) => {
|
||||
|
||||
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) => {
|
||||
try {
|
||||
const response = await api.post('/auth/login', { username, password });
|
||||
console.log('Login response:', response.data); // Debug log
|
||||
const response = await api.post("/auth/login", { username, password });
|
||||
console.log("Login response:", response.data); // Debug log
|
||||
|
||||
const { access_token } = response.data;
|
||||
localStorage.setItem('token', access_token);
|
||||
localStorage.setItem("token", access_token);
|
||||
|
||||
// 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;
|
||||
|
||||
setUser(userData);
|
||||
return userData; // Return the user data for redirect logic
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
console.error("Login error:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem("token");
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
@ -79,7 +64,7 @@ export const AuthProvider = ({ children }) => {
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
throw new Error("useAuth must be used within an AuthProvider");
|
||||
}
|
||||
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({
|
||||
baseURL: API_URL,
|
||||
// withCredentials: true,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
Accept: "application/json, text/plain, */*",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
// Add authorization header to all requests
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
config.headers['Authorization'] = `Bearer ${token}`; // Make sure this matches your backend expectation
|
||||
config.headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const getSharedDocuments = (userId) => {
|
||||
return api.get(`/documents/shared/${userId}`);
|
||||
};
|
||||
|
||||
|
||||
const getToken = () => localStorage.getItem('token');
|
||||
const getToken = () => localStorage.getItem("token");
|
||||
export const downloadDocument = async (key) => {
|
||||
try {
|
||||
const response = await api.get(`/documents/download/${encodeURIComponent(key)}`, {
|
||||
responseType: 'blob',
|
||||
const response = await api.get(
|
||||
`/documents/download/${encodeURIComponent(key)}`,
|
||||
{
|
||||
responseType: "blob",
|
||||
headers: {
|
||||
'Accept': 'application/octet-stream',
|
||||
}
|
||||
});
|
||||
Accept: "application/octet-stream",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Create blob URL and trigger download
|
||||
const blob = new Blob([response.data]);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
|
||||
// Extract filename from key
|
||||
const fileName = key.split('/').pop();
|
||||
const fileName = key.split("/").pop();
|
||||
link.download = fileName;
|
||||
|
||||
document.body.appendChild(link);
|
||||
@ -54,54 +59,58 @@ export const downloadDocument = async (key) => {
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Download error:', error);
|
||||
console.error("Download error:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const createUser = (userData) => {
|
||||
return api.post('/admin/users', {
|
||||
return api.post("/admin/users", {
|
||||
name: userData.name,
|
||||
email: userData.email,
|
||||
password: userData.password,
|
||||
isAdmin: userData.isAdmin
|
||||
isAdmin: userData.isAdmin,
|
||||
});
|
||||
};
|
||||
export const login = (username, password) => api.post('/auth/login', { username, password });
|
||||
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 login = (username, password) =>
|
||||
api.post("/auth/login", { username, password });
|
||||
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) => {
|
||||
try {
|
||||
// Debug log
|
||||
console.log('Sending to server:', {
|
||||
title: formData.get('title'),
|
||||
sharedWithId: formData.get('sharedWithId'),
|
||||
uploadedById: formData.get('uploadedById')
|
||||
console.log("Sending to server:", {
|
||||
title: formData.get("title"),
|
||||
sharedWithId: formData.get("sharedWithId"),
|
||||
uploadedById: formData.get("uploadedById"),
|
||||
});
|
||||
|
||||
const response = await api.post('/admin/documents', formData, {
|
||||
const response = await api.post("/admin/documents", formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error.response?.data);
|
||||
console.error("API Error:", error.response?.data);
|
||||
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 getAllUsers = () => api.get('/admin/users');
|
||||
export const resetUserPassword = (userId, newPassword) => api.post(`/admin/users/${userId}/reset-password`, { password: newPassword });
|
||||
export const getAllUsers = () => api.get("/admin/users");
|
||||
export const resetUserPassword = (userId, newPassword) =>
|
||||
api.post(`/admin/users/${userId}/reset-password`, { password: newPassword });
|
||||
|
||||
// Password reset endpoints
|
||||
export const forgotPassword = (email) => api.post('/auth/forgot-password', { email });
|
||||
export const resetPassword = (token, newPassword) => api.post('/auth/reset-password', { token, newPassword });
|
||||
export const forgotPassword = (email) =>
|
||||
api.post("/auth/forgot-password", { email });
|
||||
export const resetPassword = (token, newPassword) =>
|
||||
api.post("/auth/reset-password", { token, newPassword });
|
||||
|
||||
export default api;
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
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