Basic Architecture of Sport Web

This commit is contained in:
Nông Đức Huy
2026-08-11 13:37:25 +07:00
commit 8032fff6ac
262 changed files with 20348 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript';
import importX from 'eslint-plugin-import-x';
import turbo from 'eslint-plugin-turbo';
import globals from 'globals';
import tseslint from 'typescript-eslint';
/**
* Base flat config: TypeScript rules + import hygiene + the architectural
* boundary rules that every package in the workspace must obey.
*
* @type {import("eslint").Linter.Config[]}
*/
export const baseConfig = [
{
ignores: [
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
'**/coverage/**',
'**/node_modules/**',
'**/generated/**',
],
},
js.configs.recommended,
...tseslint.configs.recommended,
importX.flatConfigs.recommended,
importX.flatConfigs.typescript,
turbo.configs['flat/recommended'],
{
languageOptions: {
ecmaVersion: 2023,
globals: { ...globals.node, ...globals.es2021 },
},
settings: {
// TypeScript resolver first: it understands `.ts` extensionless imports,
// path aliases and workspace `exports` maps. The node resolver is the
// fallback for plain JS config files.
'import-x/resolver-next': [
createTypeScriptImportResolver({ alwaysTryTypes: true, project: ['*/tsconfig.json'] }),
importX.createNodeResolver(),
],
},
rules: {
// --- Type safety -------------------------------------------------
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
],
// `import type` statements stay with their source group rather than being
// herded to the bottom of the file, where they lose the context of what
// they belong to.
'@typescript-eslint/no-import-type-side-effects': 'error',
'@typescript-eslint/no-non-null-assertion': 'warn',
// --- Import hygiene ----------------------------------------------
'import-x/no-default-export': 'error',
'import-x/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
pathGroups: [{ pattern: '@sport/**', group: 'internal', position: 'before' }],
pathGroupsExcludedImportTypes: ['type'],
'newlines-between': 'always',
alphabetize: { order: 'asc', caseInsensitive: true },
},
],
'import-x/no-cycle': ['error', { maxDepth: 4 }],
// --- General ------------------------------------------------------
eqeqeq: ['error', 'smart'],
'no-console': ['error', { allow: ['warn', 'error'] }],
'prefer-const': 'error',
'object-shorthand': 'error',
},
},
{
// Config files and scripts are allowed default exports and console output.
files: ['**/*.config.{js,mjs,ts}', '**/scripts/**', '**/*.cjs'],
rules: {
'import-x/no-default-export': 'off',
'no-console': 'off',
},
},
{
files: ['**/*.{test,spec}.{ts,tsx}', '**/test/**'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'no-console': 'off',
},
},
prettier,
];
export default baseConfig;
+59
View File
@@ -0,0 +1,59 @@
import { baseConfig } from './base.js';
/**
* NestJS config.
*
* The two rules that matter architecturally:
* 1. Only the persistence layer may import PrismaService / Redis directly.
* 2. A module must not deep-import another module's internals — cross-module
* access goes through the other module's public surface (`<module>/public`).
*
* @type {import("eslint").Linter.Config[]}
*/
export const nestConfig = [
...baseConfig,
{
files: ['**/*.ts'],
rules: {
/**
* OFF, and it must stay off.
*
* Nest resolves constructor dependencies from `emitDecoratorMetadata`,
* which requires the parameter's type to survive as a *value* import.
* This rule cannot tell an injected class from a pure type, so its
* autofix silently rewrites `import { PrismaService }` into
* `import { type PrismaService }` — which compiles cleanly and then
* fails at runtime with "Nest can't resolve dependencies".
*
* The same reasoning is why `verbatimModuleSyntax` is disabled in
* @sport/config/typescript/nestjs.json.
*/
'@typescript-eslint/consistent-type-imports': 'off',
// Nest relies on parameter decorators and class-based DI.
'@typescript-eslint/no-extraneous-class': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'no-console': 'error',
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@/modules/*/*', '!@/modules/*/public'],
message:
'Cross-module deep imports are forbidden. Import from `@/modules/<name>/public` instead.',
},
],
},
],
},
},
{
// Decorated DTO / entity classes legitimately have empty bodies.
files: ['**/*.dto.ts', '**/*.entity.ts'],
rules: { '@typescript-eslint/no-extraneous-class': 'off' },
},
];
export default nestConfig;
+56
View File
@@ -0,0 +1,56 @@
import { reactConfig } from './react.js';
/**
* Next.js App Router config.
*
* Note: the App Router *requires* default exports for `page`/`layout`/`route`
* and friends, so the workspace-wide `no-default-export` rule is relaxed for
* those files only — everywhere else named exports remain mandatory.
*
* @type {import("eslint").Linter.Config[]}
*/
export const nextConfig = [
...reactConfig,
{
files: [
'src/app/**/{page,layout,template,loading,error,not-found,default,route,global-error,sitemap,robots,opengraph-image,icon,apple-icon,manifest}.{ts,tsx}',
'src/middleware.ts',
'next.config.{ts,mjs,js}',
'instrumentation.ts',
],
rules: {
'import-x/no-default-export': 'off',
},
},
{
files: ['src/**/*.{ts,tsx}'],
rules: {
// The storefront and admin must reach the backend only through the
// generated API client — never with ad-hoc fetch calls or a DB driver.
'no-restricted-imports': [
'error',
{
paths: [
{
name: '@prisma/client',
message:
'Frontend apps must never talk to the database. Use @sport/api-client instead.',
},
{
name: 'ioredis',
message: 'Frontend apps must never talk to Redis. Use @sport/api-client instead.',
},
],
patterns: [
{
group: ['@sport/api/*', '@sport/api'],
message: 'Frontend apps must not import backend code. Use @sport/api-client.',
},
],
},
],
},
},
];
export default nextConfig;
+33
View File
@@ -0,0 +1,33 @@
{
"name": "@sport/eslint-config",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "Shared ESLint flat configs for the workspace.",
"exports": {
"./base": "./base.js",
"./react": "./react.js",
"./next": "./next.js",
"./nest": "./nest.js"
},
"scripts": {
"lint": "echo 'no lint target'",
"typecheck": "echo 'no typecheck target'"
},
"dependencies": {
"@eslint/js": "^9.39.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-turbo": "^2.10.9",
"globals": "^16.5.0",
"typescript-eslint": "^8.67.0"
},
"devDependencies": {
"eslint": "catalog:",
"typescript": "catalog:"
}
}
+34
View File
@@ -0,0 +1,34 @@
import globals from 'globals';
import a11y from 'eslint-plugin-jsx-a11y';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import { baseConfig } from './base.js';
/** @type {import("eslint").Linter.Config[]} */
export const reactConfig = [
...baseConfig,
{
files: ['**/*.{ts,tsx}'],
...react.configs.flat.recommended,
languageOptions: {
...react.configs.flat.recommended.languageOptions,
globals: { ...globals.browser, ...globals.serviceworker },
},
settings: { react: { version: 'detect' } },
},
{
files: ['**/*.{ts,tsx}'],
plugins: { 'react-hooks': reactHooks, 'jsx-a11y': a11y },
rules: {
...reactHooks.configs.recommended.rules,
...a11y.flatConfigs.recommended.rules,
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
'react/self-closing-comp': 'error',
},
},
];
export default reactConfig;