Initial commit
This commit is contained in:
22
apps/api/Dockerfile
Normal file
22
apps/api/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
# This file is generated by Nx.
|
||||
# Build the docker image with `npx nx docker:build api`.
|
||||
# Tip: Modify "docker:build" options in project.json to change docker build args.
|
||||
#
|
||||
# Run the container with `nx docker:run api -p 3000:3000`.
|
||||
#
|
||||
FROM docker.io/node:lts-alpine
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=3333
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY dist .
|
||||
|
||||
# You can remove this install step if you build with `--bundle` option.
|
||||
# The bundled output will include external dependencies.
|
||||
|
||||
RUN npm --omit=dev -f install
|
||||
|
||||
CMD [ "node", "main.js" ]
|
||||
3
apps/api/eslint.config.mjs
Normal file
3
apps/api/eslint.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [...baseConfig];
|
||||
73
apps/api/project.json
Normal file
73
apps/api/project.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "api",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "apps/api/src",
|
||||
"projectType": "application",
|
||||
"tags": ["scope:api"],
|
||||
"release": {
|
||||
"docker": {
|
||||
"repositoryName": "org/angular-template-api"
|
||||
}
|
||||
},
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/esbuild:esbuild",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"platform": "node",
|
||||
"outputPath": "apps/api/dist",
|
||||
"format": ["cjs"],
|
||||
"bundle": false,
|
||||
"main": "apps/api/src/main.ts",
|
||||
"tsConfig": "apps/api/tsconfig.app.json",
|
||||
"assets": ["apps/api/src/assets"],
|
||||
"generatePackageJson": true,
|
||||
"esbuildOptions": {
|
||||
"sourcemap": true,
|
||||
"outExtension": {
|
||||
".js": ".js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"development": {},
|
||||
"production": {
|
||||
"generateLockfile": true,
|
||||
"esbuildOptions": {
|
||||
"sourcemap": false,
|
||||
"outExtension": {
|
||||
".js": ".js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"continuous": true,
|
||||
"executor": "@nx/js:node",
|
||||
"defaultConfiguration": "development",
|
||||
"dependsOn": ["build"],
|
||||
"options": {
|
||||
"buildTarget": "api:build",
|
||||
"runBuildTargetDependencies": false
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"buildTarget": "api:build:development"
|
||||
},
|
||||
"production": {
|
||||
"buildTarget": "api:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"options": {
|
||||
"passWithNoTests": true
|
||||
}
|
||||
},
|
||||
"docker:build": {
|
||||
"dependsOn": ["build"]
|
||||
}
|
||||
}
|
||||
}
|
||||
0
apps/api/src/assets/.gitkeep
Normal file
0
apps/api/src/assets/.gitkeep
Normal file
139
apps/api/src/main.ts
Normal file
139
apps/api/src/main.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import express from 'express';
|
||||
import { ProductsService } from '@org/api/products';
|
||||
import { ApiResponse, Product, ProductFilter, PaginatedResponse } from '@org/models';
|
||||
|
||||
const host = process.env.HOST ?? 'localhost';
|
||||
const port = process.env.PORT ? Number(process.env.PORT) : 3333;
|
||||
|
||||
const app = express();
|
||||
const productsService = new ProductsService();
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
|
||||
// CORS configuration for Angular app
|
||||
app.use((req, res, next) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.sendStatus(200);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send({ message: 'Hello API' });
|
||||
});
|
||||
|
||||
// Products endpoints
|
||||
app.get('/api/products', (req, res) => {
|
||||
try {
|
||||
const filter: ProductFilter = {};
|
||||
|
||||
if (req.query.category) {
|
||||
filter.category = req.query.category as string;
|
||||
}
|
||||
if (req.query.minPrice) {
|
||||
filter.minPrice = Number(req.query.minPrice);
|
||||
}
|
||||
if (req.query.maxPrice) {
|
||||
filter.maxPrice = Number(req.query.maxPrice);
|
||||
}
|
||||
if (req.query.inStock !== undefined) {
|
||||
filter.inStock = req.query.inStock === 'true';
|
||||
}
|
||||
if (req.query.searchTerm) {
|
||||
filter.searchTerm = req.query.searchTerm as string;
|
||||
}
|
||||
|
||||
const page = req.query.page ? Number(req.query.page) : 1;
|
||||
const pageSize = req.query.pageSize ? Number(req.query.pageSize) : 12;
|
||||
|
||||
const result = productsService.getAllProducts(filter, page, pageSize);
|
||||
|
||||
const response: ApiResponse<PaginatedResponse<Product>> = {
|
||||
data: result,
|
||||
success: true,
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
const response: ApiResponse<null> = {
|
||||
data: null,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
res.status(500).json(response);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/products/:id', (req, res) => {
|
||||
try {
|
||||
const product = productsService.getProductById(req.params.id);
|
||||
|
||||
if (!product) {
|
||||
const response: ApiResponse<null> = {
|
||||
data: null,
|
||||
success: false,
|
||||
error: 'Product not found',
|
||||
};
|
||||
return res.status(404).json(response);
|
||||
}
|
||||
|
||||
const response: ApiResponse<Product> = {
|
||||
data: product,
|
||||
success: true,
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
const response: ApiResponse<null> = {
|
||||
data: null,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
res.status(500).json(response);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/products-metadata/categories', (req, res) => {
|
||||
try {
|
||||
const categories = productsService.getCategories();
|
||||
const response: ApiResponse<string[]> = {
|
||||
data: categories,
|
||||
success: true,
|
||||
};
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
const response: ApiResponse<null> = {
|
||||
data: null,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
res.status(500).json(response);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/products-metadata/price-range', (req, res) => {
|
||||
try {
|
||||
const priceRange = productsService.getPriceRange();
|
||||
const response: ApiResponse<{ min: number; max: number }> = {
|
||||
data: priceRange,
|
||||
success: true,
|
||||
};
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
const response: ApiResponse<null> = {
|
||||
data: null,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
res.status(500).json(response);
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(port, host, () => {
|
||||
console.log(`[ ready ] http://${host}:${port}`);
|
||||
});
|
||||
10
apps/api/tsconfig.app.json
Normal file
10
apps/api/tsconfig.app.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
|
||||
}
|
||||
16
apps/api/tsconfig.json
Normal file
16
apps/api/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
28
apps/api/tsconfig.spec.json
Normal file
28
apps/api/tsconfig.spec.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
22
apps/api/vite.config.ts
Normal file
22
apps/api/vite.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/apps/api',
|
||||
plugins: [tsconfigPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
test: {
|
||||
name: 'api',
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
reporters: ['default'],
|
||||
passWithNoTests: true,
|
||||
coverage: {
|
||||
reportsDirectory: '../../coverage/apps/api',
|
||||
provider: 'v8' as const,
|
||||
},
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user