livrare website cu erp si crm
This commit is contained in:
parent
28773e3a72
commit
5c7bf7c295
257 changed files with 31929 additions and 0 deletions
322
website/INSTALL.md
Normal file
322
website/INSTALL.md
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
# Instalare Website DiDi de la 0
|
||||
|
||||
Build complet pentru website-ul Next.js, integrat cu ERPNext + Stripe + Keycloak.
|
||||
|
||||
## Prerequisite
|
||||
|
||||
### Software pe host
|
||||
|
||||
```bash
|
||||
docker --version # >= 24.0
|
||||
docker compose version # >= v2
|
||||
```
|
||||
|
||||
NU e nevoie de Node.js pe host - se construieste in container.
|
||||
|
||||
### Resurse
|
||||
|
||||
- 2 GB RAM
|
||||
- 5 GB spatiu disc
|
||||
- Port 3000 liber
|
||||
|
||||
### Retea Docker
|
||||
|
||||
Aceeasi retea cu ERP/CRM:
|
||||
|
||||
```bash
|
||||
docker network create didi-network 2>/dev/null || true
|
||||
```
|
||||
|
||||
### Servicii dependinte (trebuie sa ruleze inainte)
|
||||
|
||||
- **ERPNext** (`didi-erpnext:8080`) - vezi `erp_crm/INSTALL.md`
|
||||
- **Keycloak** (`didi-keycloak:8080` sau extern)
|
||||
- **Stripe** cont (cheile API in .env.production)
|
||||
- **DiDi Platform** (agent-v3) sau accesibil prin URL
|
||||
|
||||
Daca rulezi separat, website-ul porneste oricum dar paginile auth/checkout vor da erori.
|
||||
|
||||
## Build automat
|
||||
|
||||
```bash
|
||||
cd ~/website
|
||||
bash build-from-zero.sh
|
||||
```
|
||||
|
||||
Scriptul:
|
||||
1. Verifica `.env.production` (creeaza-l din template daca lipseste)
|
||||
2. Build imagine Docker (multi-stage: node:20-alpine -> next build -> prod)
|
||||
3. Start container
|
||||
4. Health check pe http://localhost:3000
|
||||
|
||||
## Build manual
|
||||
|
||||
### Pas 1. `.env.production`
|
||||
|
||||
Editeaza `~/website/.env.production`. Variabilele critice:
|
||||
|
||||
```env
|
||||
# ERPNext (intern via Docker DNS)
|
||||
NEXT_PUBLIC_ERPNEXT_URL=http://localhost:8080
|
||||
ERPNEXT_API_URL=http://didi-erpnext:8080
|
||||
ERPNEXT_API_KEY=<token API key>
|
||||
ERPNEXT_API_SECRET=<token secret>
|
||||
|
||||
# Keycloak
|
||||
AUTH_KEYCLOAK_ID=didi-website-server
|
||||
AUTH_KEYCLOAK_SECRET=<secret>
|
||||
AUTH_KEYCLOAK_ISSUER=https://<KEYCLOAK_HOST>/auth/realms/didi-clients
|
||||
AUTH_SECRET=<random 32 chars>
|
||||
AUTH_TRUST_HOST=true
|
||||
AUTH_URL=http://<HOST>:3000
|
||||
|
||||
# DiDi API (intern)
|
||||
DIDI_API_URL=http://didi-agent-v3:24803/api
|
||||
DIDI_FRAMEWORK_URL=http://didi-framework:3005
|
||||
|
||||
# Stripe
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
|
||||
STRIPE_SECRET_KEY=sk_test_...
|
||||
STRIPE_WEBHOOK_SECRET=whsec_...
|
||||
|
||||
# Stripe subscriptions
|
||||
STRIPE_PRICE_PAID_MONTHLY=price_xxx
|
||||
STRIPE_PRICE_PAID_YEARLY=price_xxx
|
||||
STRIPE_PRICE_ENTERPRISE_MONTHLY=price_xxx
|
||||
STRIPE_PRICE_ENTERPRISE_YEARLY=price_xxx
|
||||
|
||||
# Site
|
||||
NEXT_PUBLIC_SITE_URL=http://<HOST>:3000
|
||||
NEXT_PUBLIC_DEFAULT_LOCALE=ro
|
||||
```
|
||||
|
||||
Pentru valori complete vezi [CONFIGURATION.md](./CONFIGURATION.md).
|
||||
|
||||
### Pas 2. Build + run
|
||||
|
||||
```bash
|
||||
cd ~/website
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Build-ul dureaza ~30 secunde (Next.js compile + tsc + static generation).
|
||||
|
||||
### Pas 3. Verificare
|
||||
|
||||
```bash
|
||||
sleep 10
|
||||
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000
|
||||
# Trebuie sa fie 200
|
||||
```
|
||||
|
||||
Browser: http://localhost:3000
|
||||
|
||||
## Configurare Keycloak (one-time)
|
||||
|
||||
Pe platforma reala, realm-ul utilizatorilor este **`didi-clients`** (issuer cu prefix `/auth`). Pentru dezvoltare locala (mod DEMO cu mock), creeaza un realm local cu acelasi nume:
|
||||
|
||||
```bash
|
||||
KC_TOKEN=$(curl -s -X POST http://localhost:28080/realms/master/protocol/openid-connect/token \
|
||||
-d "client_id=admin-cli&username=admin&password=admin123&grant_type=password" \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
|
||||
|
||||
# Realm
|
||||
curl -s -X POST http://localhost:28080/admin/realms \
|
||||
-H "Authorization: Bearer $KC_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"realm": "didi-clients",
|
||||
"enabled": true,
|
||||
"registrationAllowed": true,
|
||||
"resetPasswordAllowed": true
|
||||
}'
|
||||
|
||||
# Client confidential
|
||||
curl -s -X POST http://localhost:28080/admin/realms/didi-clients/clients \
|
||||
-H "Authorization: Bearer $KC_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"clientId": "didi-website-server",
|
||||
"secret": "<acelasi cu AUTH_KEYCLOAK_SECRET>",
|
||||
"publicClient": false,
|
||||
"redirectUris": ["http://<HOST>:3000/*"],
|
||||
"webOrigins": ["http://<HOST>:3000"],
|
||||
"standardFlowEnabled": true,
|
||||
"directAccessGrantsEnabled": true
|
||||
}'
|
||||
|
||||
# Test user
|
||||
curl -s -X POST http://localhost:28080/admin/realms/didi-clients/users \
|
||||
-H "Authorization: Bearer $KC_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "test@didi.local",
|
||||
"email": "test@didi.local",
|
||||
"firstName": "Test",
|
||||
"lastName": "User",
|
||||
"enabled": true,
|
||||
"emailVerified": true,
|
||||
"credentials": [{"type": "password", "value": "Test1234!", "temporary": false}]
|
||||
}'
|
||||
```
|
||||
|
||||
## Configurare Stripe (one-time)
|
||||
|
||||
### Pas 1. Cont Stripe
|
||||
|
||||
Creeaza cont la https://stripe.com si activeaza modul test.
|
||||
|
||||
### Pas 2. Cheia API
|
||||
|
||||
Dashboard > Developers > API keys > "Reveal test key"
|
||||
- `pk_test_xxx` (publishable) -> `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`
|
||||
- `sk_test_xxx` (secret) -> `STRIPE_SECRET_KEY`
|
||||
|
||||
### Pas 3. Produse one-time
|
||||
|
||||
In `src/lib/stripe.ts` exista o mapare `PRICE_MAP` cu 14 price IDs. Acestea trebuie sa existe in contul tau Stripe. Pentru a le crea automat:
|
||||
|
||||
```bash
|
||||
docker exec -i didi-website node -e "
|
||||
const Stripe = require('stripe').default || require('stripe');
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
||||
const ITEMS = [
|
||||
['techniques-text', 'Techniques - Text', 5000],
|
||||
['techniques-image', 'Techniques - Image', 7500],
|
||||
// ... vezi PRICE_MAP pentru lista completa
|
||||
];
|
||||
(async () => {
|
||||
for (const [key, name, amount] of ITEMS) {
|
||||
const p = await stripe.products.create({ name, metadata: { service_key: key }});
|
||||
const price = await stripe.prices.create({ product: p.id, unit_amount: amount, currency: 'ron' });
|
||||
console.log(key, '->', price.id);
|
||||
}
|
||||
})();
|
||||
"
|
||||
```
|
||||
|
||||
### Pas 4. Produse recurente (subscriptii)
|
||||
|
||||
```bash
|
||||
docker exec -i didi-website node -e "
|
||||
const Stripe = require('stripe').default || require('stripe');
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
||||
const PLANS = [
|
||||
{ key: 'paid-monthly', name: 'DiDi Paid - Lunar', amount: 9900, interval: 'month' },
|
||||
{ key: 'paid-yearly', name: 'DiDi Paid - Anual', amount: 99900, interval: 'year' },
|
||||
{ key: 'enterprise-monthly', name: 'DiDi Enterprise - Lunar', amount: 49900, interval: 'month' },
|
||||
{ key: 'enterprise-yearly', name: 'DiDi Enterprise - Anual', amount: 499000, interval: 'year' },
|
||||
];
|
||||
(async () => {
|
||||
for (const p of PLANS) {
|
||||
const prod = await stripe.products.create({ name: p.name, metadata: { plan_key: p.key }});
|
||||
const price = await stripe.prices.create({
|
||||
product: prod.id, unit_amount: p.amount, currency: 'ron',
|
||||
recurring: { interval: p.interval }
|
||||
});
|
||||
console.log(p.key, '->', price.id);
|
||||
}
|
||||
})();
|
||||
"
|
||||
```
|
||||
|
||||
Apoi pune ID-urile in `.env.production` la `STRIPE_PRICE_PAID_MONTHLY` etc.
|
||||
|
||||
### Pas 5. Webhook (la productie)
|
||||
|
||||
In dev local NU primesti webhook-uri Stripe (nu poate ajunge la masina ta).
|
||||
|
||||
La productie:
|
||||
- Dashboard Stripe > Developers > Webhooks > Add endpoint
|
||||
- URL: `https://didi.tld/api/webhooks/stripe`
|
||||
- Events: `checkout.session.completed`, `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.paid`, `invoice.payment_failed`
|
||||
- Copy signing secret in `STRIPE_WEBHOOK_SECRET`
|
||||
|
||||
Pentru dev local, foloseste Stripe CLI:
|
||||
```bash
|
||||
stripe listen --forward-to http://<HOST>:3000/api/webhooks/stripe
|
||||
```
|
||||
|
||||
## Configurare ERPNext API key (one-time)
|
||||
|
||||
In ERPNext: Setup > User > Administrator > API Access > Generate Keys
|
||||
|
||||
Salveaza API Key + Secret in `.env.production`:
|
||||
```env
|
||||
ERPNEXT_API_KEY=85e12d367334775
|
||||
ERPNEXT_API_SECRET=910a2dfda16d2af
|
||||
```
|
||||
|
||||
## Dezvoltare locala
|
||||
|
||||
Daca vrei sa schimbi codul fara rebuild de docker:
|
||||
|
||||
```bash
|
||||
cd ~/website
|
||||
npm install
|
||||
cp .env.production .env.local
|
||||
# Edit .env.local pentru localhost-uri (ERPNEXT_API_URL=http://localhost:8080)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Acceseaza http://localhost:3000 (sau alt port daca 3000 e ocupat).
|
||||
|
||||
## Probleme frecvente
|
||||
|
||||
### Login: "Server error - problem with server configuration"
|
||||
|
||||
Verifica:
|
||||
1. `AUTH_KEYCLOAK_ISSUER` e accesibil din container (curl din container)
|
||||
2. `AUTH_SECRET` setat la 32+ caractere random
|
||||
3. Realm + Client exista in Keycloak
|
||||
4. `redirectUris` in client include URL-ul tau
|
||||
|
||||
```bash
|
||||
docker exec didi-website wget -qO- https://<KEYCLOAK_HOST>/auth/realms/didi-clients/.well-known/openid-configuration | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Login redirecteaza la /login dupa Keycloak
|
||||
|
||||
Cookies corupte. Hard refresh (Ctrl+Shift+R) sau modă incognito.
|
||||
|
||||
### Stripe Checkout duce inapoi la /login
|
||||
|
||||
`NEXT_PUBLIC_SITE_URL` are alt host decat cel din browser. Asigura-te ca acceseaza acelasi host (ex: tot `<HOST>:3000`, nu mix de localhost/IP).
|
||||
|
||||
### Pagina `/dashboard/credits` arata 0
|
||||
|
||||
Userul Keycloak nu are inregistrare in `bos_sysadmin.internet_user`. Trebuie seedat:
|
||||
|
||||
```sql
|
||||
-- PG: didiFramework DB
|
||||
INSERT INTO bos_sysadmin.internet_user (internet_user_id, person_id, credits_remained, credits_spent, storage_limit_bytes)
|
||||
VALUES (90000, 90000, 100, 0, 1073741824);
|
||||
|
||||
INSERT INTO bos_sysadmin.user_credential (internet_user_id, email, enrollment_type, cellular_phone_no, no_attempts_failed, subscription_status, activation_date, keycloak_id)
|
||||
VALUES (90000, 'test@didi.local', 1, '', 0, 1, CURRENT_DATE, '<keycloak_uuid>');
|
||||
```
|
||||
|
||||
Sau Auth.js auto-creeaza la primul login daca framework e configurat asa.
|
||||
|
||||
### Build esueaza cu "Module not found"
|
||||
|
||||
```bash
|
||||
docker compose build --no-cache website
|
||||
```
|
||||
|
||||
### Site afiseaza versiune veche dupa rebuild
|
||||
|
||||
```bash
|
||||
docker compose down website
|
||||
docker image rm website-website
|
||||
docker compose up -d --build website
|
||||
```
|
||||
|
||||
## Reset complet
|
||||
|
||||
```bash
|
||||
cd ~/website
|
||||
docker compose down
|
||||
docker image rm website-website 2>/dev/null
|
||||
bash build-from-zero.sh
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue