diff --git a/backend/.env b/backend/.env index ff2c520..b9f5661 100644 --- a/backend/.env +++ b/backend/.env @@ -36,3 +36,6 @@ ADMIN_EMAIL=taratur@gmail.com DEFAULT_ADMIN_EMAIL=taratur@gmail.com DEFAULT_ADMIN_PASSWORD=irina7654321 DEFAULT_ADMIN_NAME=admin + +CORS_ORIGIN=http://localhost:5173 +NODE_ENV=development diff --git a/backend/package-lock.json b/backend/package-lock.json index 171ba95..c0f5193 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -32,6 +32,7 @@ "passport-local": "^1.0.0", "pg": "^8.13.1", "reflect-metadata": "^0.1.13", + "rimraf": "^5.0.0", "rxjs": "^7.8.1" }, "devDependencies": { @@ -41,7 +42,7 @@ "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", + "@types/node": "^20.17.28", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", "@types/supertest": "^2.0.12", @@ -2947,6 +2948,65 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@mapbox/node-pre-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@nestjs/cli": { "version": "10.4.9", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.9.tgz", @@ -7378,6 +7438,69 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flat-cache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/flatted": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", @@ -10825,64 +10948,20 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", diff --git a/backend/package.json b/backend/package.json index 901dd9d..8a43d41 100644 --- a/backend/package.json +++ b/backend/package.json @@ -57,7 +57,7 @@ "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", + "@types/node": "^20.17.28", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", "@types/supertest": "^2.0.12", diff --git a/backend/src/main.ts b/backend/src/main.ts index 44fdc7f..583d885 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,4 +1,3 @@ -// src/main.ts import { NestFactory } from "@nestjs/core"; import { AppModule } from "./app.module"; import { Logger, ValidationPipe } from "@nestjs/common"; @@ -15,9 +14,26 @@ async function bootstrap() { // Enable CORS app.enableCors({ - origin: true, + origin: [ + "https://www.placebo.mk", + "https://placebo.mk", + "http://localhost:5173", + ], methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS", credentials: true, + allowedHeaders: [ + "Origin", + "X-Requested-With", + "Content-Type", + "Accept", + "Authorization", + ], + exposedHeaders: [ + 'Access-Control-Allow-Origin', + 'Access-Control-Allow-Credentials', + ], + preflightContinue: false, + optionsSuccessStatus: 204, }); // Global pipes diff --git a/docker-compose.yml b/docker-compose.yml index 0c0da4f..0555ce8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,11 @@ version: "3.8" services: backend: + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" container_name: imk-backend build: context: ./backend @@ -12,6 +17,7 @@ services: - NODE_ENV=production - PORT=3000 - DATABASE_URL=postgresql://postgres:postgres@imk-postgres:5432/postgres?schema=public + - FRONTEND_URL=https://www.placebo.mk env_file: - .env deploy: @@ -40,7 +46,7 @@ services: timeout: 10s retries: 3 start_period: 15s - restart: unless-stopped + restart: always postgres: container_name: imk-postgres image: postgres:14-alpine @@ -60,7 +66,7 @@ services: retries: 5 networks: - app_network - restart: unless-stopped + restart: always redis: container_name: imk-redis @@ -77,7 +83,7 @@ services: interval: 10s timeout: 5s retries: 3 - restart: unless-stopped + restart: always networks: app_network: diff --git a/frontend/src/components/adminPanel/AdminPanel.jsx b/frontend/src/components/adminPanel/AdminPanel.jsx index 20df75b..a90f68a 100644 --- a/frontend/src/components/adminPanel/AdminPanel.jsx +++ b/frontend/src/components/adminPanel/AdminPanel.jsx @@ -1,28 +1,41 @@ -import { useState, useEffect } from 'react'; -import { getAllUsers, getAllDocuments, getUserInfo, createUser, resetUserPassword } from '../../services/api'; -import DocumentUpload from '../documentUpload/DocumentUpload'; -import { useNavigate } from 'react-router-dom'; -import { FiUsers, FiFile, FiUpload, FiUserPlus, FiLoader, FiKey } from 'react-icons/fi'; +import { useState, useEffect } from "react"; +import { + getAllUsers, + getAllDocuments, + getUserInfo, + createUser, + resetUserPassword, +} from "../../services/api"; +import DocumentUpload from "../documentUpload/DocumentUpload"; +import { useNavigate } from "react-router-dom"; +import { + FiUsers, + FiFile, + FiUpload, + FiUserPlus, + FiLoader, + FiKey, +} from "react-icons/fi"; function AdminPanel() { const navigate = useNavigate(); - const [activeTab, setActiveTab] = useState('documents'); + const [activeTab, setActiveTab] = useState("documents"); const [users, setUsers] = useState([]); const [documents, setDocuments] = useState([]); const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); + const [error, setError] = useState(""); const [isAdmin, setIsAdmin] = useState(false); const [resetPasswordModal, setResetPasswordModal] = useState({ isOpen: false, userId: null, - userName: '', - newPassword: '', + userName: "", + newPassword: "", }); const [newUser, setNewUser] = useState({ - name: '', - email: '', - password: '', - isAdmin: false + name: "", + email: "", + password: "", + isAdmin: false, }); useEffect(() => { @@ -39,32 +52,32 @@ function AdminPanel() { try { const response = await getUserInfo(); if (!response?.data?.isAdmin) { - navigate('/'); + navigate("/"); } else { setIsAdmin(true); await fetchData(); } } catch (error) { - console.error('Admin check failed:', error); - navigate('/'); + // console.error("Admin check failed:", error); + navigate("/"); } }; const fetchData = async () => { setLoading(true); - setError(''); + setError(""); try { - if (activeTab === 'users') { + if (activeTab === "users") { const response = await getAllUsers(); setUsers(response.data); - } else if (activeTab === 'documents') { + } else if (activeTab === "documents") { const response = await getAllDocuments(); - console.log('Documents data:', response.data); + // console.log('Documents data:', response.data); setDocuments(response.data); } } catch (err) { - console.error('Fetch error:', err); - setError('Failed to fetch data. Please try again.'); + // console.error("Fetch error:", err); + setError("Failed to fetch data. Please try again."); } finally { setLoading(false); } @@ -75,41 +88,44 @@ function AdminPanel() { try { await createUser(newUser); setNewUser({ - name: '', - email: '', - password: '', - isAdmin: false + name: "", + email: "", + password: "", + isAdmin: false, }); fetchData(); } catch (err) { - setError('Failed to create user'); + setError("Failed to create user"); } }; const handleResetPassword = async (e) => { e.preventDefault(); try { - await resetUserPassword(resetPasswordModal.userId, resetPasswordModal.newPassword); + await resetUserPassword( + resetPasswordModal.userId, + resetPasswordModal.newPassword, + ); setResetPasswordModal({ isOpen: false, userId: null, - userName: '', - newPassword: '', + userName: "", + newPassword: "", }); // Show success message - setError('Password reset successful'); - setTimeout(() => setError(''), 3000); + setError("Password reset successful"); + setTimeout(() => setError(""), 3000); } catch (err) { - setError('Failed to reset password'); + setError("Failed to reset password"); } }; if (!isAdmin) return null; const tabs = [ - { id: 'documents', name: 'Documents', icon: FiFile }, - { id: 'users', name: 'Users', icon: FiUsers }, - { id: 'upload', name: 'Upload Document', icon: FiUpload } + { id: "documents", name: "Documents", icon: FiFile }, + { id: "users", name: "Users", icon: FiUsers }, + { id: "upload", name: "Upload Document", icon: FiUpload }, ]; if (loading) { @@ -127,7 +143,9 @@ function AdminPanel() {
-

Admin Dashboard

+

+ Admin Dashboard +

Manage users and documents

@@ -139,9 +157,11 @@ function AdminPanel() { onClick={() => setActiveTab(id)} className={` px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors - ${activeTab === id - ? 'bg-primary-600 text-white' - : 'text-neutral-400 hover:bg-primary-700/50 hover:text-white'} + ${ + activeTab === id + ? "bg-primary-600 text-white" + : "text-neutral-400 hover:bg-primary-700/50 hover:text-white" + } `} > @@ -167,11 +187,13 @@ function AdminPanel() { type="password" placeholder="New Password" value={resetPasswordModal.newPassword} - onChange={(e) => setResetPasswordModal({ - ...resetPasswordModal, - newPassword: e.target.value - })} - className="w-full bg-primary-700/30 border border-primary-600 rounded-lg px-4 py-2 + onChange={(e) => + setResetPasswordModal({ + ...resetPasswordModal, + newPassword: e.target.value, + }) + } + className="w-full bg-primary-700/30 border border-primary-600 rounded-lg px-4 py-2 text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500" required @@ -180,20 +202,22 @@ function AdminPanel() {