Compare commits
No commits in common. "7c02511dabfd9d93e65df438a5cda562ae2db70b" and "6553b1c880fb3a7efc44982d15fd026dc5a0cb7a" have entirely different histories.
7c02511dab
...
6553b1c880
34
.cursorrules
34
.cursorrules
@ -1,34 +0,0 @@
|
|||||||
You are a Senior full-stack Developer and an Expert in Nestjs, Prisma,ReactJS, JavaScript, TypeScript, HTML, CSS and modern UI/UX frameworks (e.g., TailwindCSS, Shadcn, Radix). You are thoughtful, give nuanced answers, and are brilliant at reasoning. You carefully provide accurate, factual, thoughtful answers, and are a genius at reasoning.
|
|
||||||
|
|
||||||
- Follow the user’s requirements carefully & to the letter.
|
|
||||||
- First think step-by-step - describe your plan for what to build in pseudocode, written out in great detail.
|
|
||||||
- Confirm, then write code!
|
|
||||||
- Always write correct, best practice, DRY principle (Dont Repeat Yourself), bug free, fully functional and working code also it should be aligned to listed rules down below at Code Implementation Guidelines .
|
|
||||||
- Focus on easy and readability code, over being performant.
|
|
||||||
- Fully implement all requested functionality.
|
|
||||||
- Leave NO todo’s, placeholders or missing pieces.
|
|
||||||
- Ensure code is complete! Verify thoroughly finalised.
|
|
||||||
- Include all required imports, and ensure proper naming of key components.
|
|
||||||
- Be concise Minimize any other prose.
|
|
||||||
- If you think there might not be a correct answer, you say so.
|
|
||||||
- If you do not know the answer, say so, instead of guessing.
|
|
||||||
|
|
||||||
### Coding Environment
|
|
||||||
The user asks questions about the following coding languages:
|
|
||||||
- ReactJS
|
|
||||||
- Nestjs
|
|
||||||
- Prisma
|
|
||||||
- JavaScript
|
|
||||||
- TypeScript
|
|
||||||
- TailwindCSS
|
|
||||||
- HTML
|
|
||||||
- CSS
|
|
||||||
|
|
||||||
### Code Implementation Guidelines
|
|
||||||
Follow these rules when you write code:
|
|
||||||
- Use early returns whenever possible to make the code more readable.
|
|
||||||
- Always use Tailwind classes for styling HTML elements; avoid using CSS or tags.
|
|
||||||
- Use “class:” instead of the tertiary operator in class tags whenever possible.
|
|
||||||
- Use descriptive variable and function/const names. Also, event functions should be named with a “handle” prefix, like “handleClick” for onClick and “handleKeyDown” for onKeyDown.
|
|
||||||
- Implement accessibility features on elements. For example, a tag should have a tabindex=“0”, aria-label, on:click, and on:keydown, and similar attributes.
|
|
||||||
- Use consts instead of functions, for example, “const toggle = () =>”. Also, define a type if possible.
|
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
application/cache
|
|
||||||
backend/node_modules
|
|
||||||
backend/dist
|
|
||||||
backend/test
|
|
||||||
frontend/node_modules
|
|
||||||
frontend/dist
|
|
||||||
frontend/.vite
|
|
||||||
node_modules
|
|
||||||
|
|
||||||
14
backend/.env
14
backend/.env
@ -1,14 +0,0 @@
|
|||||||
# 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: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
|
|
||||||
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
<p align="center">
|
|
||||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
|
||||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
|
||||||
|
|
||||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
|
||||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
|
||||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
|
||||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
|
||||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
|
||||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
|
||||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
|
||||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
|
||||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
|
||||||
</p>
|
|
||||||
<!--[](https://opencollective.com/nest#backer)
|
|
||||||
[](https://opencollective.com/nest#sponsor)-->
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running the app
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# development
|
|
||||||
$ npm run start
|
|
||||||
|
|
||||||
# watch mode
|
|
||||||
$ npm run start:dev
|
|
||||||
|
|
||||||
# production mode
|
|
||||||
$ npm run start:prod
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# unit tests
|
|
||||||
$ npm run test
|
|
||||||
|
|
||||||
# e2e tests
|
|
||||||
$ npm run test:e2e
|
|
||||||
|
|
||||||
# test coverage
|
|
||||||
$ npm run test:cov
|
|
||||||
```
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
|
||||||
|
|
||||||
## Stay in touch
|
|
||||||
|
|
||||||
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
|
||||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
|
||||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Nest is [MIT licensed](LICENSE).
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/nest-cli",
|
|
||||||
"collection": "@nestjs/schematics",
|
|
||||||
"sourceRoot": "src",
|
|
||||||
"compilerOptions": {
|
|
||||||
"deleteOutDir": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11352
backend/package-lock.json
generated
11352
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,94 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "imk-backend",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"description": "",
|
|
||||||
"author": "",
|
|
||||||
"private": true,
|
|
||||||
"license": "UNLICENSED",
|
|
||||||
"scripts": {
|
|
||||||
"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",
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@aws-sdk/client-s3": "^3.679.0",
|
|
||||||
"@aws-sdk/lib-storage": "^3.679.0",
|
|
||||||
"@aws-sdk/s3-request-presigner": "^3.679.0",
|
|
||||||
"@nestjs/common": "^10.0.0",
|
|
||||||
"@nestjs/config": "^3.3.0",
|
|
||||||
"@nestjs/core": "^10.0.0",
|
|
||||||
"@nestjs/jwt": "^10.2.0",
|
|
||||||
"@nestjs/mapped-types": "^2.0.5",
|
|
||||||
"@nestjs/passport": "^10.0.3",
|
|
||||||
"@nestjs/platform-express": "^10.4.6",
|
|
||||||
"@nestjs/typeorm": "^10.0.2",
|
|
||||||
"@prisma/client": "^5.12.1",
|
|
||||||
"@types/multer": "^1.4.12",
|
|
||||||
"bcrypt": "^5.1.1",
|
|
||||||
"class-transformer": "^0.5.1",
|
|
||||||
"class-validator": "^0.14.1",
|
|
||||||
"passport": "^0.7.0",
|
|
||||||
"passport-jwt": "^4.0.1",
|
|
||||||
"passport-local": "^1.0.0",
|
|
||||||
"pg": "^8.13.1",
|
|
||||||
"reflect-metadata": "^0.1.13",
|
|
||||||
"rxjs": "^7.8.1",
|
|
||||||
"typeorm": "^0.3.20"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@nestjs/cli": "^10.0.0",
|
|
||||||
"@nestjs/schematics": "^10.0.0",
|
|
||||||
"@nestjs/testing": "^10.0.0",
|
|
||||||
"@types/bcrypt": "^5.0.2",
|
|
||||||
"@types/express": "^4.17.17",
|
|
||||||
"@types/jest": "^29.5.2",
|
|
||||||
"@types/node": "^20.3.1",
|
|
||||||
"@types/passport-jwt": "^4.0.1",
|
|
||||||
"@types/passport-local": "^1.0.38",
|
|
||||||
"@types/supertest": "^2.0.12",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
||||||
"@typescript-eslint/parser": "^6.0.0",
|
|
||||||
"eslint": "^8.42.0",
|
|
||||||
"eslint-config-prettier": "^9.0.0",
|
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
|
||||||
"jest": "^29.5.0",
|
|
||||||
"prettier": "^3.0.0",
|
|
||||||
"prisma": "^5.12.1",
|
|
||||||
"source-map-support": "^0.5.21",
|
|
||||||
"supertest": "^6.3.3",
|
|
||||||
"ts-jest": "^29.1.0",
|
|
||||||
"ts-loader": "^9.4.3",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"tsconfig-paths": "^4.2.0",
|
|
||||||
"typescript": "^5.1.3"
|
|
||||||
},
|
|
||||||
"prisma": {
|
|
||||||
"seed": "ts-node prisma/seed.ts"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"moduleFileExtensions": [
|
|
||||||
"js",
|
|
||||||
"json",
|
|
||||||
"ts"
|
|
||||||
],
|
|
||||||
"rootDir": "src",
|
|
||||||
"testRegex": ".*\\.spec\\.ts$",
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
|
||||||
},
|
|
||||||
"collectCoverageFrom": [
|
|
||||||
"**/*.(t|j)s"
|
|
||||||
],
|
|
||||||
"coverageDirectory": "../coverage",
|
|
||||||
"testEnvironment": "node"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Document" (
|
|
||||||
"id" SERIAL NOT NULL,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
"content" TEXT,
|
|
||||||
"published" BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
"authorId" INTEGER NOT NULL,
|
|
||||||
"s3Key" TEXT NOT NULL,
|
|
||||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "Document_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "User" (
|
|
||||||
"id" SERIAL NOT NULL,
|
|
||||||
"email" TEXT NOT NULL,
|
|
||||||
"name" TEXT,
|
|
||||||
"password" TEXT NOT NULL,
|
|
||||||
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
|
|
||||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Notification" (
|
|
||||||
"id" SERIAL NOT NULL,
|
|
||||||
"message" TEXT NOT NULL,
|
|
||||||
"read" BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
"userId" INTEGER NOT NULL,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "_SharedDocuments" (
|
|
||||||
"A" INTEGER NOT NULL,
|
|
||||||
"B" INTEGER NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "_SharedDocuments_AB_unique" ON "_SharedDocuments"("A", "B");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "_SharedDocuments_B_index" ON "_SharedDocuments"("B");
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Document" ADD CONSTRAINT "Document_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "_SharedDocuments" ADD CONSTRAINT "_SharedDocuments_A_fkey" FOREIGN KEY ("A") REFERENCES "Document"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "_SharedDocuments" ADD CONSTRAINT "_SharedDocuments_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `authorId` on the `Document` table. All the data in the column will be lost.
|
|
||||||
- You are about to drop the column `published` on the `Document` table. All the data in the column will be lost.
|
|
||||||
- You are about to drop the `_SharedDocuments` table. If the table is not empty, all the data it contains will be lost.
|
|
||||||
- Added the required column `sharedWithId` to the `Document` table without a default value. This is not possible if the table is not empty.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- DropForeignKey
|
|
||||||
ALTER TABLE "Document" DROP CONSTRAINT "Document_authorId_fkey";
|
|
||||||
|
|
||||||
-- DropForeignKey
|
|
||||||
ALTER TABLE "_SharedDocuments" DROP CONSTRAINT "_SharedDocuments_A_fkey";
|
|
||||||
|
|
||||||
-- DropForeignKey
|
|
||||||
ALTER TABLE "_SharedDocuments" DROP CONSTRAINT "_SharedDocuments_B_fkey";
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Document" DROP COLUMN "authorId",
|
|
||||||
DROP COLUMN "published",
|
|
||||||
ADD COLUMN "sharedWithId" INTEGER NOT NULL;
|
|
||||||
|
|
||||||
-- DropTable
|
|
||||||
DROP TABLE "_SharedDocuments";
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Document" ADD CONSTRAINT "Document_sharedWithId_fkey" FOREIGN KEY ("sharedWithId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `content` on the `Document` table. All the data in the column will be lost.
|
|
||||||
- You are about to drop the column `sharedWithId` on the `Document` table. All the data in the column will be lost.
|
|
||||||
- Added the required column `uploadedById` to the `Document` table without a default value. This is not possible if the table is not empty.
|
|
||||||
- Added the required column `documentId` to the `Notification` table without a default value. This is not possible if the table is not empty.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- DropForeignKey
|
|
||||||
ALTER TABLE "Document" DROP CONSTRAINT "Document_sharedWithId_fkey";
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Document" DROP COLUMN "content",
|
|
||||||
DROP COLUMN "sharedWithId",
|
|
||||||
ADD COLUMN "uploadedById" INTEGER NOT NULL,
|
|
||||||
ALTER COLUMN "status" DROP DEFAULT;
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Notification" ADD COLUMN "documentId" INTEGER NOT NULL;
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "_SharedDocuments" (
|
|
||||||
"A" INTEGER NOT NULL,
|
|
||||||
"B" INTEGER NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "_SharedDocuments_AB_unique" ON "_SharedDocuments"("A", "B");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "_SharedDocuments_B_index" ON "_SharedDocuments"("B");
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Document" ADD CONSTRAINT "Document_uploadedById_fkey" FOREIGN KEY ("uploadedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "_SharedDocuments" ADD CONSTRAINT "_SharedDocuments_A_fkey" FOREIGN KEY ("A") REFERENCES "Document"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "_SharedDocuments" ADD CONSTRAINT "_SharedDocuments_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# Please do not edit this file manually
|
|
||||||
# It should be added in your version-control system (i.e. Git)
|
|
||||||
provider = "postgresql"
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
generator client {
|
|
||||||
provider = "prisma-client-js"
|
|
||||||
}
|
|
||||||
|
|
||||||
datasource db {
|
|
||||||
provider = "postgresql"
|
|
||||||
url = env("DATABASE_URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
model Document {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
title String
|
|
||||||
s3Key String
|
|
||||||
status String
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
uploadedById Int
|
|
||||||
uploadedBy User @relation("UploadedDocuments", fields: [uploadedById], references: [id])
|
|
||||||
Notification Notification[]
|
|
||||||
sharedWith User[] @relation("SharedDocuments")
|
|
||||||
}
|
|
||||||
|
|
||||||
model User {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
email String @unique
|
|
||||||
name String?
|
|
||||||
password String
|
|
||||||
isAdmin Boolean @default(false)
|
|
||||||
uploadedDocuments Document[] @relation("UploadedDocuments")
|
|
||||||
Notification Notification[]
|
|
||||||
sharedDocuments Document[] @relation("SharedDocuments")
|
|
||||||
}
|
|
||||||
|
|
||||||
model Notification {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
message String
|
|
||||||
read Boolean @default(false)
|
|
||||||
userId Int
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
documentId Int
|
|
||||||
document Document @relation(fields: [documentId], references: [id])
|
|
||||||
user User @relation(fields: [userId], references: [id])
|
|
||||||
}
|
|
||||||
@ -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,30 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import * as bcrypt from 'bcrypt';
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const hashedPassword = await bcrypt.hash('admin123', 10);
|
|
||||||
|
|
||||||
const admin = await prisma.user.upsert({
|
|
||||||
where: { email: 'admin@example.com' },
|
|
||||||
update: {},
|
|
||||||
create: {
|
|
||||||
email: 'admin@example.com',
|
|
||||||
name: 'Admin User',
|
|
||||||
password: hashedPassword,
|
|
||||||
isAdmin: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log({ admin });
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
})
|
|
||||||
.finally(async () => {
|
|
||||||
await prisma.$disconnect();
|
|
||||||
});
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AdminController } from './admin.controller';
|
|
||||||
|
|
||||||
describe('AdminController', () => {
|
|
||||||
let controller: AdminController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [AdminController],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<AdminController>(AdminController);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
|
|
||||||
import {
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Post,
|
|
||||||
Body,
|
|
||||||
Param,
|
|
||||||
Put,
|
|
||||||
UseInterceptors,
|
|
||||||
UploadedFile,
|
|
||||||
ParseIntPipe,
|
|
||||||
UseGuards,
|
|
||||||
BadRequestException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
|
||||||
import { AdminService } from './admin.service';
|
|
||||||
import { UpdateDocumentDto } from '../dto/update-document.dto';
|
|
||||||
import { AdminGuard } from '../auth/admin.guard';
|
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|
||||||
import { CreateUserDto } from '../dto/create-user.dto';
|
|
||||||
import { S3Service } from 'src/s3/s3.service';
|
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
|
||||||
|
|
||||||
@Controller('admin')
|
|
||||||
@UseGuards(JwtAuthGuard, AdminGuard)
|
|
||||||
export class AdminController {
|
|
||||||
constructor(
|
|
||||||
private readonly adminService: AdminService,
|
|
||||||
private readonly s3Service: S3Service,
|
|
||||||
private readonly prisma: PrismaService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Get('documents')
|
|
||||||
getAllDocuments() {
|
|
||||||
return this.adminService.getAllDocuments();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('documents/:id')
|
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
|
||||||
updateDocument(
|
|
||||||
@Param('id', ParseIntPipe) id: number,
|
|
||||||
@Body() updateDocumentDto: UpdateDocumentDto,
|
|
||||||
@UploadedFile() file?: Express.Multer.File,
|
|
||||||
) {
|
|
||||||
return this.adminService.updateDocument(id, updateDocumentDto, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('documents')
|
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
|
||||||
async uploadDocument(
|
|
||||||
@UploadedFile() file: Express.Multer.File,
|
|
||||||
@Body('title') title: string,
|
|
||||||
@Body('sharedWithId') sharedWithId: string, // Accept as string first
|
|
||||||
@Body('uploadedById') uploadedById: string // Accept as string first
|
|
||||||
) {
|
|
||||||
if (!sharedWithId || !uploadedById) {
|
|
||||||
throw new BadRequestException('sharedWithId and uploadedById are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the string values to numbers
|
|
||||||
const parsedSharedWithId = parseInt(sharedWithId, 10);
|
|
||||||
const parsedUploadedById = parseInt(uploadedById, 10);
|
|
||||||
|
|
||||||
// Validate that the parsing was successful
|
|
||||||
if (isNaN(parsedSharedWithId) || isNaN(parsedUploadedById)) {
|
|
||||||
throw new BadRequestException('sharedWithId and uploadedById must be valid numbers');
|
|
||||||
}
|
|
||||||
|
|
||||||
const document = await this.adminService.uploadDocument(
|
|
||||||
file,
|
|
||||||
title,
|
|
||||||
parsedSharedWithId,
|
|
||||||
parsedUploadedById
|
|
||||||
);
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('users')
|
|
||||||
getAllUsers() {
|
|
||||||
return this.adminService.getAllUsers();
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Post('test-document')
|
|
||||||
// async testDocumentCreation() {
|
|
||||||
// try {
|
|
||||||
// const document = await this.prisma.document.create({
|
|
||||||
// data: {
|
|
||||||
// title: 'Test Document',
|
|
||||||
// s3Key: 'test-key',
|
|
||||||
// status: 'completed',
|
|
||||||
// sharedWith: {
|
|
||||||
// connect: { id: 2 } // ID of 'pero' user
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// return document;
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error('Test document creation error:', error);
|
|
||||||
// throw error;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Post('users')
|
|
||||||
async createUser(@Body() createUserDto: CreateUserDto) {
|
|
||||||
return this.adminService.createUser(createUserDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('documents/:id/share')
|
|
||||||
async shareDocument(
|
|
||||||
@Param('id') id: string,
|
|
||||||
@Body() { userId }: { userId: number },
|
|
||||||
) {
|
|
||||||
return this.adminService.shareDocument(+id, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('documents/:id/status')
|
|
||||||
async updateDocumentStatus(
|
|
||||||
@Param('id') id: string,
|
|
||||||
@Body() { status }: { status: string },
|
|
||||||
) {
|
|
||||||
return this.adminService.updateDocumentStatus(+id, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// @Get('test-s3-connection')
|
|
||||||
// async testS3Connection() {
|
|
||||||
// const isConnected = await this.s3Service.testConnection();
|
|
||||||
// if (isConnected) {
|
|
||||||
// return { message: 'Successfully connected to S3' };
|
|
||||||
// } else {
|
|
||||||
// return { message: 'Failed to connect to S3' };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { AdminController } from './admin.controller';
|
|
||||||
import { AdminService } from './admin.service';
|
|
||||||
import { PrismaModule } from '../prisma/prisma.module';
|
|
||||||
import { S3Module } from '../s3/s3.module';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
controllers: [AdminController],
|
|
||||||
providers: [AdminService],
|
|
||||||
imports: [PrismaModule, S3Module],
|
|
||||||
})
|
|
||||||
export class AdminModule {}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AdminService } from './admin.service';
|
|
||||||
|
|
||||||
describe('AdminService', () => {
|
|
||||||
let service: AdminService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [AdminService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<AdminService>(AdminService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
|
||||||
import { S3Service } from '../s3/s3.service';
|
|
||||||
import { UpdateDocumentDto } from '../dto/update-document.dto';
|
|
||||||
import { CreateUserDto } from '../dto/create-user.dto';
|
|
||||||
import * as bcrypt from 'bcrypt';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AdminService {
|
|
||||||
constructor(
|
|
||||||
private readonly prisma: PrismaService,
|
|
||||||
private readonly s3Service: S3Service,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async getAllDocuments() {
|
|
||||||
return this.prisma.document.findMany({
|
|
||||||
include: {
|
|
||||||
uploadedBy: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sharedWith: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: 'desc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllUsers() {
|
|
||||||
return this.prisma.user.findMany();
|
|
||||||
}
|
|
||||||
|
|
||||||
async createUser(createUserDto: CreateUserDto) {
|
|
||||||
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
|
|
||||||
|
|
||||||
return this.prisma.user.create({
|
|
||||||
data: {
|
|
||||||
...createUserDto,
|
|
||||||
password: hashedPassword,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateDocument(
|
|
||||||
id: number,
|
|
||||||
updateDocumentDto: UpdateDocumentDto,
|
|
||||||
file?: Express.Multer.File,
|
|
||||||
) {
|
|
||||||
let s3Key = undefined;
|
|
||||||
if (file) {
|
|
||||||
s3Key = await this.s3Service.uploadFile(file, 'documents');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.prisma.document.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
...updateDocumentDto,
|
|
||||||
...(s3Key && { s3Key }),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async shareDocument(documentId: number, userId: number) {
|
|
||||||
return this.prisma.document.update({
|
|
||||||
where: { id: documentId },
|
|
||||||
data: {
|
|
||||||
sharedWith: {
|
|
||||||
connect: { id: userId },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateDocumentStatus(documentId: number, status: string) {
|
|
||||||
return this.prisma.document.update({
|
|
||||||
where: { id: documentId },
|
|
||||||
data: { status },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async uploadDocument(
|
|
||||||
file: Express.Multer.File,
|
|
||||||
title: string,
|
|
||||||
sharedWithId: number,
|
|
||||||
uploadedById: number
|
|
||||||
) {
|
|
||||||
const s3Key = await this.s3Service.uploadFile(file, 'documents');
|
|
||||||
|
|
||||||
return this.prisma.document.create({
|
|
||||||
data: {
|
|
||||||
title,
|
|
||||||
s3Key,
|
|
||||||
status: 'pending',
|
|
||||||
sharedWith: {
|
|
||||||
connect: { id: sharedWithId }
|
|
||||||
},
|
|
||||||
uploadedBy: {
|
|
||||||
connect: { id: uploadedById }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
uploadedBy: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sharedWith: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AppController } from './app.controller';
|
|
||||||
import { AppService } from './app.service';
|
|
||||||
|
|
||||||
describe('AppController', () => {
|
|
||||||
let appController: AppController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const app: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [AppController],
|
|
||||||
providers: [AppService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
appController = app.get<AppController>(AppController);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('root', () => {
|
|
||||||
it('should return "Hello World!"', () => {
|
|
||||||
expect(appController.getHello()).toBe('Hello World!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
|
||||||
import { AppService } from './app.service';
|
|
||||||
|
|
||||||
@Controller()
|
|
||||||
export class AppController {
|
|
||||||
constructor(private readonly appService: AppService) {}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
getHello(): string {
|
|
||||||
return this.appService.getHello();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { AppController } from './app.controller';
|
|
||||||
import { AppService } from './app.service';
|
|
||||||
import { AuthModule } from './auth/auth.module';
|
|
||||||
//import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { AdminModule } from './admin/admin.module';
|
|
||||||
import { ClientModule } from './client/client.module';
|
|
||||||
import { UploadService } from './upload/upload.service';
|
|
||||||
import { DocumentsService } from './documents/documents.service';
|
|
||||||
import { S3Service } from './s3/s3.service';
|
|
||||||
import { S3Module } from './s3/s3.module';
|
|
||||||
import { PrismaService } from './prisma/prisma.service';
|
|
||||||
import { PrismaModule } from './prisma/prisma.module';
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
|
||||||
import { AuthController } from './auth/auth.controller';
|
|
||||||
import { DocumentsController } from './documents/documents.controller';
|
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
// TypeOrmModule.forRoot({
|
|
||||||
// type: 'postgres',
|
|
||||||
// host: 'localhost',
|
|
||||||
// port: 5432,
|
|
||||||
// username: 'root',
|
|
||||||
// password: 'admin',
|
|
||||||
// database: 'imk',
|
|
||||||
// synchronize: true,
|
|
||||||
// }),
|
|
||||||
ConfigModule.forRoot({
|
|
||||||
isGlobal: true,
|
|
||||||
}),
|
|
||||||
JwtModule.register({
|
|
||||||
secret: process.env.JWT_SECRET,
|
|
||||||
signOptions: { expiresIn: '1h' },
|
|
||||||
}),
|
|
||||||
AuthModule,
|
|
||||||
AdminModule,
|
|
||||||
ClientModule,
|
|
||||||
S3Module,
|
|
||||||
PrismaModule,
|
|
||||||
],
|
|
||||||
controllers: [AppController, AuthController, DocumentsController],
|
|
||||||
providers: [
|
|
||||||
AppService,
|
|
||||||
UploadService,
|
|
||||||
DocumentsService,
|
|
||||||
S3Service,
|
|
||||||
PrismaService,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class AppModule {}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AppService {
|
|
||||||
getHello(): string {
|
|
||||||
return 'Hello World!';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import {
|
|
||||||
Injectable,
|
|
||||||
CanActivate,
|
|
||||||
ExecutionContext,
|
|
||||||
Logger,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AdminGuard implements CanActivate {
|
|
||||||
private readonly logger = new Logger(AdminGuard.name);
|
|
||||||
|
|
||||||
constructor(private prisma: PrismaService) {}
|
|
||||||
|
|
||||||
canActivate(
|
|
||||||
context: ExecutionContext,
|
|
||||||
): boolean | Promise<boolean> | Observable<boolean> {
|
|
||||||
const request = context.switchToHttp().getRequest();
|
|
||||||
return this.validateRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
async validateRequest(request: any): Promise<boolean> {
|
|
||||||
const user = await this.prisma.user.findUnique({
|
|
||||||
where: { id: request.user.userId },
|
|
||||||
});
|
|
||||||
return user?.isAdmin === true;
|
|
||||||
}
|
|
||||||
// async validateRequest(request: any): Promise<boolean> {
|
|
||||||
// this.logger.debug(`Validating request for user ID: ${request.user.userId}`);
|
|
||||||
// const user = await this.prisma.user.findUnique({
|
|
||||||
// where: { id: request.user.userId },
|
|
||||||
// });
|
|
||||||
// const isAdmin = user?.isAdmin === true;
|
|
||||||
// this.logger.debug(`User is admin: ${isAdmin}`);
|
|
||||||
// return isAdmin;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AuthController } from './auth.controller';
|
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
import { CreateUserDto } from '../dto/create-user.dto';
|
|
||||||
|
|
||||||
describe('AuthController', () => {
|
|
||||||
let controller: AuthController;
|
|
||||||
let authService: AuthService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [AuthController],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: AuthService,
|
|
||||||
useValue: {
|
|
||||||
createUser: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<AuthController>(AuthController);
|
|
||||||
authService = module.get<AuthService>(AuthService);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('register', () => {
|
|
||||||
it('should call authService.createUser with CreateUserDto', async () => {
|
|
||||||
const createUserDto: CreateUserDto = {
|
|
||||||
name: 'testuser',
|
|
||||||
password: 'password123',
|
|
||||||
email: 'test@example.com',
|
|
||||||
};
|
|
||||||
|
|
||||||
await controller.register(createUserDto);
|
|
||||||
|
|
||||||
expect(authService.createUser).toHaveBeenCalledWith(createUserDto);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import {
|
|
||||||
Controller,
|
|
||||||
Post,
|
|
||||||
Body,
|
|
||||||
UnauthorizedException,
|
|
||||||
UseGuards,
|
|
||||||
Get,
|
|
||||||
Request,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
import { LoginDto } from '../dto/login.dto';
|
|
||||||
import { CreateUserDto } from '../dto/create-user.dto';
|
|
||||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
|
||||||
import { AdminGuard } from './admin.guard';
|
|
||||||
//@UseGuards(JwtAuthGuard, AdminGuard)
|
|
||||||
@Controller('auth')
|
|
||||||
export class AuthController {
|
|
||||||
constructor(private authService: AuthService) {}
|
|
||||||
|
|
||||||
@Post('login')
|
|
||||||
async login(@Body() loginDto: LoginDto) {
|
|
||||||
const user = await this.authService.validateUser(
|
|
||||||
loginDto.username,
|
|
||||||
loginDto.password,
|
|
||||||
);
|
|
||||||
if (!user) {
|
|
||||||
throw new UnauthorizedException('Invalid credentials');
|
|
||||||
}
|
|
||||||
return this.authService.login(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('register')
|
|
||||||
async register(@Body() createUserDto: CreateUserDto) {
|
|
||||||
return this.authService.createUser(createUserDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
//@UseGuards(JwtAuthGuard)
|
|
||||||
@Post('create-admin')
|
|
||||||
async createAdmin(@Body() createUserDto: CreateUserDto) {
|
|
||||||
return this.authService.createUser(createUserDto, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
@Get('user-info')
|
|
||||||
async getUserInfo(@Request() req) {
|
|
||||||
return this.authService.getUserInfo(req.user.userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
import { LocalStrategy } from './local.strategy';
|
|
||||||
import { JwtStrategy } from './jwt.strategy';
|
|
||||||
//import { UsersModule } from '../users/users.module';
|
|
||||||
import { PassportModule } from '@nestjs/passport';
|
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
|
||||||
import { jwtConstants } from './constants';
|
|
||||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
PassportModule,
|
|
||||||
PrismaModule,
|
|
||||||
JwtModule.register({
|
|
||||||
secret: jwtConstants.secret,
|
|
||||||
signOptions: { expiresIn: '60m' },
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
providers: [AuthService, LocalStrategy, JwtStrategy],
|
|
||||||
exports: [AuthService],
|
|
||||||
})
|
|
||||||
export class AuthModule {}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
|
|
||||||
describe('AuthService', () => {
|
|
||||||
let service: AuthService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [AuthService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<AuthService>(AuthService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
import { Injectable, ConflictException } from '@nestjs/common';
|
|
||||||
import { JwtService } from '@nestjs/jwt';
|
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
|
||||||
import * as bcrypt from 'bcrypt';
|
|
||||||
import { CreateUserDto } from '../dto/create-user.dto';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AuthService {
|
|
||||||
constructor(
|
|
||||||
private prisma: PrismaService,
|
|
||||||
private jwtService: JwtService,
|
|
||||||
private configService: ConfigService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async validateUser(username: string, password: string): Promise<any> {
|
|
||||||
const user = await this.prisma.user.findUnique({
|
|
||||||
where: { email: username },
|
|
||||||
});
|
|
||||||
if (user && (await bcrypt.compare(password, user.password))) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { password, ...result } = user;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async login(user: any) {
|
|
||||||
// const payload = { username: user.email, sub: user.id };
|
|
||||||
// return {
|
|
||||||
// access_token: this.jwtService.sign(payload),
|
|
||||||
// };
|
|
||||||
const payload = { username: user.username, sub: user.id };
|
|
||||||
console.log(payload);
|
|
||||||
return {
|
|
||||||
access_token: this.jwtService.sign(payload, {
|
|
||||||
secret: this.configService.get<string>('JWT_SECRET'),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async createUser(
|
|
||||||
createUserDto: CreateUserDto,
|
|
||||||
isAdmin: boolean = false,
|
|
||||||
): Promise<any> {
|
|
||||||
const existingUser = await this.prisma.user.findUnique({
|
|
||||||
where: { email: createUserDto.email },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingUser) {
|
|
||||||
throw new ConflictException('Email already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
|
|
||||||
|
|
||||||
const newUser = await this.prisma.user.create({
|
|
||||||
data: {
|
|
||||||
email: createUserDto.email,
|
|
||||||
password: hashedPassword,
|
|
||||||
name: createUserDto.name,
|
|
||||||
isAdmin: isAdmin,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { password, ...result } = newUser;
|
|
||||||
console.log(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// async getUserInfo(userId: number) {
|
|
||||||
// return this.prisma.user.findUnique({
|
|
||||||
// where: { id: userId },
|
|
||||||
// select: {
|
|
||||||
// id: true,
|
|
||||||
// name: true,
|
|
||||||
// email: true,
|
|
||||||
// isAdmin: true,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
async getUserInfo(userId: number) {
|
|
||||||
if (!userId) {
|
|
||||||
throw new Error('User ID is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.prisma.user.findUnique({
|
|
||||||
where: {
|
|
||||||
id: userId, // Make sure userId is properly passed and converted to number if needed
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
isAdmin: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export const jwtConstants = {
|
|
||||||
secret: '1234',
|
|
||||||
};
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
|
||||||
constructor(configService: ConfigService) {
|
|
||||||
super({
|
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
||||||
ignoreExpiration: false,
|
|
||||||
secretOrKey: configService.get('JWT_SECRET'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate(payload: any) {
|
|
||||||
return { userId: payload.sub, username: payload.username };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import { Strategy } from 'passport-local';
|
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
|
||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
|
||||||
constructor(private authService: AuthService) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate(username: string, password: string): Promise<any> {
|
|
||||||
const user = await this.authService.validateUser(username, password);
|
|
||||||
if (!user) {
|
|
||||||
throw new UnauthorizedException();
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { ClientController } from './client.controller';
|
|
||||||
|
|
||||||
describe('ClientController', () => {
|
|
||||||
let controller: ClientController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [ClientController],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<ClientController>(ClientController);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import { Controller, Get, UseGuards } from '@nestjs/common';
|
|
||||||
import { ClientService } from './client.service';
|
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|
||||||
import { User } from '../decorators/user.decorator';
|
|
||||||
|
|
||||||
@Controller('client')
|
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
export class ClientController {
|
|
||||||
constructor(private readonly clientService: ClientService) {}
|
|
||||||
|
|
||||||
@Get('documents')
|
|
||||||
async getClientDocuments(@User() user) {
|
|
||||||
return this.clientService.getDocuments(user.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { ClientService } from './client.service';
|
|
||||||
import { ClientController } from './client.controller';
|
|
||||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
providers: [ClientService],
|
|
||||||
controllers: [ClientController],
|
|
||||||
imports: [PrismaModule],
|
|
||||||
})
|
|
||||||
export class ClientModule {}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { ClientService } from './client.service';
|
|
||||||
|
|
||||||
describe('ClientService', () => {
|
|
||||||
let service: ClientService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [ClientService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<ClientService>(ClientService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ClientService {
|
|
||||||
constructor(private prisma: PrismaService) {}
|
|
||||||
|
|
||||||
// async getClientDocuments(userId: string) {
|
|
||||||
// return this.prisma.document.findMany({
|
|
||||||
// where: {
|
|
||||||
// authorId: Number(userId),
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
async getDocuments(userId: string) {
|
|
||||||
return this.prisma.document.findMany({
|
|
||||||
where: {
|
|
||||||
sharedWith: {
|
|
||||||
some: {
|
|
||||||
id: Number(userId),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
sharedWith: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
|
||||||
|
|
||||||
export const User = createParamDecorator(
|
|
||||||
(data: unknown, ctx: ExecutionContext) => {
|
|
||||||
const request = ctx.switchToHttp().getRequest();
|
|
||||||
return request.user;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { DocumentsController } from './documents.controller';
|
|
||||||
|
|
||||||
describe('DocumentsController', () => {
|
|
||||||
let controller: DocumentsController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [DocumentsController],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<DocumentsController>(DocumentsController);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
import { Controller, Get, Param, Req, Res, UseGuards, Logger, Request } from '@nestjs/common';
|
|
||||||
import { Response } from 'express';
|
|
||||||
import { DocumentsService } from './documents.service';
|
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|
||||||
import { S3Service } from '../s3/s3.service';
|
|
||||||
|
|
||||||
interface S3File {
|
|
||||||
buffer: Buffer;
|
|
||||||
contentType: string;
|
|
||||||
contentLength: number;
|
|
||||||
fileName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Controller('documents')
|
|
||||||
export class DocumentsController {
|
|
||||||
private readonly logger = new Logger(DocumentsController.name);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly documentsService: DocumentsService,
|
|
||||||
private readonly s3Service: S3Service
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Get('shared/:userId')
|
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
async getSharedDocuments(@Param('userId') userId: string) {
|
|
||||||
return this.documentsService.getClientDocuments(Number(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('download/:key')
|
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
async downloadDocument(
|
|
||||||
@Param('key') key: string,
|
|
||||||
@Request() req,
|
|
||||||
@Res() res: Response
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
this.logger.debug(`Download request for key: ${key}`);
|
|
||||||
|
|
||||||
const decodedKey = decodeURIComponent(key);
|
|
||||||
this.logger.debug(`Decoded key: ${decodedKey}`);
|
|
||||||
|
|
||||||
// Get document from database first to verify access
|
|
||||||
const document = await this.documentsService.findDocumentByS3Key(decodedKey);
|
|
||||||
|
|
||||||
if (!document) {
|
|
||||||
return res.status(404).json({ message: 'Document not found' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify user has access to this document
|
|
||||||
const hasAccess = await this.documentsService.verifyDocumentAccess(
|
|
||||||
document.id,
|
|
||||||
req.user.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasAccess) {
|
|
||||||
return res.status(403).json({ message: 'Access denied' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the file from S3
|
|
||||||
const file = await this.s3Service.getFile(decodedKey);
|
|
||||||
|
|
||||||
if (!file || !file.buffer) {
|
|
||||||
return res.status(404).json({ message: 'File not found in storage' });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.set({
|
|
||||||
'Content-Type': file.contentType || 'application/octet-stream',
|
|
||||||
'Content-Length': file.contentLength,
|
|
||||||
'Content-Disposition': `attachment; filename="${encodeURIComponent(file.fileName)}"`,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.send(file.buffer);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error('Download error:', error);
|
|
||||||
return res.status(500).json({
|
|
||||||
message: 'Failed to download file',
|
|
||||||
error: error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { DocumentsService } from './documents.service';
|
|
||||||
|
|
||||||
describe('DocumentsService', () => {
|
|
||||||
let service: DocumentsService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [DocumentsService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<DocumentsService>(DocumentsService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
import { Injectable, Logger, NotFoundException, ForbiddenException } from '@nestjs/common';
|
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
|
||||||
import { S3Service } from '../s3/s3.service';
|
|
||||||
import { Document, User, Prisma } from '@prisma/client';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DocumentsService {
|
|
||||||
private readonly logger = new Logger(DocumentsService.name);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly prisma: PrismaService,
|
|
||||||
private readonly s3Service: S3Service
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async findDocumentByS3Key(s3Key: string) {
|
|
||||||
return this.prisma.document.findFirst({
|
|
||||||
where: { s3Key },
|
|
||||||
include: {
|
|
||||||
uploadedBy: true,
|
|
||||||
sharedWith: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async verifyDocumentAccess(documentId: number, userId: number): Promise<boolean> {
|
|
||||||
const document = await this.prisma.document.findUnique({
|
|
||||||
where: { id: documentId },
|
|
||||||
include: {
|
|
||||||
uploadedBy: true,
|
|
||||||
sharedWith: {
|
|
||||||
where: {
|
|
||||||
id: userId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!document) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// User has access if they uploaded the document or it's shared with them
|
|
||||||
return document.uploadedBy.id === userId || document.sharedWith.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getClientDocuments(clientId: number) {
|
|
||||||
return this.prisma.document.findMany({
|
|
||||||
where: {
|
|
||||||
OR: [
|
|
||||||
{ uploadedById: clientId },
|
|
||||||
{
|
|
||||||
sharedWith: {
|
|
||||||
some: {
|
|
||||||
id: clientId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
uploadedBy: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sharedWith: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: 'desc'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async uploadDocument(
|
|
||||||
file: Express.Multer.File,
|
|
||||||
title: string,
|
|
||||||
sharedWithId: number,
|
|
||||||
uploadedById: number
|
|
||||||
) {
|
|
||||||
const s3Key = await this.s3Service.uploadFile(file, 'documents');
|
|
||||||
|
|
||||||
return this.prisma.document.create({
|
|
||||||
data: {
|
|
||||||
title,
|
|
||||||
s3Key,
|
|
||||||
status: 'pending',
|
|
||||||
uploadedBy: {
|
|
||||||
connect: { id: uploadedById }
|
|
||||||
},
|
|
||||||
sharedWith: {
|
|
||||||
connect: { id: sharedWithId }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
uploadedBy: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sharedWith: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { IsString, IsNotEmpty } from 'class-validator';
|
|
||||||
|
|
||||||
export class LoginDto {
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { IsString, IsDate, IsUrl } from 'class-validator';
|
|
||||||
|
|
||||||
export class ClientDocumentDto {
|
|
||||||
@IsString()
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
content: string;
|
|
||||||
|
|
||||||
@IsDate()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@IsUrl()
|
|
||||||
fileUrl: string;
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { IsString, IsOptional, IsEmail } from 'class-validator';
|
|
||||||
|
|
||||||
export class CreateDocumentDto {
|
|
||||||
@IsString()
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
content?: string;
|
|
||||||
|
|
||||||
@IsEmail()
|
|
||||||
@IsOptional()
|
|
||||||
clientEmail?: string;
|
|
||||||
|
|
||||||
file: Express.Multer.File;
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { IsString, IsEmail, MinLength, IsBoolean } from 'class-validator';
|
|
||||||
|
|
||||||
export class CreateUserDto {
|
|
||||||
@IsString()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsEmail()
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@MinLength(6)
|
|
||||||
password: string;
|
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
isAdmin: boolean = false;
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { IsString, IsNotEmpty } from 'class-validator';
|
|
||||||
|
|
||||||
export class LoginDto {
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { IsString, IsNotEmpty, IsEmail, MinLength } from 'class-validator';
|
|
||||||
|
|
||||||
export class RegisterDto {
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsEmail()
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@MinLength(6)
|
|
||||||
password: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
role: 'admin' | 'client';
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
import { PartialType } from '@nestjs/mapped-types';
|
|
||||||
import { CreateDocumentDto } from './create-document.dto';
|
|
||||||
|
|
||||||
export class UpdateDocumentDto extends PartialType(CreateDocumentDto) {}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export interface FileResponse {
|
|
||||||
buffer: Buffer;
|
|
||||||
contentType: string;
|
|
||||||
contentLength: number;
|
|
||||||
fileName: string;
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export interface S3File {
|
|
||||||
buffer: Buffer;
|
|
||||||
contentType: string;
|
|
||||||
contentLength: number;
|
|
||||||
fileName: string;
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
|
||||||
import { AppModule } from './app.module';
|
|
||||||
|
|
||||||
async function bootstrap() {
|
|
||||||
const app = await NestFactory.create(AppModule);
|
|
||||||
app.enableCors();
|
|
||||||
await app.listen(3000);
|
|
||||||
}
|
|
||||||
bootstrap();
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { PrismaService } from './prisma.service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
providers: [PrismaService],
|
|
||||||
exports: [PrismaService],
|
|
||||||
})
|
|
||||||
export class PrismaModule {}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { PrismaService } from './prisma.service';
|
|
||||||
|
|
||||||
describe('PrismaService', () => {
|
|
||||||
let service: PrismaService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [PrismaService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<PrismaService>(PrismaService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PrismaService
|
|
||||||
extends PrismaClient
|
|
||||||
implements OnModuleInit, OnModuleDestroy
|
|
||||||
{
|
|
||||||
async onModuleInit() {
|
|
||||||
await this.$connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
async onModuleDestroy() {
|
|
||||||
await this.$disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
|
||||||
import { S3Service } from './s3.service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [ConfigModule],
|
|
||||||
providers: [S3Service],
|
|
||||||
exports: [S3Service],
|
|
||||||
})
|
|
||||||
export class S3Module {}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { S3Service } from './s3.service';
|
|
||||||
|
|
||||||
describe('S3Service', () => {
|
|
||||||
let service: S3Service;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [S3Service],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<S3Service>(S3Service);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
import { Injectable, Logger, InternalServerErrorException } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
||||||
import { S3File } from '../interfaces/s3-file.interface';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class S3Service {
|
|
||||||
private readonly s3Client: S3Client;
|
|
||||||
private readonly logger = new Logger(S3Service.name);
|
|
||||||
|
|
||||||
constructor(private readonly configService: ConfigService) {
|
|
||||||
this.s3Client = new S3Client({
|
|
||||||
region: this.configService.get<string>('AWS_REGION'),
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: this.configService.get<string>('AWS_ACCESS_KEY_ID'),
|
|
||||||
secretAccessKey: this.configService.get<string>('AWS_SECRET_ACCESS_KEY'),
|
|
||||||
},
|
|
||||||
endpoint: this.configService.get<string>('AWS_ENDPOINT_URL'),
|
|
||||||
forcePathStyle: true, // Required for Contabo Object Storage
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async uploadFile(file: Express.Multer.File, folder: string): Promise<string> {
|
|
||||||
try {
|
|
||||||
const key = `${folder}/${Date.now()}-${file.originalname}`;
|
|
||||||
|
|
||||||
const command = new PutObjectCommand({
|
|
||||||
Bucket: this.configService.get<string>('AWS_S3_BUCKET_NAME'),
|
|
||||||
Key: key,
|
|
||||||
Body: file.buffer,
|
|
||||||
ContentType: file.mimetype,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.s3Client.send(command);
|
|
||||||
this.logger.debug(`File uploaded successfully: ${key}`);
|
|
||||||
|
|
||||||
return key;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`Error uploading file to S3: ${error.message}`);
|
|
||||||
throw new InternalServerErrorException('Failed to upload file to storage');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFile(key: string): Promise<S3File> {
|
|
||||||
try {
|
|
||||||
const command = new GetObjectCommand({
|
|
||||||
Bucket: this.configService.get<string>('AWS_S3_BUCKET_NAME'),
|
|
||||||
Key: key,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await this.s3Client.send(command);
|
|
||||||
|
|
||||||
const chunks = [];
|
|
||||||
for await (const chunk of response.Body as any) {
|
|
||||||
chunks.push(chunk);
|
|
||||||
}
|
|
||||||
const buffer = Buffer.concat(chunks);
|
|
||||||
|
|
||||||
return {
|
|
||||||
buffer,
|
|
||||||
contentType: response.ContentType || 'application/octet-stream',
|
|
||||||
contentLength: response.ContentLength || buffer.length,
|
|
||||||
fileName: key.split('/').pop() || 'download',
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`Error getting file from S3: ${error.message}`);
|
|
||||||
throw new InternalServerErrorException('Failed to retrieve file from storage');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { UploadService } from './upload.service';
|
|
||||||
|
|
||||||
describe('UploadService', () => {
|
|
||||||
let service: UploadService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [UploadService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<UploadService>(UploadService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { S3Client, S3ClientConfig } from '@aws-sdk/client-s3';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { Upload } from '@aws-sdk/lib-storage';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class UploadService {
|
|
||||||
private s3Client: S3Client;
|
|
||||||
|
|
||||||
constructor(private configService: ConfigService) {
|
|
||||||
this.s3Client = new S3Client({
|
|
||||||
region: this.configService.get('AWS_REGION'),
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID'),
|
|
||||||
secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY'),
|
|
||||||
},
|
|
||||||
endpoint: this.configService.get('S3_ENDPOINT'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async uploadFile(file: Express.Multer.File, key: string): Promise<string> {
|
|
||||||
const upload = new Upload({
|
|
||||||
client: this.s3Client,
|
|
||||||
params: {
|
|
||||||
Bucket: this.configService.get('S3_BUCKET'),
|
|
||||||
Key: key,
|
|
||||||
Body: file.buffer,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const result = await upload.done();
|
|
||||||
return result.Location;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"declaration": true,
|
|
||||||
"removeComments": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"target": "ES2021",
|
|
||||||
"sourceMap": true,
|
|
||||||
"outDir": "./dist",
|
|
||||||
"baseUrl": "./",
|
|
||||||
"incremental": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strictNullChecks": false,
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"strictBindCallApply": false,
|
|
||||||
"forceConsistentCasingInFileNames": false,
|
|
||||||
"noFallthroughCasesInSwitch": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,77 +0,0 @@
|
|||||||
{
|
|
||||||
"hash": "6aa7385c",
|
|
||||||
"browserHash": "a3876199",
|
|
||||||
"optimized": {
|
|
||||||
"react": {
|
|
||||||
"src": "../../node_modules/react/index.js",
|
|
||||||
"file": "react.js",
|
|
||||||
"fileHash": "eec1dd81",
|
|
||||||
"needsInterop": true
|
|
||||||
},
|
|
||||||
"react-dom/client": {
|
|
||||||
"src": "../../node_modules/react-dom/client.js",
|
|
||||||
"file": "react-dom_client.js",
|
|
||||||
"fileHash": "be85cf8b",
|
|
||||||
"needsInterop": true
|
|
||||||
},
|
|
||||||
"react-router-dom": {
|
|
||||||
"src": "../../node_modules/react-router-dom/dist/index.js",
|
|
||||||
"file": "react-router-dom.js",
|
|
||||||
"fileHash": "dc3bbb9d",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@headlessui/react": {
|
|
||||||
"src": "../../node_modules/@headlessui/react/dist/headlessui.esm.js",
|
|
||||||
"file": "@headlessui_react.js",
|
|
||||||
"fileHash": "a4ed943c",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@heroicons/react/24/outline": {
|
|
||||||
"src": "../../node_modules/@heroicons/react/24/outline/esm/index.js",
|
|
||||||
"file": "@heroicons_react_24_outline.js",
|
|
||||||
"fileHash": "08a02318",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@heroicons/react/20/solid": {
|
|
||||||
"src": "../../node_modules/@heroicons/react/20/solid/esm/index.js",
|
|
||||||
"file": "@heroicons_react_20_solid.js",
|
|
||||||
"fileHash": "a26ad170",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"framer-motion": {
|
|
||||||
"src": "../../node_modules/framer-motion/dist/es/index.mjs",
|
|
||||||
"file": "framer-motion.js",
|
|
||||||
"fileHash": "d4deaa03",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"react-icons/fi": {
|
|
||||||
"src": "../../node_modules/react-icons/fi/index.mjs",
|
|
||||||
"file": "react-icons_fi.js",
|
|
||||||
"fileHash": "8894f9b1",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"date-fns": {
|
|
||||||
"src": "../../node_modules/date-fns/index.js",
|
|
||||||
"file": "date-fns.js",
|
|
||||||
"fileHash": "51d96688",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"axios": {
|
|
||||||
"src": "../../node_modules/axios/index.js",
|
|
||||||
"file": "axios.js",
|
|
||||||
"fileHash": "38b66fb3",
|
|
||||||
"needsInterop": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chunks": {
|
|
||||||
"chunk-K2BGRD5Z": {
|
|
||||||
"file": "chunk-K2BGRD5Z.js"
|
|
||||||
},
|
|
||||||
"chunk-G4O6EYSD": {
|
|
||||||
"file": "chunk-G4O6EYSD.js"
|
|
||||||
},
|
|
||||||
"chunk-ZC22LKFR": {
|
|
||||||
"file": "chunk-ZC22LKFR.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,42 +0,0 @@
|
|||||||
var __create = Object.create;
|
|
||||||
var __defProp = Object.defineProperty;
|
|
||||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
||||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
||||||
var __getProtoOf = Object.getPrototypeOf;
|
|
||||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
||||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
||||||
var __commonJS = (cb, mod) => function __require() {
|
|
||||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
||||||
};
|
|
||||||
var __export = (target, all) => {
|
|
||||||
for (var name in all)
|
|
||||||
__defProp(target, name, { get: all[name], enumerable: true });
|
|
||||||
};
|
|
||||||
var __copyProps = (to, from, except, desc) => {
|
|
||||||
if (from && typeof from === "object" || typeof from === "function") {
|
|
||||||
for (let key of __getOwnPropNames(from))
|
|
||||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
||||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
||||||
}
|
|
||||||
return to;
|
|
||||||
};
|
|
||||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
||||||
// If the importer is in node compatibility mode or this is not an ESM
|
|
||||||
// file that has been converted to a CommonJS file using a Babel-
|
|
||||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
||||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
||||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
||||||
mod
|
|
||||||
));
|
|
||||||
var __publicField = (obj, key, value) => {
|
|
||||||
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
__commonJS,
|
|
||||||
__export,
|
|
||||||
__toESM,
|
|
||||||
__publicField
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=chunk-ZC22LKFR.js.map
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
39
frontend/.vite/deps/react-dom_client.js
vendored
39
frontend/.vite/deps/react-dom_client.js
vendored
@ -1,39 +0,0 @@
|
|||||||
import {
|
|
||||||
require_react_dom
|
|
||||||
} from "./chunk-K2BGRD5Z.js";
|
|
||||||
import "./chunk-G4O6EYSD.js";
|
|
||||||
import {
|
|
||||||
__commonJS
|
|
||||||
} from "./chunk-ZC22LKFR.js";
|
|
||||||
|
|
||||||
// node_modules/react-dom/client.js
|
|
||||||
var require_client = __commonJS({
|
|
||||||
"node_modules/react-dom/client.js"(exports) {
|
|
||||||
var m = require_react_dom();
|
|
||||||
if (false) {
|
|
||||||
exports.createRoot = m.createRoot;
|
|
||||||
exports.hydrateRoot = m.hydrateRoot;
|
|
||||||
} else {
|
|
||||||
i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
||||||
exports.createRoot = function(c, o) {
|
|
||||||
i.usingClientEntryPoint = true;
|
|
||||||
try {
|
|
||||||
return m.createRoot(c, o);
|
|
||||||
} finally {
|
|
||||||
i.usingClientEntryPoint = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
exports.hydrateRoot = function(c, h, o) {
|
|
||||||
i.usingClientEntryPoint = true;
|
|
||||||
try {
|
|
||||||
return m.hydrateRoot(c, h, o);
|
|
||||||
} finally {
|
|
||||||
i.usingClientEntryPoint = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
export default require_client();
|
|
||||||
//# sourceMappingURL=react-dom_client.js.map
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": ["../../node_modules/react-dom/client.js"],
|
|
||||||
"sourcesContent": ["'use strict';\n\nvar m = require('react-dom');\nif (process.env.NODE_ENV === 'production') {\n exports.createRoot = m.createRoot;\n exports.hydrateRoot = m.hydrateRoot;\n} else {\n var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n exports.createRoot = function(c, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.createRoot(c, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n exports.hydrateRoot = function(c, h, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.hydrateRoot(c, h, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n}\n"],
|
|
||||||
"mappings": ";;;;;;;;;AAAA;AAAA;AAEA,QAAI,IAAI;AACR,QAAI,OAAuC;AACzC,cAAQ,aAAa,EAAE;AACvB,cAAQ,cAAc,EAAE;AAAA,IAC1B,OAAO;AACD,UAAI,EAAE;AACV,cAAQ,aAAa,SAAS,GAAG,GAAG;AAClC,UAAE,wBAAwB;AAC1B,YAAI;AACF,iBAAO,EAAE,WAAW,GAAG,CAAC;AAAA,QAC1B,UAAE;AACA,YAAE,wBAAwB;AAAA,QAC5B;AAAA,MACF;AACA,cAAQ,cAAc,SAAS,GAAG,GAAG,GAAG;AACtC,UAAE,wBAAwB;AAC1B,YAAI;AACF,iBAAO,EAAE,YAAY,GAAG,GAAG,CAAC;AAAA,QAC9B,UAAE;AACA,YAAE,wBAAwB;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAjBM;AAAA;AAAA;",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
1306
frontend/.vite/deps/react-icons_fi.js
vendored
1306
frontend/.vite/deps/react-icons_fi.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
4947
frontend/.vite/deps/react-router-dom.js
vendored
4947
frontend/.vite/deps/react-router-dom.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
frontend/.vite/deps/react.js
vendored
6
frontend/.vite/deps/react.js
vendored
@ -1,6 +0,0 @@
|
|||||||
import {
|
|
||||||
require_react
|
|
||||||
} from "./chunk-G4O6EYSD.js";
|
|
||||||
import "./chunk-ZC22LKFR.js";
|
|
||||||
export default require_react();
|
|
||||||
//# sourceMappingURL=react.js.map
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# React + Vite
|
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
||||||
# imk
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<!-- <link rel="icon" type="image/svg+xml" href="/logo.svg" /> -->
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>IMK</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
4536
frontend/package-lock.json
generated
4536
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "imk",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@headlessui/react": "^1.7.19",
|
|
||||||
"@heroicons/react": "^2.2.0",
|
|
||||||
"axios": "^1.7.7",
|
|
||||||
"date-fns": "^4.1.0",
|
|
||||||
"framer-motion": "^11.18.2",
|
|
||||||
"jwt-decode": "^4.0.0",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-icons": "^5.5.0",
|
|
||||||
"react-router-dom": "^6.15.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/react": "^18.2.15",
|
|
||||||
"@types/react-dom": "^18.2.7",
|
|
||||||
"@vitejs/plugin-react": "^4.0.3",
|
|
||||||
"autoprefixer": "^10.4.20",
|
|
||||||
"eslint": "^8.45.0",
|
|
||||||
"eslint-plugin-react": "^7.32.2",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"eslint-plugin-react-refresh": "^0.4.3",
|
|
||||||
"postcss": "^8.4.47",
|
|
||||||
"tailwindcss": "^3.4.14",
|
|
||||||
"vite": "^4.4.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 100 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 124 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user