Fixes and adjustments to demo
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -56,4 +56,5 @@ __screenshots__/
|
||||
|
||||
/prisma/libs/prisma-generated/src/lib/generated
|
||||
libs/prisma-generated/src/lib/generated/
|
||||
/prisma/migrations/
|
||||
|
||||
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -2,6 +2,7 @@
|
||||
"recommendations": [
|
||||
"nrwl.angular-console",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint"
|
||||
"dbaeumer.vscode-eslint",
|
||||
"firsttris.vscode-jest-runner"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default {
|
||||
module.exports = {
|
||||
displayName: 'api-e2e',
|
||||
preset: '../../jest.preset.js',
|
||||
globalSetup: '<rootDir>/src/support/global-setup.ts',
|
||||
|
||||
@@ -9,6 +9,9 @@ import { AppModule } from './app/app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.enableCors({
|
||||
origin: ['http://localhost:4200'],
|
||||
});
|
||||
const globalPrefix = 'api';
|
||||
app.setGlobalPrefix(globalPrefix);
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Controller, Get, NotFoundException, Param } from '@nestjs/common';
|
||||
import { Post } from '@shared/prisma-generated/src/types';
|
||||
import { prisma } from '@shared/prisma-generated/src/prisma';
|
||||
|
||||
@Controller('posts')
|
||||
export class PostsController {
|
||||
@Get()
|
||||
async findAll(): Promise<Post[]> {
|
||||
const posts = await prisma.post.findMany();
|
||||
return posts;
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findById(@Param('id') id: string): Promise<Post> {
|
||||
const post = await prisma.post.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
throw new NotFoundException(`Post with id ${id} not found`);
|
||||
}
|
||||
|
||||
return post;
|
||||
}
|
||||
}
|
||||
41
apps/api/src/modules/users/posts.controller.ts
Normal file
41
apps/api/src/modules/users/posts.controller.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Controller, Get, Param, Post, Body, Put, Delete, NotFoundException } from '@nestjs/common';
|
||||
import { PostsService } from './posts.service';
|
||||
import { Post as PostEntity } from '@shared/prisma-generated/src/types';
|
||||
import { PostDto } from '@shared/shared-dto';
|
||||
import { UpdatePostDto } from '@shared/shared-dto';
|
||||
|
||||
@Controller('posts')
|
||||
export class PostsController {
|
||||
constructor(private readonly postsService: PostsService) {}
|
||||
|
||||
@Get()
|
||||
async findAll(): Promise<PostEntity[]> {
|
||||
const posts = this.postsService.findAll();
|
||||
|
||||
if (!posts) {
|
||||
throw new NotFoundException("No posts found")
|
||||
}
|
||||
|
||||
return posts
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findById(@Param('id') id: string): Promise<PostEntity> {
|
||||
return this.postsService.findById(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() data: PostDto): Promise<PostEntity> {
|
||||
return this.postsService.create(data);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async update(@Param('id') id: string, @Body() data: UpdatePostDto): Promise<PostEntity> {
|
||||
return this.postsService.update(id, data);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(@Param('id') id: string): Promise<PostEntity> {
|
||||
return this.postsService.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PostsController } from './controllers/posts/posts.controller';
|
||||
import { PostsController } from './posts.controller';
|
||||
import { PostsService } from './posts.service';
|
||||
|
||||
@Module({
|
||||
controllers: [PostsController],
|
||||
providers: [PostsService],
|
||||
exports: [PostsService],
|
||||
})
|
||||
export class PostsModule {}
|
||||
40
apps/api/src/modules/users/posts.service.ts
Normal file
40
apps/api/src/modules/users/posts.service.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Post } from '@shared/prisma-generated/src/types';
|
||||
import { prisma } from '@shared/prisma-generated/src/prisma';
|
||||
import { PostDto } from '@shared/shared-dto';
|
||||
import { UpdatePostDto } from '@shared/shared-dto';
|
||||
|
||||
@Injectable()
|
||||
export class PostsService {
|
||||
async findAll(): Promise<Post[]> {
|
||||
const posts = await prisma.post.findMany();
|
||||
return posts;
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Post> {
|
||||
const post = await prisma.post.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
throw new NotFoundException(`Post with id ${id} not found`);
|
||||
}
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
async create(data: PostDto): Promise<Post> {
|
||||
return prisma.post.create({ data });
|
||||
}
|
||||
|
||||
async update(id: string, data: UpdatePostDto): Promise<Post> {
|
||||
return prisma.post.update({
|
||||
where: { id },
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<Post> {
|
||||
return prisma.post.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
} from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { appRoutes } from './app.routes';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideBrowserGlobalErrorListeners(), provideRouter(appRoutes)],
|
||||
providers: [provideBrowserGlobalErrorListeners(), provideRouter(appRoutes), provideHttpClient()],
|
||||
};
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
<app-nx-welcome></app-nx-welcome>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { Route } from '@angular/router';
|
||||
import { Home } from './features/home/home';
|
||||
|
||||
export const appRoutes: Route[] = [];
|
||||
export const appRoutes: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
component: Home,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideRouter, Router } from '@angular/router';
|
||||
import { App } from './app';
|
||||
import { NxWelcome } from './nx-welcome';
|
||||
import { appRoutes } from './app.routes';
|
||||
|
||||
describe('App', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [App, NxWelcome],
|
||||
imports: [App],
|
||||
providers: [provideRouter(appRoutes)],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should render title', async () => {
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render Home page on default route', async () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const router = TestBed.inject(Router);
|
||||
|
||||
await router.navigateByUrl('/');
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain(
|
||||
'Welcome web-app',
|
||||
);
|
||||
expect(compiled.textContent).toContain('Posts');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NxWelcome } from './nx-welcome';
|
||||
import { Post } from '@shared/prisma-generated/src/types'
|
||||
|
||||
@Component({
|
||||
imports: [NxWelcome, RouterModule],
|
||||
imports: [RouterModule],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss',
|
||||
})
|
||||
export class App {
|
||||
protected title = 'web-app';
|
||||
protected posts: Post[] = [];
|
||||
}
|
||||
|
||||
32
apps/web-app/src/app/features/home/home.html
Normal file
32
apps/web-app/src/app/features/home/home.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<section class="home">
|
||||
<h1>Posts</h1>
|
||||
|
||||
@if (isLoading) {
|
||||
<p>Loading posts...</p>
|
||||
} @else if (errorMessage) {
|
||||
<p class="error-message">{{ errorMessage }}</p>
|
||||
} @else if (posts.length === 0) {
|
||||
<p class="no-posts-message">No posts found.</p>
|
||||
} @else {
|
||||
<div class="cards-container">
|
||||
@for (post of posts; track post) {
|
||||
<mat-card class="post-card" appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ post.title }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>{{ post.content || 'No content' }}</p>
|
||||
<p>UserId: {{ post.userId }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="button-container" data-testid="fetch-button">
|
||||
<button mat-raised-button color="primary" (click)="fetchPosts()" [disabled]="isLoading">
|
||||
{{ isLoading ? 'Loading...' : 'Fetch Posts' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
56
apps/web-app/src/app/features/home/home.scss
Normal file
56
apps/web-app/src/app/features/home/home.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
.button-container {
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.cards-container {
|
||||
display: flex;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
justify-items: center;
|
||||
width: 100%;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.post-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
mat-card-header {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
mat-card-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
max-height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d32f2f;
|
||||
font-weight: 500;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.no-posts-message {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.home {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
34
apps/web-app/src/app/features/home/home.spec.ts
Normal file
34
apps/web-app/src/app/features/home/home.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Home } from './home';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
describe('Home', () => {
|
||||
let component: Home;
|
||||
let fixture: ComponentFixture<Home>;
|
||||
let debugElement: DebugElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Home],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Home);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
|
||||
debugElement = fixture.debugElement;
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have button', () => {
|
||||
const fetchButton = debugElement.query(
|
||||
By.css('[data-testid="fetch-button"]')
|
||||
);
|
||||
|
||||
expect(fetchButton).toBeDefined();
|
||||
})
|
||||
});
|
||||
39
apps/web-app/src/app/features/home/home.ts
Normal file
39
apps/web-app/src/app/features/home/home.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { ChangeDetectorRef, Component, inject } from '@angular/core';
|
||||
import { PostDto } from '@shared/shared-dto'
|
||||
import { PostService } from '../../services/PostService/PostService/post-service';
|
||||
import { timeout } from 'rxjs';
|
||||
import { MatButtonModule } from '@angular/material/button'
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
imports: [MatButtonModule, MatCardModule],
|
||||
templateUrl: './home.html',
|
||||
styleUrl: './home.scss',
|
||||
})
|
||||
export class Home {
|
||||
posts: PostDto[] = [];
|
||||
isLoading = false;
|
||||
errorMessage = '';
|
||||
|
||||
postService = inject(PostService);
|
||||
cdr = inject(ChangeDetectorRef);
|
||||
|
||||
fetchPosts(): void {
|
||||
this.isLoading = true;
|
||||
this.errorMessage = '';
|
||||
this.postService.getPosts().pipe(timeout(8000)).subscribe({
|
||||
next: (posts) => {
|
||||
this.posts = posts;
|
||||
this.isLoading = false;
|
||||
this.cdr.detectChanges();
|
||||
},
|
||||
error: () => {
|
||||
this.errorMessage = 'Failed to load posts.';
|
||||
this.isLoading = false;
|
||||
this.cdr.detectChanges();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PostService } from './post-service';
|
||||
|
||||
describe('PostService', () => {
|
||||
let service: PostService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(PostService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Observable } from 'rxjs';
|
||||
import { PostDto } from '@shared/shared-dto';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PostService {
|
||||
private httpClient = inject(HttpClient);
|
||||
private baseUrl = 'http://localhost:3000/api';
|
||||
|
||||
getPosts(): Observable<PostDto[]> {
|
||||
return this.httpClient.get<PostDto[]>(`${this.baseUrl}/posts`);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
|
||||
@@ -1 +1,41 @@
|
||||
|
||||
// Include theming for Angular Material with `mat.theme()`.
|
||||
// This Sass mixin will define CSS variables that are used for styling Angular Material
|
||||
// components according to the Material 3 design spec.
|
||||
// Learn more about theming and how to use it for your application's
|
||||
// custom components at https://material.angular.dev/guide/theming
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
@include mat.theme((
|
||||
color: (
|
||||
primary: mat.$azure-palette,
|
||||
tertiary: mat.$blue-palette,
|
||||
),
|
||||
typography: Roboto,
|
||||
density: 0,
|
||||
));
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// Default the application to a light color theme. This can be changed to
|
||||
// `dark` to enable the dark color theme, or to `light dark` to defer to the
|
||||
// user's system settings.
|
||||
color-scheme: light;
|
||||
|
||||
// Set a default background, font and text colors for the application using
|
||||
// Angular Material's system-level CSS variables. Learn more about these
|
||||
// variables at https://material.angular.dev/guide/system-variables
|
||||
background-color: var(--mat-sys-surface);
|
||||
color: var(--mat-sys-on-surface);
|
||||
font: var(--mat-sys-body-medium);
|
||||
|
||||
// Reset the user agent margin.
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
|
||||
@@ -20,7 +20,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./apps/api/Dockerfile.dev
|
||||
command: sh -c "pnpm install && pnpm prisma generate && pnpm prisma migrate deploy && pnpm nx serve api"
|
||||
command: sh -c "pnpm install && pnpm prisma generate && pnpm prisma migrate dev && pnpm prisma db seed && pnpm nx serve api"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
|
||||
6
jest.config.ts
Normal file
6
jest.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { Config } from 'jest';
|
||||
import { getJestProjectsAsync } from '@nx/jest';
|
||||
|
||||
export default async (): Promise<Config> => ({
|
||||
projects: await getJestProjectsAsync(),
|
||||
});
|
||||
3
jest.preset.js
Normal file
3
jest.preset.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const nxPreset = require('@nx/jest/preset').default;
|
||||
|
||||
module.exports = { ...nxPreset };
|
||||
11
libs/shared-dto/README.md
Normal file
11
libs/shared-dto/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# shared-dto
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build shared-dto` to build the library.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test shared-dto` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
19
libs/shared-dto/eslint.config.mjs
Normal file
19
libs/shared-dto/eslint.config.mjs
Normal file
@@ -0,0 +1,19 @@
|
||||
import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.json'],
|
||||
rules: {
|
||||
'@nx/dependency-checks': [
|
||||
'error',
|
||||
{
|
||||
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'],
|
||||
},
|
||||
],
|
||||
},
|
||||
languageOptions: {
|
||||
parser: await import('jsonc-eslint-parser'),
|
||||
},
|
||||
},
|
||||
];
|
||||
10
libs/shared-dto/jest.config.cts
Normal file
10
libs/shared-dto/jest.config.cts
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
displayName: 'shared-dto',
|
||||
preset: '../../jest.preset.js',
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }]
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../../coverage/libs/shared-dto',
|
||||
};
|
||||
9
libs/shared-dto/project.json
Normal file
9
libs/shared-dto/project.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "shared-dto",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/shared-dto/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
}
|
||||
}
|
||||
1
libs/shared-dto/src/index.ts
Normal file
1
libs/shared-dto/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './lib/shared-dto';
|
||||
7
libs/shared-dto/src/lib/shared-dto.spec.ts
Normal file
7
libs/shared-dto/src/lib/shared-dto.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { sharedDto } from './shared-dto';
|
||||
|
||||
describe('sharedDto', () => {
|
||||
it('should work', () => {
|
||||
expect(sharedDto()).toEqual('shared-dto');
|
||||
});
|
||||
});
|
||||
9
libs/shared-dto/src/lib/shared-dto.ts
Normal file
9
libs/shared-dto/src/lib/shared-dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Post } from '@shared/prisma-generated/src/types';
|
||||
|
||||
export function sharedDto(): string {
|
||||
return 'shared-dto';
|
||||
}
|
||||
|
||||
export type PostDto = Omit<Post, 'id' | 'createdAt' | 'updatedAt'>;
|
||||
|
||||
export type UpdatePostDto = Partial<PostDto>;
|
||||
23
libs/shared-dto/tsconfig.json
Normal file
23
libs/shared-dto/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"importHelpers": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
libs/shared-dto/tsconfig.lib.json
Normal file
15
libs/shared-dto/tsconfig.lib.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"jest.config.cts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
16
libs/shared-dto/tsconfig.spec.json
Normal file
16
libs/shared-dto/tsconfig.spec.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node10",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"jest.config.cts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
21
nx.json
21
nx.json
@@ -6,7 +6,12 @@
|
||||
"production": [
|
||||
"default",
|
||||
"!{projectRoot}/.eslintrc.json",
|
||||
"!{projectRoot}/eslint.config.mjs"
|
||||
"!{projectRoot}/eslint.config.mjs",
|
||||
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
|
||||
"!{projectRoot}/tsconfig.spec.json",
|
||||
"!{projectRoot}/jest.config.[jt]s",
|
||||
"!{projectRoot}/src/test-setup.[jt]s",
|
||||
"!{projectRoot}/test-setup.[jt]s"
|
||||
],
|
||||
"sharedGlobals": []
|
||||
},
|
||||
@@ -28,6 +33,11 @@
|
||||
"@angular/build:unit-test": {
|
||||
"cache": true,
|
||||
"inputs": ["default", "^production"]
|
||||
},
|
||||
"@nx/js:tsc": {
|
||||
"cache": true,
|
||||
"dependsOn": ["^build"],
|
||||
"inputs": ["production", "^production"]
|
||||
}
|
||||
},
|
||||
"generators": {
|
||||
@@ -36,6 +46,9 @@
|
||||
"linter": "eslint",
|
||||
"style": "scss",
|
||||
"unitTestRunner": "vitest-angular"
|
||||
},
|
||||
"@nx/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"neverConnectToCloud": true,
|
||||
@@ -56,6 +69,12 @@
|
||||
"options": {
|
||||
"targetName": "eslint:lint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"plugin": "@nx/jest/plugin",
|
||||
"options": {
|
||||
"targetName": "test"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
12
package.json
12
package.json
@@ -5,10 +5,12 @@
|
||||
"scripts": {},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^21.2.4",
|
||||
"@angular/common": "~21.2.0",
|
||||
"@angular/compiler": "~21.2.0",
|
||||
"@angular/core": "~21.2.0",
|
||||
"@angular/forms": "~21.2.0",
|
||||
"@angular/material": "^21.2.4",
|
||||
"@angular/platform-browser": "~21.2.0",
|
||||
"@angular/router": "~21.2.0",
|
||||
"@nestjs/common": "^11.0.0",
|
||||
@@ -17,7 +19,6 @@
|
||||
"@prisma/adapter-pg": "^7.5.0",
|
||||
"@prisma/client": "^7.5.0",
|
||||
"axios": "^1.6.0",
|
||||
"pg": "^8.20.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "~7.8.0"
|
||||
},
|
||||
@@ -34,6 +35,7 @@
|
||||
"@nx/angular": "22.6.1",
|
||||
"@nx/eslint": "22.6.1",
|
||||
"@nx/eslint-plugin": "22.6.1",
|
||||
"@nx/jest": "22.6.1",
|
||||
"@nx/js": "22.6.1",
|
||||
"@nx/nest": "^22.6.1",
|
||||
"@nx/node": "22.6.1",
|
||||
@@ -44,17 +46,25 @@
|
||||
"@swc-node/register": "~1.11.1",
|
||||
"@swc/core": "~1.15.5",
|
||||
"@swc/helpers": "~0.5.18",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "20.19.9",
|
||||
"@typescript-eslint/utils": "^8.40.0",
|
||||
"angular-eslint": "^21.2.0",
|
||||
"dotenv": "^17.3.1",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"jest": "^30.0.2",
|
||||
"jest-environment-node": "^30.0.2",
|
||||
"jest-util": "^30.0.2",
|
||||
"jsdom": "^27.1.0",
|
||||
"jsonc-eslint-parser": "^2.1.0",
|
||||
"nx": "22.6.1",
|
||||
"prettier": "~3.6.2",
|
||||
"prisma": "^7.5.0",
|
||||
"ts-jest": "^29.4.0",
|
||||
"ts-node": "10.9.1",
|
||||
"tslib": "^2.3.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.40.0",
|
||||
"vitest": "^4.0.8",
|
||||
|
||||
517
pnpm-lock.yaml
generated
517
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ export default defineConfig({
|
||||
schema: "prisma/schema.prisma",
|
||||
migrations: {
|
||||
path: "prisma/migrations",
|
||||
seed: "tsx prisma/seedData.ts"
|
||||
},
|
||||
datasource: {
|
||||
url: process.env["DATABASE_URL"],
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PostStatus" AS ENUM ('DRAFT', 'PUBLISHED', 'ARCHIVED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "posts" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT,
|
||||
"status" "PostStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"authorName" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "posts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
@@ -1,3 +0,0 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
@@ -19,13 +19,20 @@ enum PostStatus {
|
||||
|
||||
// Simple table for blog posts
|
||||
model Post {
|
||||
id String @id @default(cuid())
|
||||
id String @id @unique @default(cuid())
|
||||
title String
|
||||
content String?
|
||||
status PostStatus @default(DRAFT)
|
||||
authorName String
|
||||
published Boolean
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
|
||||
@@map("posts")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @unique @default(cuid())
|
||||
email String @unique
|
||||
name String
|
||||
posts Post[]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
INSERT INTO "posts" ("id","title","content","status","authorName","createdAt","updatedAt")
|
||||
VALUES
|
||||
('post_1','Hello Prisma','First seeded post','DRAFT','Alice',NOW(),NOW()),
|
||||
('post_2','Published post','Seeded and published','PUBLISHED','Bob',NOW(),NOW())
|
||||
ON CONFLICT ("id") DO NOTHING;
|
||||
56
prisma/seedData.ts
Normal file
56
prisma/seedData.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import "dotenv/config";
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
import { PrismaClient } from "../libs/prisma-generated/src/client";
|
||||
|
||||
const connectionString = `${process.env.DATABASE_URL}`;
|
||||
const adapter = new PrismaPg({ connectionString });
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
async function main() {
|
||||
const alice = await prisma.user.upsert({
|
||||
where: { email: "alice@prisma.io" },
|
||||
update: {},
|
||||
create: {
|
||||
email: "alice@prisma.io",
|
||||
name: "Alice",
|
||||
posts: {
|
||||
create: {
|
||||
title: "Check out Prisma with Next.js",
|
||||
content: "https://www.prisma.io/nextjs",
|
||||
published: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const bob = await prisma.user.upsert({
|
||||
where: { email: "bob@prisma.io" },
|
||||
update: {},
|
||||
create: {
|
||||
email: "bob@prisma.io",
|
||||
name: "Bob",
|
||||
posts: {
|
||||
create: [
|
||||
{
|
||||
title: "Check out the code in gitea",
|
||||
content: "https://gitea.tonssikas.ovh",
|
||||
published: true,
|
||||
},
|
||||
{
|
||||
title: "A third example card",
|
||||
content: "https://google.com",
|
||||
published: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log({ alice, bob });
|
||||
}
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -27,7 +27,8 @@
|
||||
"@shared/prisma-generated/src/prisma": [
|
||||
"libs/prisma-generated/src/prisma.ts"
|
||||
],
|
||||
},
|
||||
"@shared/shared-dto": ["libs/shared-dto/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "tmp"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user