This commit is contained in:
vahidrezvani 2025-08-21 11:56:39 +03:30
commit d2b6c0bb2b
168 changed files with 28613 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

6
.env Normal file
View File

@ -0,0 +1,6 @@
MONGODB_URI=mongodb://root:ct4k7miShBc3VDw2wzVaDQRq@fitz-roy.liara.cloud:32207/my-app?authSource=admin
LIARA_ENDPOINT=https://storage.iran.liara.space
LIARA_BUCKET_NAME=development-bucket
LIARA_ACCESS_KEY=cc510kvmpiu861r2
LIARA_SECRET_KEY=13ed8542-c0e6-48cb-b3b4-ff8af6d12364
LIARA_REGION=ir

44
.gitignore vendored Normal file
View File

@ -0,0 +1,44 @@
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# compiled output
dist
tmp
out-tsc
# dependencies
node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
.nx/cache
.nx/workspace-data
.cursor/rules/nx-rules.mdc
.github/instructions/nx.instructions.md

5
.prettierignore Normal file
View File

@ -0,0 +1,5 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/workspace-data

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

7
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"recommendations": [
"nrwl.angular-console",
"esbenp.prettier-vscode",
"firsttris.vscode-jest-runner"
]
}

172
README.md Normal file
View File

@ -0,0 +1,172 @@
# EcommerceMicroservices
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
✨ Your new, shiny [Nx workspace](https://nx.dev) is almost ready ✨.
Run `npx nx graph` to visually explore what got created. Now, let's get you up to speed!
## Finish your remote caching setup
[Click here to finish setting up your workspace!](https://cloud.nx.app/connect/LFsmU67JYN)
## Run tasks
To run tasks with Nx use:
```sh
npx nx <target> <project-name>
```
For example:
```sh
npx nx build myproject
```
These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the `project.json` or `package.json` files.
[More about running tasks in the docs &raquo;](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Add new projects
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.
To install a new plugin you can use the `nx add` command. Here's an example of adding the React plugin:
```sh
npx nx add @nx/react
```
Use the plugin's generator to create new projects. For example, to create a new React app or library:
```sh
# Generate an app
npx nx g @nx/react:app demo
# Generate a library
npx nx g @nx/react:lib some-lib
```
You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx list <plugin-name>` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE.
[Learn more about Nx plugins &raquo;](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry &raquo;](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Install Nx Console
Nx Console is an editor extension that enriches your developer experience. It lets you run tasks, generate code, and improves code autocompletion in your IDE. It is available for VSCode and IntelliJ.
[Install Nx Console &raquo;](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Useful links
Learn more:
- [Learn about Nx on CI](https://nx.dev/ci/intro/ci-with-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [Releasing Packages with Nx release](https://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [What are Nx plugins?](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
And join the Nx community:
- [Discord](https://go.nx.dev/community)
- [Follow us on X](https://twitter.com/nxdevtools) or [LinkedIn](https://www.linkedin.com/company/nrwl)
- [Our Youtube channel](https://www.youtube.com/@nxdevtools)
- [Our blog](https://nx.dev/blog?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
/*
User Service (Handles user profiles, authentication, notifications):
Collections: User, Address, AddressDocument, PaymentMethod, Invitation, Support, Comment.
Services to Implement:
User registration/login (with roles: seller/user).
Profile management (update name, language, nationality).
Notification system (send based on categories/frequency).
Address CRUD (create/read/update/delete).
Payment method management.
Support ticket creation/resolution.
Comment/review posting (with likes/dislikes).
Catalog Service (Manages products, categories, properties):
Collections: ProductCategory, ProductCategoryDocument, ShopCategory, ShopCategoryDocument, Property, PropertyDocument, Tag, Product, ProductDocument, Brand.
Services to Implement:
Category CRUD (hierarchical trees for shops/products).
Product listing/create/update (with properties, tags, images).
Brand management.
Search/filtering (by tags, categories, properties).
Property definition (user/system types).
Why? Core catalog domain; scales independently for search-heavy ops.
Vendor Service (Handles shops, branches, regions):
Collections: Shop, ShopDocument, Branch, Region, RegionDocument.
Services to Implement:
Shop/branch creation (with addresses, schedules, facilities).
Region management (polygons for geo-queries).
Vendor approval (adminStatus updates).
Scoring/facilities updates.
Why? Vendor-specific; allows separate scaling for multi-vendor growth.
Discount Service (Manages promotions and discounts):
Collections: ItemDiscount, Discount.
Services to Implement:
Discount creation (types, percentages, expirations, limits).
Apply discounts to items/products/shops.
Validation (e.g., check expiry, max usage).
Event emission for discount applications.
Why? Isolated for complex rules; easy to update without affecting core e-commerce.
Order Service (Handles orders, reservations, transactions):
Collections: Order, OrderDetail, Transaction, Reservation.
Services to Implement:
Order creation/processing (apply discounts, calculate final price).
Payment integration (via PaymentMethod refs).
Transaction logging (increases/decreases).
Status updates (e.g., delivery time, process).
Reservation handling.
Why? Transactional core; use sagas for distributed transactions (e.g., with discounts).
Advertisement Service (Handles ads and banners):
Collections: Advertisement.
Services to Implement:
Ad creation (banners, costs, dates).
Targeting (by location, tags, user).
Transaction linking for ad payments.
Why? Non-core; can be scaled separately for marketing teams.
API Gateway / Orchestration Service (Cross-cutting):
No specific collections; aggregates from others.
Services: Authentication, rate-limiting, routing to other services.
Why? Entry point; handles multi-language responses.
*/

View File

@ -0,0 +1,3 @@
const baseConfig = require('../eslint.config.js');
module.exports = [...baseConfig];

View File

@ -0,0 +1,10 @@
export default {
displayName: 'advertisement-service',
preset: '../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../coverage/advertisement-service',
};

View File

@ -0,0 +1,26 @@
{
"name": "advertisement-service",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "advertisement-service/src",
"projectType": "application",
"tags": [],
"targets": {
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"dependsOn": ["build"],
"options": {
"buildTarget": "advertisement-service:build",
"runBuildTargetDependencies": false
},
"configurations": {
"development": {
"buildTarget": "advertisement-service:build:development"
},
"production": {
"buildTarget": "advertisement-service:build:production"
}
}
}
}
}

View File

@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let app: TestingModule;
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
});
describe('getData', () => {
it('should return "Hello API"', () => {
const appController = app.get<AppController>(AppController);
expect(appController.getData()).toEqual({ message: 'Hello API' });
});
});
});

View File

@ -0,0 +1,13 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getData() {
return this.appService.getData();
}
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@ -0,0 +1,21 @@
import { Test } from '@nestjs/testing';
import { AppService } from './app.service';
describe('AppService', () => {
let service: AppService;
beforeAll(async () => {
const app = await Test.createTestingModule({
providers: [AppService],
}).compile();
service = app.get<AppService>(AppService);
});
describe('getData', () => {
it('should return "Hello API"', () => {
expect(service.getData()).toEqual({ message: 'Hello API' });
});
});
});

View File

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getData(): { message: string } {
return { message: 'Hello API' };
}
}

View File

@ -0,0 +1,21 @@
/**
* This is not a production server yet!
* This is only a minimal backend to get started.
*/
import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
import { Transport } from '@nestjs/microservices';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.TCP,
options: { port: 4006 },
});
await app.listen();
console.log('User Service is running on port 4006');
}
bootstrap();

View File

@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"module": "commonjs",
"types": ["node"],
"emitDecoratorMetadata": true,
"target": "es2021"
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

View File

@ -0,0 +1,16 @@
{
"extends": "../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,20 @@
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, '../dist/advertisement-service'),
},
plugins: [
new NxAppWebpackPlugin({
target: 'node',
compiler: 'tsc',
main: './src/main.ts',
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
optimization: false,
outputHashing: 'none',
generatePackageJson: true,
}),
],
};

View File

@ -0,0 +1,3 @@
const baseConfig = require('../eslint.config.js');
module.exports = [...baseConfig];

View File

@ -0,0 +1,10 @@
export default {
displayName: 'api-gateway',
preset: '../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../coverage/api-gateway',
};

26
api-gateway/project.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "api-gateway",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "api-gateway/src",
"projectType": "application",
"tags": [],
"targets": {
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"dependsOn": ["build"],
"options": {
"buildTarget": "api-gateway:build",
"runBuildTargetDependencies": false
},
"configurations": {
"development": {
"buildTarget": "api-gateway:build:development"
},
"production": {
"buildTarget": "api-gateway:build:production"
}
}
}
}
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AdvertisementController } from './advertisement.controller';
describe('AdvertisementController', () => {
let controller: AdvertisementController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AdvertisementController],
}).compile();
controller = module.get<AdvertisementController>(AdvertisementController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,12 @@
import { Controller } from '@nestjs/common';
import { Client, ClientProxy, Transport } from '@nestjs/microservices';
@Controller('advertisement')
export class AdvertisementController {
@Client({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 4006 } })
private advertismentService: ClientProxy;
}

View File

@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let app: TestingModule;
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
});
describe('getData', () => {
it('should return "Hello API"', () => {
const appController = app.get<AppController>(AppController);
expect(appController.getData()).toEqual({ message: 'Hello API' });
});
});
});

View File

@ -0,0 +1,10 @@
import { Controller, All, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';
import { ClientProxyFactory, Transport, ClientProxy } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';
@Controller()
export class ApiGatewayController {
}

View File

@ -0,0 +1,55 @@
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { ApiGatewayController } from './app.controller';
import { CatalogController } from './catalog/catalog.controller';
import { UserController } from './user/user.controller';
import { VendorController } from './vendor/vendor.controller';
import { OrderController } from './order/order.controller';
import { AdvertisementController } from './advertisement/advertisement.controller';
import { DiscountController } from './discount/discount.controller';
@Module({
imports: [
ClientsModule.register([
{
name: 'USER_SERVICE',
transport: Transport.TCP,
options: { host: 'localhost', port: 4001 },
},
{
name: 'VENDOR_SERVICE',
transport: Transport.TCP,
options: { host: 'localhost', port: 4002 },
},
{
name: 'ORDER_SERVICE',
transport: Transport.TCP,
options: { host: 'localhost', port: 4003 },
},
{
name: 'DISCOUNT_SERVICE',
transport: Transport.TCP,
options: { host: 'localhost', port: 4004 },
},
{
name: 'CATALOG_SERVICE',
transport: Transport.TCP,
options: { host: 'localhost', port: 4005 },
},
{
name: 'ADVERTISEMENT_SERVICE',
transport: Transport.TCP,
options: { host: 'localhost', port: 4006 },
},
]),
],
controllers: [
ApiGatewayController,
CatalogController,
UserController,
VendorController,
OrderController,
AdvertisementController,
DiscountController,
],
})
export class AppModule {}

View File

@ -0,0 +1,21 @@
import { Test } from '@nestjs/testing';
import { AppService } from './app.service';
describe('AppService', () => {
let service: AppService;
beforeAll(async () => {
const app = await Test.createTestingModule({
providers: [AppService],
}).compile();
service = app.get<AppService>(AppService);
});
describe('getData', () => {
it('should return "Hello API"', () => {
expect(service.getData()).toEqual({ message: 'Hello API' });
});
});
});

View File

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getData(): { message: string } {
return { message: 'Hello API' };
}
}

View File

@ -0,0 +1,27 @@
import { Controller, Post, Body, Inject, Query, Headers } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
interface CreateLanguageDto {
lang?: string; // زبان (اختیاری)
// سایر فیلدها
}
@Controller('catalog')
export class CatalogController {
constructor(@Inject('CATALOG_SERVICE') private readonly catalogService: ClientProxy) {}
@Post('/language/create')
async getCatalog(
@Body() body: CreateLanguageDto,
@Query('lang') queryLang: string,
@Headers('accept-language') acceptLanguage: string,
) {
// اولویت‌بندی: body > query > header > پیش‌فرض
const lang = body.lang || queryLang || acceptLanguage?.split(',')[0] || 'en';
// ارسال پیام به catalog-service
return this.catalogService
.send({ cmd: 'POST-language/create' }, { body, lang })
.toPromise();
}
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DiscountController } from './discount.controller';
describe('DiscountController', () => {
let controller: DiscountController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [DiscountController],
}).compile();
controller = module.get<DiscountController>(DiscountController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,10 @@
import { Controller } from '@nestjs/common';
import { Client, ClientProxy, Transport } from '@nestjs/microservices';
@Controller('discount')
export class DiscountController {
@Client({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 4004 } })
private discountService: ClientProxy;
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { OrderController } from './order.controller';
describe('OrderController', () => {
let controller: OrderController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [OrderController],
}).compile();
controller = module.get<OrderController>(OrderController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,10 @@
import { Controller } from '@nestjs/common';
import { Client, ClientProxy, Transport } from '@nestjs/microservices';
@Controller('order')
export class OrderController {
@Client({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 4003 } })
private orderService: ClientProxy;
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserController } from './user.controller';
describe('UserController', () => {
let controller: UserController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
}).compile();
controller = module.get<UserController>(UserController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,12 @@
import { Controller } from '@nestjs/common';
import { Client, ClientProxy, Transport } from '@nestjs/microservices';
@Controller('user')
export class UserController {
@Client({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 4001 } })
private userService: ClientProxy;
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { VendorController } from './vendor.controller';
describe('VendorController', () => {
let controller: VendorController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [VendorController],
}).compile();
controller = module.get<VendorController>(VendorController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,10 @@
import { Controller } from '@nestjs/common';
import { Client, ClientProxy, Transport } from '@nestjs/microservices';
@Controller('vendor')
export class VendorController {
@Client({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 4002 } })
private vendorService: ClientProxy;
}

View File

19
api-gateway/src/main.ts Normal file
View File

@ -0,0 +1,19 @@
/**
* This is not a production server yet!
* This is only a minimal backend to get started.
*/
import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
console.log('API Gateway is running on port 3000');
}
bootstrap();

View File

@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"module": "commonjs",
"types": ["node"],
"emitDecoratorMetadata": true,
"target": "es2021"
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

16
api-gateway/tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"extends": "../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,20 @@
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, '../dist/api-gateway'),
},
plugins: [
new NxAppWebpackPlugin({
target: 'node',
compiler: 'tsc',
main: './src/main.ts',
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
optimization: false,
outputHashing: 'none',
generatePackageJson: true,
}),
],
};

View File

@ -0,0 +1,3 @@
const baseConfig = require('../eslint.config.js');
module.exports = [...baseConfig];

View File

@ -0,0 +1,10 @@
export default {
displayName: 'catalog-service',
preset: '../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../coverage/catalog-service',
};

View File

@ -0,0 +1,29 @@
{
"name": "catalog-service",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "catalog-service/src",
"projectType": "application",
"tags": [],
"targets": {
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"dependsOn": [
"build"
],
"options": {
"buildTarget": "catalog-service:build",
"runBuildTargetDependencies": false
},
"configurations": {
"development": {
"buildTarget": "catalog-service:build:development"
},
"production": {
"buildTarget": "catalog-service:build:production"
}
}
}
}
}

View File

@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let app: TestingModule;
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
});
describe('getData', () => {
it('should return "Hello API"', () => {
const appController = app.get<AppController>(AppController);
expect(appController.getData()).toEqual({ message: 'Hello API' });
});
});
});

View File

@ -0,0 +1,22 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { MessagePattern } from '@nestjs/microservices';
import { CreateLanguageDto } from './language/dto/language.dto';
import { LanguageService } from './language/language.service';
@Controller()
export class AppController {
constructor(private readonly languageService: LanguageService) { }
@MessagePattern({ cmd: 'get-catalog' })
getCatalog() {
return [
{ id: 1, name: 'Laptop' },
{ id: 2, name: 'Phone' },
];
}
}

View File

@ -0,0 +1,35 @@
import { Module } from '@nestjs/common';
import { I18nJsonLoader, I18nModule } from 'nestjs-i18n';
import * as path from 'path';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { UploadModule } from './upload/upload.module';
import { ShopCategoryModule } from './shop-category/shop-category.module';
import { LanguageModule } from './language/language.module';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LanguageController } from './language/language.controller';
import { I18nextModule } from './i18next/i18next.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
uri: configService.get<string>('MONGODB_URI'),
}),
inject: [ConfigService],
}),
UploadModule,
ShopCategoryModule,
LanguageModule,
I18nextModule,
],
controllers: [AppController, LanguageController],
providers: [AppService],
})
export class AppModule {}

View File

@ -0,0 +1,21 @@
import { Test } from '@nestjs/testing';
import { AppService } from './app.service';
describe('AppService', () => {
let service: AppService;
beforeAll(async () => {
const app = await Test.createTestingModule({
providers: [AppService],
}).compile();
service = app.get<AppService>(AppService);
});
describe('getData', () => {
it('should return "Hello API"', () => {
expect(service.getData()).toEqual({ message: 'Hello API' });
});
});
});

View File

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getData(): { message: string } {
return { message: 'Hello API' };
}
}

View File

@ -0,0 +1,27 @@
import { Module } from '@nestjs/common';
import * as i18next from 'i18next';
import Backend from 'i18next-fs-backend';
import { join } from 'path';
@Module({
providers: [
{
provide: 'I18NEXT',
useFactory: async () => {
await i18next.use(Backend).init({
lng: 'en', // زبان پیش‌فرض
fallbackLng: 'en', // زبان جایگزین
backend: {
loadPath: join(__dirname, './locales/{{lng}}/{{ns}}.json'), // مسیر فایل‌های ترجمه
},
ns: ['language', 'validation','translation'], // فضاهای نام
defaultNS: 'translation', // فضای نام پیش‌فرض
debug: true, // برای دیباگ
});
return i18next;
},
},
],
exports: ['I18NEXT'],
})
export class I18nextModule {}

View File

@ -0,0 +1,44 @@
import { IsString, IsNotEmpty, IsOptional, IsEnum, IsUUID } from 'class-validator';
export enum LanguageDirection {
LTR = 'ltr',
RTL = 'rtl',
}
export class CreateLanguageDto {
@IsString()
@IsNotEmpty()
Name: string;
@IsString()
@IsOptional()
Symbol?: string;
@IsString()
@IsOptional()
Icon?: string;
@IsEnum(LanguageDirection)
@IsOptional()
Direction?: LanguageDirection;
}
export class UpdateLanguageDto {
@IsString()
@IsOptional()
Name?: string;
@IsString()
@IsOptional()
Symbol?: string;
@IsString()
@IsOptional()
Icon?: string;
@IsEnum(LanguageDirection)
@IsOptional()
Direction?: LanguageDirection;
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LanguageController } from './language.controller';
describe('LanguageController', () => {
let controller: LanguageController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [LanguageController],
}).compile();
controller = module.get<LanguageController>(LanguageController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,26 @@
import { Controller, Get, Post, Put, Delete, Body, Param, HttpStatus } from '@nestjs/common';
import { LanguageService } from './language.service';
import { CreateLanguageDto, UpdateLanguageDto } from 'user-service/src/app/language/dto/language.dto';
import { Language } from 'user-service/src/schemas/language.schema';
import { I18n, I18nContext, I18nService } from 'nestjs-i18n';
import { MessagePattern, Payload } from '@nestjs/microservices';
@Controller()
export class LanguageController {
constructor(private readonly languageService: LanguageService,
) { }
@MessagePattern({cmd:'POST-language/create'})
async create(payload: any): Promise<any> {
console.log("---------------------",payload)
const createLanguageDto: CreateLanguageDto = payload.body;
const lang = payload.body.lang || 'en'
return this.languageService.create(createLanguageDto,lang);
}
}

View File

@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { LanguageController } from './language.controller';
import { LanguageService } from './language.service';
import { MongooseModule } from '@nestjs/mongoose';
import { Language,LanguageSchema } from 'user-service/src/schemas/language.schema';
import { I18nModule } from 'nestjs-i18n';
import { I18nextModule } from '../i18next/i18next.module';
@Module({
imports: [ MongooseModule.forFeature([{ name: Language.name, schema: LanguageSchema }]),I18nextModule],
controllers: [LanguageController],
providers: [LanguageService],
exports:[LanguageService]
})
export class LanguageModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LanguageService } from './language.service';
describe('LanguageService', () => {
let service: LanguageService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LanguageService],
}).compile();
service = module.get<LanguageService>(LanguageService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,66 @@
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Language } from 'user-service/src/schemas/language.schema';
import { CreateLanguageDto, UpdateLanguageDto } from 'user-service/src/app/language/dto/language.dto';
import { v4 as uuidv4 } from 'uuid';
import { I18nContext, I18nService } from 'nestjs-i18n';
import i18next from 'i18next';
@Injectable()
export class LanguageService {
constructor(@InjectModel(Language.name) private languageModel: Model<Language>,
@Inject('I18NEXT') private readonly i18n: typeof i18next) {}
async create(
createLanguageDto: CreateLanguageDto,lang:string): Promise<any> {
const createdLanguage = new this.languageModel({
...createLanguageDto,
ID: uuidv4(),
Status: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const result = await createdLanguage.save();
console.log("language -----------------", await this.i18n.t('created', { lng: lang ,ns: 'language' }))
return {
success: true,
message: await this.i18n.t('created', { lng:'fa' }),
data: result,
};
}
async findAll(): Promise<Language[]> {
return this.languageModel.find().exec();
}
async findOne(id: string): Promise<Language> {
const language = await this.languageModel.findOne({ ID: id }).exec();
if (!language) {
throw new NotFoundException(`Language with ID ${id} not found`);
}
return language;
}
async update(id: string, updateLanguageDto: UpdateLanguageDto): Promise<Language> {
const language = await this.languageModel
.findOneAndUpdate(
{ ID: id },
{ ...updateLanguageDto, updatedAt: new Date() },
{ new: true },
)
.exec();
if (!language) {
throw new NotFoundException(`Language with ID ${id} not found`);
}
return language;
}
async remove(id: string): Promise<void> {
const result = await this.languageModel.deleteOne({ ID: id }).exec();
if (result.deletedCount === 0) {
throw new NotFoundException(`Language with ID ${id} not found`);
}
}
}

View File

@ -0,0 +1,30 @@
import { IsString, IsOptional, IsNumber, IsNotEmpty } from 'class-validator';
import { Types } from 'mongoose';
import { i18nValidationMessage } from 'nestjs-i18n';
import { IsMongoId } from 'class-validator';
export class CreateShopCategoryDto {
@IsString({ message: i18nValidationMessage('validation.isString') })
@IsOptional()
Name?: string;
@IsString({ message: i18nValidationMessage('validation.isString') })
@IsOptional()
Description?: string;
@IsString({ message: i18nValidationMessage('validation.isString') })
@IsOptional()
Icon?: string;
@IsNumber({}, { message: i18nValidationMessage('validation.isNumber') })
@IsOptional()
Level?: number;
@IsMongoId({ message: i18nValidationMessage('validation.isMongoId') })
@IsOptional()
ParentCategory?: Types.ObjectId;
@IsMongoId({ message: i18nValidationMessage('validation.isMongoId') })
@IsOptional()
Language?: Types.ObjectId;
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ShopCategoryController } from './shop-category.controller';
describe('ShopCategoryController', () => {
let controller: ShopCategoryController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ShopCategoryController],
}).compile();
controller = module.get<ShopCategoryController>(ShopCategoryController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,4 @@
import { Controller } from '@nestjs/common';
@Controller('shop-category')
export class ShopCategoryController {}

View File

@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { ShopCategoryController } from './shop-category.controller';
import { ShopCategoryService } from './shop-category.service';
import { MongooseModule } from '@nestjs/mongoose';
import { ShopCategory, ShopCategorySchema } from 'catalog-service/src/schemas/shopCategory.schema';
@Module({
imports: [
MongooseModule.forFeature([{ name: ShopCategory.name, schema: ShopCategorySchema }]),
],
controllers: [ShopCategoryController],
providers: [ShopCategoryService],
})
export class ShopCategoryModule { }

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ShopCategoryService } from './shop-category.service';
describe('ShopCategoryService', () => {
let service: ShopCategoryService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ShopCategoryService],
}).compile();
service = module.get<ShopCategoryService>(ShopCategoryService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,47 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { ShopCategory,ShopCategoryDoc } from '../../schemas/shopCategory.schema';
import { CreateShopCategoryDto } from './dto/create-shop-category.dto';
import { I18n, I18nContext } from 'nestjs-i18n';
@Injectable()
export class ShopCategoryService {
constructor(
@InjectModel(ShopCategory.name) private shopCategoryModel: Model<ShopCategoryDoc>,
) {}
async create(createShopCategoryDto: CreateShopCategoryDto): Promise<any> {
}
async findAll(): Promise<ShopCategory[]> {
return this.shopCategoryModel.find()
.populate('Tags')
.populate('ParentCategory')
.populate('Document')
.exec();
}
async findOne(id: string): Promise<ShopCategory> {
const category = await this.shopCategoryModel.findOne({ ID: id })
.populate('Tags')
.populate('ParentCategory')
.populate('Document')
.exec();
if (!category) {
throw new NotFoundException(`ShopCategory with ID ${id} not found`);
}
return category;
}
async remove(id: string): Promise<void> {
const result = await this.shopCategoryModel.deleteOne({ ID: id }).exec();
if (result.deletedCount === 0) {
throw new NotFoundException(`ShopCategory with ID ${id} not found`);
}
}
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UploadController } from './upload.controller';
describe('UploadController', () => {
let controller: UploadController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UploadController],
}).compile();
controller = module.get<UploadController>(UploadController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,28 @@
import { Body, Post, Controller, Res, UseInterceptors, UploadedFile } from '@nestjs/common';
import { UploadService } from './upload.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { Response } from 'express';
interface MulterFile {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
size: number;
destination?: string;
filename?: string;
path?: string;
buffer?: Buffer;
}
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post()
@UseInterceptors(FileInterceptor('file'))
async uploadfiles(@UploadedFile() file: MulterFile, @Res() res: Response) {
const result = await this.uploadService.uploadFileSingle(file);
return res.status(result.status).json(result);
}
}

View File

@ -0,0 +1,19 @@
import { Module } from '@nestjs/common';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import { FileSchema } from 'catalog-service/src/schemas/file.schema';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule } from '@nestjs/config';
import { ConfigService } from '@nestjs/config';
@Module({
imports:[
ConfigModule,
MongooseModule.forFeature([
{ name: 'File', schema: FileSchema },
])],
providers: [UploadService,ConfigService],
controllers: [UploadController],
exports: [UploadService],
})
export class UploadModule {}

View File

@ -0,0 +1,18 @@
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();
});
});

View File

@ -0,0 +1,115 @@
// src/s3/s3.service.ts
import { Injectable } from '@nestjs/common';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { ConfigService } from '@nestjs/config';
import { v4 as uuidv4 } from 'uuid';
import { extname } from 'path';
import { File, FileDocument } from 'catalog-service/src/schemas/file.schema'
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose'
import { Express } from 'express';
interface CreateSellerResponse {
message: string;
status: number;
data: any;
}
@Injectable()
export class UploadService {
private s3: S3Client;
private bucket: string;
private region: string;
private endpoint: string;
constructor(private configService: ConfigService,
@InjectModel(File.name) private fileModel: Model<FileDocument>,
) {
const accessKeyId = configService.get<string>('LIARA_ACCESS_KEY');
const secretAccessKey = configService.get<string>('LIARA_SECRET_KEY');
const region = configService.get<string>('LIARA_REGION') || 'default';
const endpoint = configService.get<string>('LIARA_ENDPOINT');
const bucket = configService.get<string>('LIARA_BUCKET_NAME');
if (!accessKeyId || !secretAccessKey || !bucket || !endpoint) {
throw new Error('Missing Liara S3 configuration');
}
this.bucket = bucket;
this.region = region;
this.endpoint = endpoint;
this.s3 = new S3Client({
region: this.region,
endpoint: this.endpoint,
forcePathStyle: true,
credentials: {
accessKeyId,
secretAccessKey,
},
});
}
async uploadFileSingle(file: any): Promise<CreateSellerResponse> {
const key: string = uuidv4();
const extension = extname(file.originalname);
const urlFile = `${this.endpoint}/${this.bucket}/${key}${extension}`;
const keyWithExt = `${key}${extension}`;
const command = new PutObjectCommand({
Bucket: this.bucket,
Key: keyWithExt,
Body: file.buffer,
ContentType: file.mimetype,
});
const dataupload = await this.s3.send(command);
const mimeType = file.mimetype;
console.log("dataupload", dataupload);
let fileType: 'image' | 'video' | 'document' | 'other' = 'other';
if (mimeType.startsWith('image/')) {
fileType = 'image';
} else if (mimeType.startsWith('video/')) {
fileType = 'video';
} else if (
mimeType === 'application/pdf' ||
mimeType === 'application/msword' ||
mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
) {
fileType = 'document';
}
try {
const newFile = new this.fileModel({
FileType: fileType,
Title: "avatar",
Description: "avatar",
Format: mimeType,
Url: urlFile,
ID: key
});
const savedSeller = await newFile.save();
return {
message: "فایل با موفقیت آپلود شد.",
status: 200,
data: {
ID: key,
Url: urlFile,
_id: newFile._id
}
};
} catch (e) {
return {
message: "آپلود فایل با شکست مواجه شد",
status: 502,
data: null
};
}
}
}

View File

View File

@ -0,0 +1,3 @@
{
"created": "Language created successfully"
}

View File

@ -0,0 +1,7 @@
{
"isString": "The field $property must be a string",
"isNumber": "The field $property must be a number",
"isMongoId": "The field $property must be a valid MongoDB ObjectId",
"isNotEmpty": "The field $property should not be empty",
"isEnum": "The field $property must be one of the allowed values"
}

View File

@ -0,0 +1,3 @@
{
"created": "زبان با موفقیت ایجاد شد"
}

View File

@ -0,0 +1,7 @@
{
"isString": "فیلد $property باید رشته باشد",
"isNumber": "فیلد $property باید یک عدد باشد",
"isMongoId": "فیلد $property باید یک شناسه معتبر مونگو باشد",
"isNotEmpty": "فیلد $property نباید خالی باشد",
"isEnum": "فیلد $property باید یکی از مقادیر مجاز باشد"
}

View File

@ -0,0 +1,16 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
transport: Transport.TCP,
options: {
host: 'localhost',
port: 4005, // پورت میکروسرویس
},
});
await app.listen();
console.log('Catalog Microservice is listening on port 4005');
}
bootstrap();

View File

@ -0,0 +1,34 @@
// src/schemas/album.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Types } from 'mongoose';
import { v4 as uuidv4 } from 'uuid';
export type AlbumDocument = Album & Document;
@Schema()
export class Album {
@Prop({ default: () => uuidv4(), unique: true })
ID: string; // UUID
@Prop()
Name: string;
@Prop()
Description: string;
@Prop({ type: Types.ObjectId, ref: 'Album', default: null })
Parent: Types.ObjectId;
@Prop({ type: Types.ObjectId, ref: 'User' })
Owner: Types.ObjectId;
@Prop({ default: 'active' })
Status: string;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
export const AlbumSchema = SchemaFactory.createForClass(Album);

View File

@ -0,0 +1,62 @@
// src/schemas/file.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
import { v4 as uuidv4 } from 'uuid';
export type FileDocument = File & Document;
/*
@Schema()
export class File {
@Prop({ default: () => uuidv4(), unique: true })
ID: string; // UUID
@Prop()
Title: string;
@Prop()
Description: string;
@Prop()
Format: string;
@Prop()
Url: string;
@Prop({ type: Types.ObjectId, ref: 'Album' })
Album: Types.ObjectId; // آلبوم مربوطه
@Prop({ default: 'active' })
Status: string;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
*/
@Schema({ timestamps: true })
export class File {
@Prop()
FileType: string;
@Prop({required:false})
Title:string
@Prop({required:false})
Description:string
@Prop({required:false})
Format:string
@Prop({required:false})
Url:string
@Prop({required:true,unique:true})
ID:string
}
export const FileSchema = SchemaFactory.createForClass(File);

View File

@ -0,0 +1,41 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
@Schema()
export class ProductCategory {
@Prop({ required: true, unique: true })
ID: string;
@Prop({ type: Types.ObjectId, ref: 'ProductCategoryDocument' })
Document: Types.ObjectId;
@Prop()
Name: string;
@Prop()
Description: string;
@Prop()
Icon: string;
@Prop()
Level: number;
@Prop({ type: [{ type: Types.ObjectId, ref: 'Tag' }] })
Tags: Types.ObjectId[];
@Prop({ type: Types.ObjectId, ref: 'ProductCategory' })
ParentCategory: Types.ObjectId;
@Prop()
Status: string;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
export const ProductCategorySchema = SchemaFactory.createForClass(ProductCategory);
ProductCategorySchema.index({ ID: 1 });

View File

@ -0,0 +1,35 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
@Schema()
export class ProductCategoryDocument {
@Prop({ required: true, unique: true })
ID: string;
@Prop()
Name: string;
@Prop()
Description: string;
@Prop({ type: Types.ObjectId, ref: 'ProductCategory' })
ProductCategory: Types.ObjectId;
@Prop({ type: [{ type: Types.ObjectId, ref: 'Tag' }] })
Tags: Types.ObjectId[];
@Prop()
Status: string;
@Prop({ type: Types.ObjectId, ref: 'Language' })
Language: Types.ObjectId;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
export const ProductCategoryDocumentSchema = SchemaFactory.createForClass(ProductCategoryDocument);
ProductCategoryDocumentSchema.index({ID:1})

View File

@ -0,0 +1,39 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
@Schema()
export class Property {
@Prop({ required: true, unique: true })
ID: string;
@Prop({ type: Types.ObjectId, ref: 'PropertyDocument' })
Document: Types.ObjectId;
@Prop()
Name: string;
@Prop()
Description: string;
@Prop()
Data: string;
@Prop({ enum: ['user', 'system'] })
Type: string;
@Prop()
Status: string;
@Prop()
AdminStatus: string;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
export const PropertySchema = SchemaFactory.createForClass(Property);
PropertySchema.index({ ID: 1 })

View File

@ -0,0 +1,33 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
@Schema()
export class PropertyDocument {
@Prop({ required: true, unique: true })
ID: string;
@Prop()
Name: string;
@Prop()
Description: string;
@Prop({ type: Types.ObjectId, ref: 'Property' })
Property: Types.ObjectId;
@Prop()
Data: string;
@Prop({ type: Types.ObjectId, ref: 'Language' })
Language: Types.ObjectId;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
export const PropertyDocumentSchema = SchemaFactory.createForClass(PropertyDocument);
PropertyDocumentSchema.index({ ID: 1 })

View File

@ -0,0 +1,32 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
@Schema()
export class Region {
@Prop({ required: true, unique: true })
ID: string;
@Prop()
Name: string;
@Prop()
Description: string;
@Prop({ type: { type: String, enum: ['Polygon'] }, coordinates: [[[Number]]] })
Location: { type: 'Polygon', coordinates: number[][][] };
@Prop({ type: Types.ObjectId, ref: 'RegionDocument' })
Document: Types.ObjectId;
@Prop()
Status: string;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
export const RegionSchema = SchemaFactory.createForClass(Region);
RegionSchema.index({ ID: 1 })

View File

@ -0,0 +1,33 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
@Schema()
export class RegionDocument {
@Prop({ required: true, unique: true })
ID: string;
@Prop()
Name: string;
@Prop()
Description: string;
@Prop({ type: Types.ObjectId, ref: 'Region' })
Region: Types.ObjectId;
@Prop()
Status: string;
@Prop({ type: Types.ObjectId, ref: 'Language' })
Language: Types.ObjectId;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
export const RegionDocumentSchema = SchemaFactory.createForClass(RegionDocument);
RegionDocumentSchema.index({ ID: 1 })

View File

@ -0,0 +1,43 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
export type ShopCategoryDoc = HydratedDocument<ShopCategory>;
@Schema()
export class ShopCategory {
@Prop({ required: true, unique: true })
ID: string;
@Prop()
Name: string;
@Prop()
Description: string;
@Prop()
Icon: string;
@Prop()
Level: number;
@Prop({ type: [{ type: Types.ObjectId, ref: 'Tag' }] })
Tags: Types.ObjectId[];
@Prop({ type: Types.ObjectId, ref: 'ShopCategory' })
ParentCategory: Types.ObjectId;
@Prop({ type: Types.ObjectId, ref: 'ShopCategoryDocument' })
Document: Types.ObjectId;
@Prop()
Status: string;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
export const ShopCategorySchema = SchemaFactory.createForClass(ShopCategory);
ShopCategorySchema.index({ ID: 1 });

View File

@ -0,0 +1,36 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
@Schema()
export class ShopCategoryDocument {
@Prop({ required: true, unique: true })
ID: string;
@Prop()
Name: string;
@Prop()
Description: string;
@Prop({ type: Types.ObjectId, ref: 'ShopCategory' })
ShopCategory: Types.ObjectId;
@Prop({ type: [{ type: Types.ObjectId, ref: 'Tag' }] })
Tags: Types.ObjectId[];
@Prop()
Status: string;
@Prop({ type: Types.ObjectId, ref: 'Language' })
Language: Types.ObjectId;
@Prop({ type: Date, default: Date.now })
createdAt: Date;
@Prop({ type: Date, default: Date.now })
updatedAt: Date;
}
export const ShopCategoryDocumentSchema = SchemaFactory.createForClass(ShopCategoryDocument);
ShopCategoryDocumentSchema.index({ID:1})

View File

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"module": "commonjs",
"types": ["node"],
"emitDecoratorMetadata": true,
"target": "es2021",
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,20 @@
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, '../dist/catalog-service'),
},
plugins: [
new NxAppWebpackPlugin({
target: 'node',
compiler: 'tsc',
main: './src/main.ts',
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
optimization: false,
outputHashing: 'none',
generatePackageJson: true,
}),
],
};

View File

@ -0,0 +1,3 @@
const baseConfig = require('../eslint.config.js');
module.exports = [...baseConfig];

View File

@ -0,0 +1,10 @@
export default {
displayName: 'discount-service',
preset: '../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../coverage/discount-service',
};

View File

@ -0,0 +1,26 @@
{
"name": "discount-service",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "discount-service/src",
"projectType": "application",
"tags": [],
"targets": {
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"dependsOn": ["build"],
"options": {
"buildTarget": "discount-service:build",
"runBuildTargetDependencies": false
},
"configurations": {
"development": {
"buildTarget": "discount-service:build:development"
},
"production": {
"buildTarget": "discount-service:build:production"
}
}
}
}
}

View File

@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let app: TestingModule;
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
});
describe('getData', () => {
it('should return "Hello API"', () => {
const appController = app.get<AppController>(AppController);
expect(appController.getData()).toEqual({ message: 'Hello API' });
});
});
});

View File

@ -0,0 +1,13 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getData() {
return this.appService.getData();
}
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@ -0,0 +1,21 @@
import { Test } from '@nestjs/testing';
import { AppService } from './app.service';
describe('AppService', () => {
let service: AppService;
beforeAll(async () => {
const app = await Test.createTestingModule({
providers: [AppService],
}).compile();
service = app.get<AppService>(AppService);
});
describe('getData', () => {
it('should return "Hello API"', () => {
expect(service.getData()).toEqual({ message: 'Hello API' });
});
});
});

View File

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getData(): { message: string } {
return { message: 'Hello API' };
}
}

Some files were not shown because too many files have changed in this diff Show More