feat: 初始化项目
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
POSTGRES_USER=atlas_app_login
|
||||
POSTGRES_PASSWORD=change-me-local-only
|
||||
POSTGRES_DB=atlas
|
||||
POSTGRES_PORT=5432
|
||||
REDIS_URL=redis://localhost:6379
|
||||
MINIO_ROOT_USER=atlas_minio_local
|
||||
MINIO_ROOT_PASSWORD=change-me-local-only
|
||||
MINIO_PORT=9000
|
||||
MINIO_CONSOLE_PORT=9001
|
||||
API_PORT=8200
|
||||
WEB_PORT=8100
|
||||
+11
@@ -15,3 +15,14 @@ dist/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.log
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
.turbo/
|
||||
dist/
|
||||
coverage/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.log
|
||||
.DS_Store
|
||||
data/
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
.turbo/
|
||||
dist/
|
||||
apps/extension/.output/
|
||||
apps/extension/.wxt/
|
||||
docs/**
|
||||
!docs/architecture/project-structure.md
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@atlas/api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"dev": "tsx watch src/main.ts",
|
||||
"start": "node dist/main.js",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlas/contracts": "workspace:*",
|
||||
"@nestjs/common": "12.0.1",
|
||||
"@nestjs/core": "12.0.1",
|
||||
"@nestjs/platform-express": "12.0.1",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rxjs": "7.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "26.3.0",
|
||||
"tsx": "4.23.13",
|
||||
"typescript": "7.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get, Module } from '@nestjs/common';
|
||||
|
||||
@Controller('health')
|
||||
class HealthController {
|
||||
@Get()
|
||||
check() {
|
||||
return { status: 'ok', service: 'api' };
|
||||
}
|
||||
}
|
||||
|
||||
@Module({ controllers: [HealthController] })
|
||||
export class AppModule {}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'reflect-metadata';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
|
||||
import { AppModule } from './app.module.js';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.enableCors({ origin: process.env.WEB_ORIGIN ?? 'http://localhost:8100' });
|
||||
await app.listen(Number(process.env.API_PORT ?? 8200));
|
||||
}
|
||||
|
||||
void bootstrap();
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"types": ["node"],
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Atlas Seller Kit Collector",
|
||||
"description": "将已确认的来源商品采集到 Atlas 工作空间。",
|
||||
"version": "0.1.0",
|
||||
"permissions": [
|
||||
"storage",
|
||||
"identity",
|
||||
"tabs",
|
||||
"scripting"
|
||||
],
|
||||
"host_permissions": [
|
||||
"*://*.ozon.ru/*",
|
||||
"*://*.ozon.kz/*",
|
||||
"*://*.ozon.by/*",
|
||||
"*://*.1688.com/*",
|
||||
"*://*.taobao.com/*",
|
||||
"*://*.tmall.com/*",
|
||||
"http://localhost/*"
|
||||
],
|
||||
"commands": {
|
||||
"wxt:reload-extension": {
|
||||
"description": "Reload the extension during development",
|
||||
"suggested_key": {
|
||||
"default": "Alt+R"
|
||||
}
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval' http://localhost:3000; object-src 'self';",
|
||||
"sandbox": "script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:3000; sandbox allow-scripts allow-forms allow-popups allow-modals; child-src 'self';"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
var background=(function(){function e(e){return e==null||typeof e==`function`?{main:e}:e}var t=e(()=>{console.info(`Atlas collector background started.`)});globalThis.browser?.runtime?.id?globalThis.browser:globalThis.chrome;var n={debug:(...e)=>([...e],void 0),log:(...e)=>([...e],void 0),warn:(...e)=>([...e],void 0),error:(...e)=>([...e],void 0)},r;try{r=t.main(),r instanceof Promise&&console.warn(`The background's main() function return a promise, but it must be synchronous`)}catch(e){throw n.error(`The background crashed on startup!`),e}return r})();
|
||||
@@ -0,0 +1 @@
|
||||
{"manifest_version":3,"name":"Atlas Seller Kit Collector","description":"将已确认的来源商品采集到 Atlas 工作空间。","version":"0.1.0","permissions":["storage","identity"],"host_permissions":["*://*.ozon.ru/*","*://*.ozon.kz/*","*://*.ozon.by/*","*://*.1688.com/*","*://*.taobao.com/*","*://*.tmall.com/*"],"background":{"service_worker":"background.js"}}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"target": "ESNext",
|
||||
"module": "Preserve",
|
||||
"moduleDetection": "force",
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"paths": {
|
||||
"@": [
|
||||
".."
|
||||
],
|
||||
"@/*": [
|
||||
"../*"
|
||||
],
|
||||
"~": [
|
||||
".."
|
||||
],
|
||||
"~/*": [
|
||||
"../*"
|
||||
],
|
||||
"@@": [
|
||||
".."
|
||||
],
|
||||
"@@/*": [
|
||||
"../*"
|
||||
],
|
||||
"~~": [
|
||||
".."
|
||||
],
|
||||
"~~/*": [
|
||||
"../*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"../**/*",
|
||||
"./wxt.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../**/node_modules",
|
||||
"../.output"
|
||||
]
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Generated by wxt
|
||||
interface ImportMetaEnv {
|
||||
readonly MANIFEST_VERSION: 2 | 3;
|
||||
readonly BROWSER: string;
|
||||
readonly CHROME: boolean;
|
||||
readonly FIREFOX: boolean;
|
||||
readonly SAFARI: boolean;
|
||||
readonly EDGE: boolean;
|
||||
readonly OPERA: boolean;
|
||||
readonly COMMAND: "build" | "serve";
|
||||
readonly ENTRYPOINT: string;
|
||||
}
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
// Generated by wxt
|
||||
import "wxt/browser";
|
||||
|
||||
declare module "wxt/browser" {
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
|
||||
*/
|
||||
interface GetMessageOptions {
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
|
||||
*/
|
||||
escapeLt?: boolean
|
||||
}
|
||||
|
||||
export interface WxtI18n extends I18n.Static {
|
||||
/**
|
||||
* The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even non-localized extensions can use this message.
|
||||
* Note: You can't use this message in a manifest file.
|
||||
*
|
||||
* "<browser.runtime.id>"
|
||||
*/
|
||||
getMessage(
|
||||
messageName: "@@extension_id",
|
||||
substitutions?: string | string[],
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
/**
|
||||
* "<browser.i18n.getUiLocale()>"
|
||||
*/
|
||||
getMessage(
|
||||
messageName: "@@ui_locale",
|
||||
substitutions?: string | string[],
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
/**
|
||||
* The text direction for the current locale, either "ltr" for left-to-right languages such as English or "rtl" for right-to-left languages such as Japanese.
|
||||
*
|
||||
* "<ltr|rtl>"
|
||||
*/
|
||||
getMessage(
|
||||
messageName: "@@bidi_dir",
|
||||
substitutions?: string | string[],
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
/**
|
||||
* If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".
|
||||
*
|
||||
* "<rtl|ltr>"
|
||||
*/
|
||||
getMessage(
|
||||
messageName: "@@bidi_reversed_dir",
|
||||
substitutions?: string | string[],
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
/**
|
||||
* If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".
|
||||
*
|
||||
* "<left|right>"
|
||||
*/
|
||||
getMessage(
|
||||
messageName: "@@bidi_start_edge",
|
||||
substitutions?: string | string[],
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
/**
|
||||
* If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".
|
||||
*
|
||||
* "<right|left>"
|
||||
*/
|
||||
getMessage(
|
||||
messageName: "@@bidi_end_edge",
|
||||
substitutions?: string | string[],
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
getMessage(
|
||||
messageName: "@@extension_id" | "@@ui_locale" | "@@bidi_dir" | "@@bidi_reversed_dir" | "@@bidi_start_edge" | "@@bidi_end_edge",
|
||||
substitutions?: string | string[],
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Generated by wxt
|
||||
// Types for the #import virtual module
|
||||
declare module '#imports' {
|
||||
export { browser, Browser } from 'wxt/browser';
|
||||
export { storage, WxtStorage, WxtStorageItem, StorageArea, StorageItemKey, StorageAreaChanges, MigrationError } from 'wxt/utils/storage';
|
||||
export { getAppConfig, useAppConfig } from 'wxt/utils/app-config';
|
||||
export { ContentScriptContext, WxtWindowEventMap } from 'wxt/utils/content-script-context';
|
||||
export { createIframeUi, IframeContentScriptUi, IframeContentScriptUiOptions } from 'wxt/utils/content-script-ui/iframe';
|
||||
export { createIntegratedUi, IntegratedContentScriptUi, IntegratedContentScriptUiOptions } from 'wxt/utils/content-script-ui/integrated';
|
||||
export { createShadowRootUi, ShadowRootContentScriptUi, ShadowRootContentScriptUiOptions } from 'wxt/utils/content-script-ui/shadow-root';
|
||||
export { ContentScriptUi, ContentScriptUiOptions, ContentScriptOverlayAlignment, ContentScriptAppendMode, ContentScriptInlinePositioningOptions, ContentScriptOverlayPositioningOptions, ContentScriptModalPositioningOptions, ContentScriptPositioningOptions, ContentScriptAnchoredOptions, AutoMountOptions, StopAutoMount, AutoMount } from 'wxt/utils/content-script-ui/types';
|
||||
export { defineAppConfig, WxtAppConfig } from 'wxt/utils/define-app-config';
|
||||
export { defineBackground } from 'wxt/utils/define-background';
|
||||
export { defineContentScript } from 'wxt/utils/define-content-script';
|
||||
export { defineUnlistedScript } from 'wxt/utils/define-unlisted-script';
|
||||
export { defineWxtPlugin } from 'wxt/utils/define-wxt-plugin';
|
||||
export { injectScript, ScriptPublicPath, InjectScriptOptions } from 'wxt/utils/inject-script';
|
||||
export { InvalidMatchPattern, MatchPattern } from 'wxt/utils/match-patterns';
|
||||
export { useState, useCallback, useMemo, useEffect, useRef, useContext, useReducer } from 'react';
|
||||
export { fakeBrowser } from 'wxt/testing/fake-browser';
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Generated by wxt
|
||||
export {}
|
||||
declare global {
|
||||
const ContentScriptContext: typeof import('wxt/utils/content-script-context').ContentScriptContext
|
||||
const InvalidMatchPattern: typeof import('wxt/utils/match-patterns').InvalidMatchPattern
|
||||
const MatchPattern: typeof import('wxt/utils/match-patterns').MatchPattern
|
||||
const browser: typeof import('wxt/browser').browser
|
||||
const createIframeUi: typeof import('wxt/utils/content-script-ui/iframe').createIframeUi
|
||||
const createIntegratedUi: typeof import('wxt/utils/content-script-ui/integrated').createIntegratedUi
|
||||
const createShadowRootUi: typeof import('wxt/utils/content-script-ui/shadow-root').createShadowRootUi
|
||||
const defineAppConfig: typeof import('wxt/utils/define-app-config').defineAppConfig
|
||||
const defineBackground: typeof import('wxt/utils/define-background').defineBackground
|
||||
const defineContentScript: typeof import('wxt/utils/define-content-script').defineContentScript
|
||||
const defineUnlistedScript: typeof import('wxt/utils/define-unlisted-script').defineUnlistedScript
|
||||
const defineWxtPlugin: typeof import('wxt/utils/define-wxt-plugin').defineWxtPlugin
|
||||
const fakeBrowser: typeof import('wxt/testing/fake-browser').fakeBrowser
|
||||
const getAppConfig: typeof import('wxt/utils/app-config').getAppConfig
|
||||
const injectScript: typeof import('wxt/utils/inject-script').injectScript
|
||||
const storage: typeof import('wxt/utils/storage').storage
|
||||
const useAppConfig: typeof import('wxt/utils/app-config').useAppConfig
|
||||
const useCallback: typeof import('react').useCallback
|
||||
const useContext: typeof import('react').useContext
|
||||
const useEffect: typeof import('react').useEffect
|
||||
const useMemo: typeof import('react').useMemo
|
||||
const useReducer: typeof import('react').useReducer
|
||||
const useRef: typeof import('react').useRef
|
||||
const useState: typeof import('react').useState
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Browser } from 'wxt/browser'
|
||||
import('wxt/browser')
|
||||
// @ts-ignore
|
||||
export type { WxtStorage, WxtStorageItem, StorageArea, StorageItemKey, StorageAreaChanges, MigrationError } from 'wxt/utils/storage'
|
||||
import('wxt/utils/storage')
|
||||
// @ts-ignore
|
||||
export type { WxtWindowEventMap } from 'wxt/utils/content-script-context'
|
||||
import('wxt/utils/content-script-context')
|
||||
// @ts-ignore
|
||||
export type { IframeContentScriptUi, IframeContentScriptUiOptions } from 'wxt/utils/content-script-ui/iframe'
|
||||
import('wxt/utils/content-script-ui/iframe')
|
||||
// @ts-ignore
|
||||
export type { IntegratedContentScriptUi, IntegratedContentScriptUiOptions } from 'wxt/utils/content-script-ui/integrated'
|
||||
import('wxt/utils/content-script-ui/integrated')
|
||||
// @ts-ignore
|
||||
export type { ShadowRootContentScriptUi, ShadowRootContentScriptUiOptions } from 'wxt/utils/content-script-ui/shadow-root'
|
||||
import('wxt/utils/content-script-ui/shadow-root')
|
||||
// @ts-ignore
|
||||
export type { ContentScriptUi, ContentScriptUiOptions, ContentScriptOverlayAlignment, ContentScriptAppendMode, ContentScriptInlinePositioningOptions, ContentScriptOverlayPositioningOptions, ContentScriptModalPositioningOptions, ContentScriptPositioningOptions, ContentScriptAnchoredOptions, AutoMountOptions, StopAutoMount, AutoMount } from 'wxt/utils/content-script-ui/types'
|
||||
import('wxt/utils/content-script-ui/types')
|
||||
// @ts-ignore
|
||||
export type { WxtAppConfig } from 'wxt/utils/define-app-config'
|
||||
import('wxt/utils/define-app-config')
|
||||
// @ts-ignore
|
||||
export type { ScriptPublicPath, InjectScriptOptions } from 'wxt/utils/inject-script'
|
||||
import('wxt/utils/inject-script')
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
// Generated by wxt
|
||||
import "wxt/browser";
|
||||
|
||||
declare module "wxt/browser" {
|
||||
export type PublicPath =
|
||||
| ""
|
||||
| "/"
|
||||
| "/background.js"
|
||||
type HtmlPublicPath = Extract<PublicPath, `${string}.html`>
|
||||
export interface WxtRuntime {
|
||||
getURL(path: PublicPath): string;
|
||||
getURL(path: `${HtmlPublicPath}${string}`): string;
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// Generated by wxt
|
||||
/// <reference types="wxt/vite-builder-env" />
|
||||
/// <reference types="@wxt-dev/module-react" />
|
||||
/// <reference path="./types/paths.d.ts" />
|
||||
/// <reference path="./types/i18n.d.ts" />
|
||||
/// <reference path="./types/globals.d.ts" />
|
||||
/// <reference path="./types/imports-module.d.ts" />
|
||||
/// <reference path="./types/imports.d.ts" />
|
||||
@@ -0,0 +1,3 @@
|
||||
export default defineBackground(() => {
|
||||
console.info('Atlas collector background started.');
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@atlas/extension",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "wxt build",
|
||||
"dev": "wxt",
|
||||
"typecheck": "wxt prepare && tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlas/contracts": "workspace:*",
|
||||
"@wxt-dev/module-react": "1.2.2",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.5",
|
||||
"typescript": "7.0.2",
|
||||
"wxt": "0.21.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", ".wxt/**/*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/module-react'],
|
||||
manifest: {
|
||||
name: 'Atlas Seller Kit Collector',
|
||||
description: '将已确认的来源商品采集到 Atlas 工作空间。',
|
||||
permissions: ['storage', 'identity'],
|
||||
host_permissions: [
|
||||
'*://*.ozon.ru/*',
|
||||
'*://*.ozon.kz/*',
|
||||
'*://*.ozon.by/*',
|
||||
'*://*.1688.com/*',
|
||||
'*://*.taobao.com/*',
|
||||
'*://*.tmall.com/*',
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Atlas Seller Kit</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@atlas/web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -b && vite build",
|
||||
"dev": "vite",
|
||||
"typecheck": "tsc -b --pretty false"
|
||||
},
|
||||
"dependencies": {
|
||||
"antd": "6.6.2",
|
||||
"axios": "1.20.0",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8",
|
||||
"react-router-dom": "7.18.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.5",
|
||||
"@vitejs/plugin-react": "6.1.1",
|
||||
"typescript": "7.0.2",
|
||||
"vite": "8.2.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { App as AntdApp, ConfigProvider, Typography } from 'antd';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import 'antd/dist/reset.css';
|
||||
|
||||
function Root() {
|
||||
return (
|
||||
<ConfigProvider>
|
||||
<AntdApp>
|
||||
<main style={{ margin: '64px auto', maxWidth: 840, padding: '0 24px' }}>
|
||||
<Typography.Title>Atlas Seller Kit</Typography.Title>
|
||||
<Typography.Paragraph>
|
||||
卖家商品工作台初始化完成。账户、平台上下文和门店模块将在此基础上实现。
|
||||
</Typography.Paragraph>
|
||||
</main>
|
||||
</AntdApp>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<Root />);
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.css';
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||
"jsx": "react-jsx",
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": false
|
||||
},
|
||||
"include": ["src", "vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: Number(process.env.WEB_PORT ?? 8100),
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: process.env.API_ORIGIN ?? 'http://localhost:8200',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@atlas/worker",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"dev": "tsx watch src/main.ts",
|
||||
"start": "node dist/main.js",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlas/contracts": "workspace:*",
|
||||
"bullmq": "6.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "26.3.0",
|
||||
"tsx": "4.23.13",
|
||||
"typescript": "7.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Queue } from 'bullmq';
|
||||
|
||||
const connection = { url: process.env.REDIS_URL ?? 'redis://localhost:6379' };
|
||||
const dispatchQueue = new Queue('atlas-dispatch', { connection });
|
||||
|
||||
async function bootstrap() {
|
||||
await dispatchQueue.waitUntilReady();
|
||||
console.info(
|
||||
'Atlas Worker is ready; it will consume transactional-outbox events after that module exists.',
|
||||
);
|
||||
}
|
||||
|
||||
void bootstrap();
|
||||
|
||||
async function shutdown() {
|
||||
await dispatchQueue.close();
|
||||
}
|
||||
|
||||
process.once('SIGINT', () => void shutdown());
|
||||
process.once('SIGTERM', () => void shutdown());
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
| 产品需求 | [`requirements/`](./requirements/) | 页面、流程、字段和待确认事项 |
|
||||
| 基础架构 | [`architecture/`](./architecture/) | 多租户、平台、门店、水印、物流和定价的数据设计及 DDL |
|
||||
| 商品领域设计 | [`product/`](./product/) | 已有的多 SKU 商品编辑页、领域模型与 API 草案 |
|
||||
| 项目结构 | [`architecture/project-structure.md`](./architecture/project-structure.md) | pnpm monorepo 的目录、依赖关系与本地运行边界。 |
|
||||
| 外部平台资料 | [`integrations/`](./integrations/) | Ozon Seller API 的整理资料与适配依据 |
|
||||
| 学习指南 | [`guides/`](./guides/) | 项目所用技术概念与设计原则的入门说明 |
|
||||
| HTTP API 设计 | [`api/`](./api/) | 可评审的服务端接口契约,按模块逐步补全。 |
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# 项目结构与运行边界
|
||||
|
||||
> 状态:初始化骨架已创建。本文描述仓库的代码归属、构建关系和本地运行边界;业务规则仍以项目交接、API 契约和数据模型文档为准。
|
||||
|
||||
## 1. 目录结构
|
||||
|
||||
```text
|
||||
atlas-seller-kit/
|
||||
├── apps/ # 可独立启动、构建和部署的入口
|
||||
│ ├── web/ # React + Vite 卖家工作台
|
||||
│ ├── api/ # NestJS:HTTP、鉴权、事务与任务创建
|
||||
│ ├── worker/ # BullMQ:Outbox 投递、媒体、AI、同步、发布
|
||||
│ └── extension/ # WXT 浏览器采集扩展
|
||||
├── packages/ # 跨应用的稳定复用单元
|
||||
│ ├── contracts/ # Zod 契约、DTO、共享类型
|
||||
│ └── database/ # SQL migrations、迁移/应用连接基础设施
|
||||
├── infra/
|
||||
│ ├── compose.yaml # local PostgreSQL、Redis、MinIO
|
||||
│ └── README.md # 本地基础设施启动说明
|
||||
├── docs/ # 产品、架构、API 与评审记录
|
||||
├── package.json # 根命令、Node/pnpm 版本约束
|
||||
├── .nvmrc # 本地 Node 24 LTS 版本
|
||||
├── pnpm-workspace.yaml # workspace 边界
|
||||
├── turbo.json # 构建、检查、测试任务图
|
||||
└── tsconfig.base.json # 最小共享 TypeScript 编译约定
|
||||
```
|
||||
|
||||
`apps` 只能依赖明确的 `packages`,不能横向导入另一个应用的源码。这样 API、Worker 和扩展即使独立部署,也始终共享同一份契约。反过来,`packages` 不得依赖任何 `apps`。
|
||||
|
||||
## 2. 代码与服务关系图
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Repository[Atlas Seller Kit · pnpm workspace]
|
||||
subgraph Applications[apps:部署与运行入口]
|
||||
WEB[web\nReact / Vite]
|
||||
API[api\nNestJS HTTP API]
|
||||
WORKER[worker\nBullMQ processors]
|
||||
EXT[extension\nWXT Collector]
|
||||
end
|
||||
|
||||
subgraph Shared[packages:跨应用稳定边界]
|
||||
CONTRACTS[contracts\nZod · DTO · payload types]
|
||||
DB[database\nSQL migrations · DB access]
|
||||
end
|
||||
|
||||
subgraph LocalInfra[infra/compose.yaml:本地依赖]
|
||||
PG[(PostgreSQL)]
|
||||
REDIS[(Redis)]
|
||||
MINIO[(MinIO)]
|
||||
end
|
||||
end
|
||||
|
||||
WEB -->|HTTPS /api| API
|
||||
EXT -->|Bearer API request| API
|
||||
API --> CONTRACTS
|
||||
EXT --> CONTRACTS
|
||||
API --> DB
|
||||
WORKER --> DB
|
||||
API -->|transactional outbox| PG
|
||||
API --> REDIS
|
||||
WORKER -->|consume / retry| REDIS
|
||||
WORKER --> PG
|
||||
WORKER --> MINIO
|
||||
```
|
||||
|
||||
## 3. 运行时责任与数据流
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant E as 浏览器扩展
|
||||
participant W as Web
|
||||
participant A as API
|
||||
participant P as PostgreSQL
|
||||
participant R as Redis / BullMQ
|
||||
participant K as Worker
|
||||
participant O as MinIO / Kodo
|
||||
|
||||
U->>E: 在来源商品页采集
|
||||
E->>A: 共享契约校验后的采集载荷
|
||||
A->>P: 同一事务写业务记录、operation、outbox event
|
||||
A-->>E: 采集记录 / 编辑入口
|
||||
K->>P: Dispatcher 领取未投递 outbox event
|
||||
K->>R: 投递或消费可重试任务
|
||||
K->>O: 下载、处理、转存素材
|
||||
K->>P: 保存任务状态、结果与错误
|
||||
U->>W: 查看进度、编辑、发布
|
||||
W->>A: 鉴权后的 HTTP 请求
|
||||
A->>P: SET LOCAL app.workspace_id 后读写
|
||||
```
|
||||
|
||||
业务事实、审计、操作状态和 Outbox 只在 PostgreSQL 持久化;Redis/BullMQ 负责排队、限流和重试,不能成为唯一事实来源。local 使用 MinIO,staging/production 使用隔离的七牛 Kodo Bucket。
|
||||
|
||||
## 4. 依赖规则
|
||||
|
||||
| 位置 | 可以依赖 | 不应依赖 |
|
||||
|---|---|---|
|
||||
| `apps/web` | `packages/contracts`、前端库 | API/Worker 源码、数据库直连 |
|
||||
| `apps/api` | `packages/contracts`、`packages/database` | Web/扩展源码 |
|
||||
| `apps/worker` | `packages/contracts`、`packages/database` | Web/扩展源码、浏览器会话 |
|
||||
| `apps/extension` | `packages/contracts` | API 内部模块、ERP localStorage token |
|
||||
| `packages/contracts` | Zod 等无运行环境依赖库 | NestJS、React、数据库驱动 |
|
||||
| `packages/database` | PostgreSQL 驱动、迁移工具 | HTTP/UI/平台适配逻辑 |
|
||||
|
||||
当前只抽取了已经确认会跨端复用的采集契约和数据库边界。认证、商品、定价或 Ozon 适配器尚不单独建共享包,等首个纵向闭环出现稳定复用关系后再提取,避免过早形成空壳“公共库”。
|
||||
|
||||
## 5. 本地开发入口
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
pnpm install
|
||||
pnpm infra:up # PostgreSQL、Redis、MinIO
|
||||
pnpm dev # Web、API、Worker、Extension 并行 watch
|
||||
```
|
||||
|
||||
常用验证命令:`pnpm build`、`pnpm typecheck`、`pnpm format:check`。首次实现数据库迁移执行器时,必须将 `atlas_migrator` 和 `atlas_app_login` 的连接串分开配置,并在集成测试中验证 RLS 隔离。
|
||||
|
||||
## 6. 接下来的实现顺序
|
||||
|
||||
1. 在 `packages/database` 实现只接受 migrator 连接串的可重复 SQL migration runner,并导入已评审的 `001_foundation.sql`、`002_product-domain.sql`。
|
||||
2. 在 API 建立配置校验、认证、工作空间上下文和事务包装器;任何工作空间查询前固定 `SET LOCAL app.workspace_id`。
|
||||
3. 建立 `operations`、幂等请求和 Outbox Dispatcher 的第一个集成测试闭环。
|
||||
4. 再实现平台/门店、素材上传和 Web 对应页面;扩展仅在 API 采集入口稳定后接入实际解析器。
|
||||
|
||||
相关约束见:[技术与部署方案](./technology-and-deployment.md)、[数据库安全](./database-security.md)、[Outbox 与幂等](./transactional-outbox-and-idempotency.md)、[项目交接](../project/HANDOFF.md)。
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
不将旧项目的 FastAPI 服务作为新项目运行时依赖;旧项目的 Ozon 资料、计价规则、采集经验和前端技术路线可作为参考或选择性复用。
|
||||
|
||||
代码仓库采用 pnpm workspace monorepo:Web、API、Worker 与浏览器扩展位于独立 `apps/*`,跨端 API 契约和数据库迁移基础设施位于 `packages/*`。目录和依赖边界见 [项目结构与运行边界](./project-structure.md)。
|
||||
|
||||
## 2. 服务边界
|
||||
|
||||
```text
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# 本地基础设施
|
||||
|
||||
复制根目录 `.env.example` 为 `.env` 后,执行 `pnpm infra:up` 启动 PostgreSQL、Redis 和 MinIO。镜像使用已验证的明确版本标签;升级前应先在本地或 staging 验证,再显式更新标签。
|
||||
|
||||
应用进程由 `pnpm dev` 在宿主机启动:Web 使用 Vite 热更新,API 与 Worker 使用各自的 watch 模式。生产环境的 API、Worker 和反向代理容器编排将在部署运行手册中补充;PostgreSQL 和对象存储继续遵守现有架构文档的托管服务边界。
|
||||
@@ -0,0 +1,44 @@
|
||||
name: atlas-seller-kit
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18.6-alpine
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-atlas_app_login}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-me-local-only}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-atlas}
|
||||
ports:
|
||||
- '${POSTGRES_PORT:-5432}:5432'
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
redis:
|
||||
image: redis:7.4.11-alpine3.21
|
||||
ports:
|
||||
- '6379:6379'
|
||||
healthcheck:
|
||||
test: ['CMD', 'redis-cli', 'ping']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
|
||||
command: server /data --console-address ':9001'
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-atlas_minio_local}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-change-me-local-only}
|
||||
ports:
|
||||
- '${MINIO_PORT:-9000}:9000'
|
||||
- '${MINIO_CONSOLE_PORT:-9001}:9001'
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
minio-data:
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "atlas-seller-kit",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@11.25.0",
|
||||
"engines": {
|
||||
"node": ">=24.20.0 <25",
|
||||
"pnpm": ">=11.25.0 <12"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev --parallel",
|
||||
"lint": "turbo run lint",
|
||||
"typecheck": "turbo run typecheck",
|
||||
"test": "turbo run test",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"infra:up": "docker compose -f infra/compose.yaml up -d postgres redis minio",
|
||||
"infra:down": "docker compose -f infra/compose.yaml down"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "3.9.6",
|
||||
"turbo": "2.10.12",
|
||||
"typescript": "7.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@atlas/contracts",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "4.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "7.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* 浏览器扩展与 API 共享的采集载荷最小契约。
|
||||
* 领域字段会随采集模块实现逐步补全,不能在扩展侧绕过此契约直接构造 API 请求。
|
||||
*/
|
||||
export const collectedProductPayloadSchema = z.object({
|
||||
source: z.object({
|
||||
platform: z.enum(['ozon', '1688', 'taobao', 'tmall']),
|
||||
itemKey: z.string().min(1),
|
||||
canonicalUrl: z.url(),
|
||||
collectedAt: z.iso.datetime(),
|
||||
parserVersion: z.string().min(1),
|
||||
}),
|
||||
common: z.object({
|
||||
title: z.string().min(1).optional(),
|
||||
}),
|
||||
variantAxes: z.array(z.unknown()),
|
||||
variants: z.array(z.unknown()),
|
||||
assets: z.array(z.unknown()),
|
||||
evidence: z.array(z.unknown()),
|
||||
});
|
||||
|
||||
export type CollectedProductPayload = z.infer<typeof collectedProductPayloadSchema>;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './collection';
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# SQL migrations
|
||||
|
||||
此目录只保存按序号命名、不可修改历史的 SQL migration,例如 `001_foundation.sql`。
|
||||
|
||||
迁移执行器必须使用 `atlas_migrator` 连接;API 和 Worker 只允许使用 `atlas_app_login`。每个涉及工作空间数据的应用事务,都必须在查询前执行 `SET LOCAL app.workspace_id`。详细权限规则见 [`docs/architecture/database-security.md`](../../../docs/architecture/database-security.md)。
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "@atlas/database",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"pg": "8.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "26.3.0",
|
||||
"@types/pg": "8.23.1",
|
||||
"typescript": "7.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 数据库访问层的唯一入口。
|
||||
* 后续在此封装迁移连接与应用连接,应用事务必须先设置 app.workspace_id。
|
||||
*/
|
||||
export const databasePackageName = '@atlas/database';
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Generated
+5602
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
packages:
|
||||
- apps/*
|
||||
- packages/*
|
||||
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
msgpackr-extract: false
|
||||
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
printWidth: 100,
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2023",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**", ".output/**"]
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"lint": {
|
||||
"dependsOn": ["^lint"]
|
||||
},
|
||||
"typecheck": {
|
||||
"dependsOn": ["^typecheck"],
|
||||
"outputs": []
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["coverage/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user