prototype working state
This commit is contained in:
1 parent
fdae53f82f
commit
eada8b3eb1
86 files changed
+22240
No files matched your search
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<p align="center">
|
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" 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://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" alt="Donate us"/></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" alt="Follow us on Twitter"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||
|
||||
## Project setup
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
## Compile and run the project
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ npm run start
|
||||
|
||||
# watch mode
|
||||
$ npm run start:dev
|
||||
|
||||
# production mode
|
||||
$ npm run start:prod
|
||||
```
|
||||
|
||||
## Run tests
|
||||
|
||||
```bash
|
||||
# unit tests
|
||||
$ npm run test
|
||||
|
||||
# e2e tests
|
||||
$ npm run test:e2e
|
||||
|
||||
# test coverage
|
||||
$ npm run test:cov
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
||||
|
||||
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
||||
|
||||
```bash
|
||||
$ npm install -g mau
|
||||
$ mau deploy
|
||||
```
|
||||
|
||||
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
||||
|
||||
## Resources
|
||||
|
||||
Check out a few resources that may come in handy when working with NestJS:
|
||||
|
||||
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
|
||||
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
|
||||
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
|
||||
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
|
||||
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
|
||||
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
|
||||
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
|
||||
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
|
||||
|
||||
## 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://twitter.com/kammysliwiec)
|
||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||
|
||||
## License
|
||||
|
||||
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
||||
@@ -0,0 +1,35 @@
|
||||
// @ts-check
|
||||
import eslint from '@eslint/js';
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['eslint.config.mjs'],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
ecmaVersion: 5,
|
||||
sourceType: 'module',
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn'
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
> backend@0.0.1 start:dev
|
||||
> nest start --watch
|
||||
|
||||
[2J[3J[H[[90m11:59:27 PM[0m] Starting compilation in watch mode...
|
||||
|
||||
[[90m11:59:29 PM[0m] Found 0 errors. Watching for file changes.
|
||||
|
||||
[32m[Nest] 14734 - [39m04/07/2025, 11:59:29 PM [32m LOG[39m [38;5;3m[NestFactory] [39m[32mStarting Nest application...[39m
|
||||
[32m[Nest] 14734 - [39m04/07/2025, 11:59:29 PM [32m LOG[39m [38;5;3m[InstanceLoader] [39m[32mPrismaModule dependencies initialized[39m[38;5;3m +10ms[39m
|
||||
[32m[Nest] 14734 - [39m04/07/2025, 11:59:29 PM [32m LOG[39m [38;5;3m[InstanceLoader] [39m[32mAppModule dependencies initialized[39m[38;5;3m +1ms[39m
|
||||
[32m[Nest] 14734 - [39m04/07/2025, 11:59:29 PM [32m LOG[39m [38;5;3m[InstanceLoader] [39m[32mArticlesModule dependencies initialized[39m[38;5;3m +0ms[39m
|
||||
[32m[Nest] 14734 - [39m04/07/2025, 11:59:29 PM [32m LOG[39m [38;5;3m[RoutesResolver] [39m[32mAppController {/}:[39m[38;5;3m +3ms[39m
|
||||
[32m[Nest] 14734 - [39m04/07/2025, 11:59:29 PM [32m LOG[39m [38;5;3m[RouterExplorer] [39m[32mMapped {/, GET} route[39m[38;5;3m +3ms[39m
|
||||
[32m[Nest] 14734 - [39m04/07/2025, 11:59:29 PM [32m LOG[39m [38;5;3m[RoutesResolver] [39m[32mArticlesController {/articles}:[39m[38;5;3m +0ms[39m
|
||||
[32m[Nest] 14734 - [39m04/07/2025, 11:59:29 PM [32m LOG[39m [38;5;3m[RouterExplorer] [39m[32mMapped {/articles, GET} route[39m[38;5;3m +1ms[39m
|
||||
[32m[Nest] 14734 - [39m04/07/2025, 11:59:29 PM [32m LOG[39m [38;5;3m[NestApplication] [39m[32mNest application successfully started[39m[38;5;3m +64ms[39m
|
||||
[2J[3J[H[[90m12:17:20 AM[0m] File change detected. Starting incremental compilation...
|
||||
|
||||
[[90m12:17:20 AM[0m] Found 0 errors. Watching for file changes.
|
||||
|
||||
[32m[Nest] 25027 - [39m04/08/2025, 12:17:21 AM [32m LOG[39m [38;5;3m[NestFactory] [39m[32mStarting Nest application...[39m
|
||||
[32m[Nest] 25027 - [39m04/08/2025, 12:17:21 AM [32m LOG[39m [38;5;3m[InstanceLoader] [39m[32mPrismaModule dependencies initialized[39m[38;5;3m +10ms[39m
|
||||
[32m[Nest] 25027 - [39m04/08/2025, 12:17:21 AM [32m LOG[39m [38;5;3m[InstanceLoader] [39m[32mAppModule dependencies initialized[39m[38;5;3m +0ms[39m
|
||||
[32m[Nest] 25027 - [39m04/08/2025, 12:17:21 AM [32m LOG[39m [38;5;3m[InstanceLoader] [39m[32mArticlesModule dependencies initialized[39m[38;5;3m +0ms[39m
|
||||
[32m[Nest] 25027 - [39m04/08/2025, 12:17:21 AM [32m LOG[39m [38;5;3m[RoutesResolver] [39m[32mAppController {/}:[39m[38;5;3m +4ms[39m
|
||||
[32m[Nest] 25027 - [39m04/08/2025, 12:17:21 AM [32m LOG[39m [38;5;3m[RouterExplorer] [39m[32mMapped {/, GET} route[39m[38;5;3m +2ms[39m
|
||||
[32m[Nest] 25027 - [39m04/08/2025, 12:17:21 AM [32m LOG[39m [38;5;3m[RoutesResolver] [39m[32mArticlesController {/articles}:[39m[38;5;3m +0ms[39m
|
||||
[32m[Nest] 25027 - [39m04/08/2025, 12:17:21 AM [32m LOG[39m [38;5;3m[RouterExplorer] [39m[32mMapped {/articles, GET} route[39m[38;5;3m +0ms[39m
|
||||
[32m[Nest] 25027 - [39m04/08/2025, 12:17:21 AM [32m LOG[39m [38;5;3m[NestApplication] [39m[32mNest application successfully started[39m[38;5;3m +116ms[39m
|
||||
Generated
+12839
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"name": "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": {
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "^5.47.2",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"prisma": "^6.6.0",
|
||||
"redis": "^4.7.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rss-parser": "^3.13.0",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@swc/cli": "^0.6.0",
|
||||
"@swc/core": "^1.10.7",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"globals": "^16.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Article" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"publishedAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"categories" TEXT[],
|
||||
"authors" TEXT[],
|
||||
|
||||
CONSTRAINT "Article_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Note" (
|
||||
"id" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"articleId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Note_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Article_url_key" ON "Article"("url");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Note" ADD CONSTRAINT "Note_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `collectionId` to the `Note` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `title` to the `Note` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Note" DROP CONSTRAINT "Note_articleId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Article" ADD COLUMN "collectionId" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Note" ADD COLUMN "collectionId" TEXT NOT NULL,
|
||||
ADD COLUMN "shared" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "sharedVia" TEXT,
|
||||
ADD COLUMN "title" TEXT NOT NULL,
|
||||
ALTER COLUMN "articleId" DROP NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Collection" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Collection_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "QuickNote" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"shared" BOOLEAN NOT NULL DEFAULT false,
|
||||
"sharedVia" TEXT,
|
||||
"collectionId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "QuickNote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Article" ADD CONSTRAINT "Article_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "Collection"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "QuickNote" ADD CONSTRAINT "QuickNote_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "Collection"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Note" ADD CONSTRAINT "Note_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "Collection"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Note" ADD CONSTRAINT "Note_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `userId` to the `Collection` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Collection" ADD COLUMN "userId" TEXT;
|
||||
|
||||
-- Update existing rows with a default user ID
|
||||
UPDATE "Collection" SET "userId" = 'default-user-id' WHERE "userId" IS NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Collection" ALTER COLUMN "userId" SET NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Collection" ADD CONSTRAINT "Collection_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `collectionId` on the `Article` table. All the data in the column will be lost.
|
||||
- Added the required column `userId` to the `QuickNote` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Article" DROP CONSTRAINT "Article_collectionId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Article" DROP COLUMN "collectionId";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Note" ADD COLUMN "userId" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "QuickNote" ADD COLUMN "articleId" TEXT,
|
||||
ADD COLUMN "userId" TEXT NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_ArticleCollections" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "_ArticleCollections_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_ArticleCollections_B_index" ON "_ArticleCollections"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "QuickNote" ADD CONSTRAINT "QuickNote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "QuickNote" ADD CONSTRAINT "QuickNote_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Note" ADD CONSTRAINT "Note_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ArticleCollections" ADD CONSTRAINT "_ArticleCollections_A_fkey" FOREIGN KEY ("A") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ArticleCollections" ADD CONSTRAINT "_ArticleCollections_B_fkey" FOREIGN KEY ("B") REFERENCES "Collection"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `userId` on table `Note` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Note" DROP CONSTRAINT "Note_userId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Note" ALTER COLUMN "userId" SET NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Note" ADD CONSTRAINT "Note_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
@@ -0,0 +1,80 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Collection {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
articles Article[] @relation("ArticleCollections")
|
||||
quickNotes QuickNote[] @relation("CollectionQuickNotes")
|
||||
notes Note[]
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
}
|
||||
|
||||
model Article {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
content String
|
||||
source String
|
||||
url String @unique
|
||||
publishedAt DateTime
|
||||
collections Collection[] @relation("ArticleCollections")
|
||||
quickNotes QuickNote[] @relation("ArticleQuickNotes")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
categories String[]
|
||||
authors String[]
|
||||
Note Note[]
|
||||
}
|
||||
|
||||
model QuickNote {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
content String
|
||||
shared Boolean @default(false)
|
||||
sharedVia String?
|
||||
collection Collection @relation("CollectionQuickNotes", fields: [collectionId], references: [id])
|
||||
collectionId String
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
article Article? @relation("ArticleQuickNotes", fields: [articleId], references: [id])
|
||||
articleId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Note {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
content String
|
||||
shared Boolean @default(false)
|
||||
sharedVia String?
|
||||
collection Collection @relation(fields: [collectionId], references: [id])
|
||||
collectionId String
|
||||
article Article? @relation(fields: [articleId], references: [id])
|
||||
articleId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
User User? @relation(fields: [userId], references: [id])
|
||||
userId String?
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
username String @unique
|
||||
password String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
collections Collection[]
|
||||
quickNotes QuickNote[]
|
||||
notes Note[]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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!');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { ArticlesModule } from './articles/articles.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { RssModule } from './rss/rss.module';
|
||||
import { CollectionsModule } from './collections/collections.module';
|
||||
|
||||
@Module({
|
||||
imports: [ArticlesModule, AuthModule, RssModule, CollectionsModule],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ArticlesService } from './articles.service';
|
||||
|
||||
@Controller('api/articles')
|
||||
export class ArticlesController {
|
||||
constructor(private readonly articlesService: ArticlesService) {}
|
||||
|
||||
@Get()
|
||||
async findAll() {
|
||||
return this.articlesService.findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ArticlesController } from './articles.controller';
|
||||
import { ArticlesService } from './articles.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [ArticlesController],
|
||||
providers: [ArticlesService],
|
||||
})
|
||||
export class ArticlesModule {}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ArticlesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findAll() {
|
||||
return this.prisma.article.findMany({
|
||||
orderBy: { publishedAt: 'desc' },
|
||||
take: 100,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
import { Body, Controller, Post, Request, UseGuards } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { AuthService } from './auth.service';
|
||||
import { User } from '@prisma/client';
|
||||
|
||||
type UserResponse = { access_token: string; user: Omit<User, 'password'> };
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
@UseGuards(AuthGuard('local'))
|
||||
@Post('login')
|
||||
async login(@Request() req): Promise<UserResponse> {
|
||||
return this.authService.login(req.user);
|
||||
}
|
||||
|
||||
@Post('register')
|
||||
async register(@Body() body: { username: string; password: string }): Promise<UserResponse> {
|
||||
console.log('Registering user:', body.username);
|
||||
const user = await this.authService.register(body.username, body.password);
|
||||
return this.authService.login(user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { LocalStrategy } from './local.strategy';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
import { AuthService } from './auth.service';
|
||||
import { UsersModule } from '../users/users.module';
|
||||
import { AuthController } from './auth.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
UsersModule,
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
secret: process.env.JWT_SECRET || 'your-secret-key',
|
||||
signOptions: { expiresIn: '24h' },
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, LocalStrategy, JwtStrategy],
|
||||
exports: [AuthService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Injectable, ConflictException } from '@nestjs/common';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { User } from '@prisma/client';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
type UserWithoutPassword = Omit<User, 'password'>;
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private usersService: UsersService,
|
||||
private jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
async validateUser(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<UserWithoutPassword | null> {
|
||||
const user = await this.usersService.findOne(username);
|
||||
if (user && (await bcrypt.compare(password, user.password))) {
|
||||
const { password: _, ...result } = user;
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async login(user: UserWithoutPassword) {
|
||||
const payload = { username: user.username, sub: user.id };
|
||||
return {
|
||||
access_token: this.jwtService.sign(payload),
|
||||
user,
|
||||
};
|
||||
}
|
||||
|
||||
async register(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<UserWithoutPassword> {
|
||||
// Check if user exists
|
||||
const existingUser = await this.usersService.findOne(username);
|
||||
if (existingUser) {
|
||||
throw new ConflictException('Username already exists');
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Create user
|
||||
const user = await this.usersService.create(username, hashedPassword);
|
||||
|
||||
// Return user without password
|
||||
const { password: _, ...result } = user;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './jwt-auth.guard';
|
||||
export * from './jwt.strategy';
|
||||
export * from './auth.service';
|
||||
export * from './auth.controller';
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ExtractJwt, Strategy, StrategyOptions } from 'passport-jwt';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
interface JwtPayload {
|
||||
sub: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor() {
|
||||
const jwtExtractor = ExtractJwt.fromAuthHeaderAsBearerToken();
|
||||
const options: StrategyOptions = {
|
||||
jwtFromRequest: jwtExtractor,
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: process.env.JWT_SECRET || 'your-secret-key',
|
||||
};
|
||||
|
||||
super(options);
|
||||
}
|
||||
|
||||
validate(payload: JwtPayload): { id: string; username: string } {
|
||||
return { id: payload.sub, username: payload.username };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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(); // Configure the strategy (e.g., usernameField: 'email') if needed
|
||||
}
|
||||
|
||||
async validate(username: string, password: string): Promise<any> {
|
||||
const user = await this.authService.validateUser(username, password);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Param,
|
||||
UseGuards,
|
||||
Request,
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { JwtAuthGuard } from '../auth';
|
||||
import { Article, Collection, QuickNote } from '@prisma/client';
|
||||
|
||||
interface RequestWithUser extends Request {
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
type CollectionWithRelations = Collection & {
|
||||
articles: Article[];
|
||||
quickNotes: QuickNote[];
|
||||
notes: any[];
|
||||
};
|
||||
|
||||
interface CreateCollectionDto {
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface CreateArticleDto {
|
||||
title: string;
|
||||
content: string;
|
||||
source: string;
|
||||
url: string;
|
||||
publishedAt: Date;
|
||||
}
|
||||
|
||||
interface CreateQuickNoteDto {
|
||||
title: string;
|
||||
content: string;
|
||||
shared?: boolean;
|
||||
sharedVia?: string;
|
||||
}
|
||||
|
||||
@Controller('api/collections')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class CollectionsController {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
@Get()
|
||||
async getAllCollections(
|
||||
@Request() req: RequestWithUser,
|
||||
): Promise<CollectionWithRelations[]> {
|
||||
return await this.prisma.collection.findMany({
|
||||
where: {
|
||||
userId: req.user.id,
|
||||
},
|
||||
include: {
|
||||
articles: true,
|
||||
quickNotes: true,
|
||||
notes: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createCollection(
|
||||
@Body() data: CreateCollectionDto,
|
||||
@Request() req: RequestWithUser,
|
||||
): Promise<CollectionWithRelations> {
|
||||
return await this.prisma.collection.create({
|
||||
data: {
|
||||
...data,
|
||||
userId: req.user.id,
|
||||
},
|
||||
include: {
|
||||
articles: true,
|
||||
quickNotes: true,
|
||||
notes: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Post(':id/add-article')
|
||||
async addArticleToCollection(
|
||||
@Param('id') id: string,
|
||||
@Body() articleData: CreateArticleDto,
|
||||
): Promise<Article> {
|
||||
const existingArticle = await this.prisma.article.findUnique({
|
||||
where: { url: articleData.url },
|
||||
});
|
||||
|
||||
if (existingArticle) {
|
||||
await this.prisma.collection.update({
|
||||
where: { id },
|
||||
data: {
|
||||
articles: {
|
||||
connect: { id: existingArticle.id },
|
||||
},
|
||||
},
|
||||
});
|
||||
return existingArticle;
|
||||
}
|
||||
|
||||
return await this.prisma.article.create({
|
||||
data: {
|
||||
...articleData,
|
||||
collections: {
|
||||
connect: { id },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Post(':id/quick-notes')
|
||||
async addQuickNoteToCollection(
|
||||
@Param('id') id: string,
|
||||
@Body() quickNoteData: CreateQuickNoteDto,
|
||||
@Request() req: RequestWithUser,
|
||||
): Promise<QuickNote> {
|
||||
return await this.prisma.quickNote.create({
|
||||
data: {
|
||||
...quickNoteData,
|
||||
shared: quickNoteData.shared ?? false,
|
||||
collection: {
|
||||
connect: { id },
|
||||
},
|
||||
user: {
|
||||
connect: { id: req.user.id },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { CollectionsController } from './collections.controller';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, AuthModule],
|
||||
controllers: [CollectionsController],
|
||||
})
|
||||
export class CollectionsModule {}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
|
||||
bootstrap().catch((err) => {
|
||||
console.error('Error during application bootstrap:', err);
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService
|
||||
extends PrismaClient
|
||||
implements OnModuleInit, OnModuleDestroy
|
||||
{
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Controller, Post, Body, Get } from '@nestjs/common';
|
||||
import { RssService } from './rss.service';
|
||||
|
||||
@Controller('rss')
|
||||
export class RssController {
|
||||
constructor(private readonly rssService: RssService) {}
|
||||
|
||||
@Post('fetch')
|
||||
async fetchAndStore(@Body('feedUrl') feedUrl: string): Promise<{ message: string }> {
|
||||
await this.rssService.fetchAndStoreArticles(feedUrl);
|
||||
return { message: 'RSS feed processed and articles stored successfully.' };
|
||||
}
|
||||
|
||||
@Get('articles')
|
||||
async getAllArticles() {
|
||||
return this.rssService.getAllArticles();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { RssService } from './rss.service';
|
||||
import { RssController } from './rss.controller';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [RssService],
|
||||
controllers: [RssController],
|
||||
exports: [RssService],
|
||||
})
|
||||
export class RssModule {}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import * as Parser from 'rss-parser';
|
||||
|
||||
@Injectable()
|
||||
export class RssService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
feddUrl: string = 'https://www.rt.com/rss/';
|
||||
async fetchAndStoreArticles(feedUrl: string): Promise<void> {
|
||||
const parser = new Parser();
|
||||
const feed = await parser.parseURL(feedUrl);
|
||||
|
||||
for (const item of feed.items) {
|
||||
await this.prisma.article.upsert({
|
||||
where: { url: item.link },
|
||||
update: {},
|
||||
create: {
|
||||
title: item.title || 'Untitled',
|
||||
content: item.contentSnippet || '',
|
||||
source: feed.title || 'Unknown Source',
|
||||
url: item.link || '',
|
||||
publishedAt: item.isoDate ? new Date(item.isoDate) : new Date(),
|
||||
categories: item.categories || [],
|
||||
authors: item.creator ? [item.creator] : [],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getAllArticles() {
|
||||
return this.prisma.article.findMany({
|
||||
orderBy: { publishedAt: 'desc' },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UsersService } from './users.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [UsersService],
|
||||
exports: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { User } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findOne(username: string): Promise<User | null> {
|
||||
return await this.prisma.user.findUnique({
|
||||
where: { username },
|
||||
});
|
||||
}
|
||||
|
||||
async create(username: string, hashedPassword: string): Promise<User> {
|
||||
return await this.prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
password: hashedPassword,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { App } from 'supertest/types';
|
||||
import { AppModule } from './../src/app.module';
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
let app: INestApplication<App>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('/ (GET)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Hello World!');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { AppModule } from '../src/app.module';
|
||||
|
||||
describe('AuthController (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('/auth/login (POST) - success', () => {
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/login')
|
||||
.send({ username: 'test', password: 'test' })
|
||||
.expect(201)
|
||||
.expect({ userId: 1, username: 'test' });
|
||||
});
|
||||
|
||||
it('/auth/login (POST) - failure', () => {
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/login')
|
||||
.send({ username: 'wrong', password: 'wrong' })
|
||||
.expect(401);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".e2e-spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2023",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user