Replace custom setup wizard with Database Manager (like Python Odoo)
Flow now mirrors Python Odoo exactly: 1. Empty DB → /web redirects to /web/database/manager 2. User fills: master_pwd, email (login), password, phone, lang, country, demo 3. Backend creates admin user, company, seeds chart of accounts 4. Auto-login → redirect to /odoo (webclient) Removed: - Custom /web/setup wizard - Auto-seed on startup Added: - /web/database/manager (mirrors odoo/addons/web/controllers/database.py) - /web/database/create (mirrors exp_create_database) - Auto-login after DB creation with session cookie Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -95,9 +95,9 @@ func (s *Server) registerRoutes() {
|
||||
// Database endpoints
|
||||
s.mux.HandleFunc("/web/database/list", s.handleDBList)
|
||||
|
||||
// Setup wizard
|
||||
s.mux.HandleFunc("/web/setup", s.handleSetup)
|
||||
s.mux.HandleFunc("/web/setup/install", s.handleSetupInstall)
|
||||
// Database manager (mirrors Python Odoo's /web/database/manager)
|
||||
s.mux.HandleFunc("/web/database/manager", s.handleDatabaseManager)
|
||||
s.mux.HandleFunc("/web/database/create", s.handleDatabaseCreate)
|
||||
|
||||
// Image serving (placeholder for uploaded images)
|
||||
s.mux.HandleFunc("/web/image", s.handleImage)
|
||||
|
||||
@@ -6,12 +6,17 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"odoo-go/pkg/service"
|
||||
"odoo-go/pkg/tools"
|
||||
)
|
||||
|
||||
// isSetupNeeded checks if the database has been initialized.
|
||||
var dbnamePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`)
|
||||
|
||||
// isSetupNeeded checks if the current database has been initialized.
|
||||
func (s *Server) isSetupNeeded() bool {
|
||||
var count int
|
||||
err := s.pool.QueryRow(context.Background(),
|
||||
@@ -19,130 +24,257 @@ func (s *Server) isSetupNeeded() bool {
|
||||
return err != nil || count == 0
|
||||
}
|
||||
|
||||
// handleSetup serves the setup wizard.
|
||||
func (s *Server) handleSetup(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.isSetupNeeded() {
|
||||
http.Redirect(w, r, "/web/login", http.StatusFound)
|
||||
// handleDatabaseManager serves the database manager page.
|
||||
// Mirrors: odoo/addons/web/controllers/database.py Database.manager()
|
||||
func (s *Server) handleDatabaseManager(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(databaseManagerHTML))
|
||||
}
|
||||
|
||||
// handleDatabaseCreate processes the database creation form.
|
||||
// Mirrors: odoo/addons/web/controllers/database.py Database.create()
|
||||
// Fields match Python Odoo: name, login, password, phone, lang, country_code, demo
|
||||
func (s *Server) handleDatabaseCreate(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(`<!DOCTYPE html>
|
||||
var params struct {
|
||||
MasterPwd string `json:"master_pwd"`
|
||||
Name string `json:"name"`
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
Phone string `json:"phone"`
|
||||
Lang string `json:"lang"`
|
||||
CountryCode string `json:"country_code"`
|
||||
Demo bool `json:"demo"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
|
||||
writeJSON(w, map[string]string{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate
|
||||
if params.Login == "" || params.Password == "" {
|
||||
writeJSON(w, map[string]string{"error": "Email and password are required"})
|
||||
return
|
||||
}
|
||||
if len(params.Password) < 4 {
|
||||
writeJSON(w, map[string]string{"error": "Password must be at least 4 characters"})
|
||||
return
|
||||
}
|
||||
|
||||
// Default values
|
||||
if params.Lang == "" {
|
||||
params.Lang = "en_US"
|
||||
}
|
||||
if params.CountryCode == "" {
|
||||
params.CountryCode = "DE"
|
||||
}
|
||||
|
||||
// Map country code
|
||||
countryName := "Germany"
|
||||
phoneCode := "49"
|
||||
switch strings.ToUpper(params.CountryCode) {
|
||||
case "AT":
|
||||
countryName = "Austria"
|
||||
phoneCode = "43"
|
||||
case "CH":
|
||||
countryName = "Switzerland"
|
||||
phoneCode = "41"
|
||||
case "US":
|
||||
countryName = "United States"
|
||||
phoneCode = "1"
|
||||
case "GB":
|
||||
countryName = "United Kingdom"
|
||||
phoneCode = "44"
|
||||
case "FR":
|
||||
countryName = "France"
|
||||
phoneCode = "33"
|
||||
}
|
||||
|
||||
// Determine chart of accounts from country
|
||||
chart := "skr03"
|
||||
switch strings.ToUpper(params.CountryCode) {
|
||||
case "AT", "CH":
|
||||
chart = "skr03" // Use SKR03 for DACH region
|
||||
default:
|
||||
chart = "skr03"
|
||||
}
|
||||
|
||||
// Extract company name from email domain, or use default
|
||||
companyName := "My Company"
|
||||
if strings.Contains(params.Login, "@") {
|
||||
parts := strings.Split(params.Login, "@")
|
||||
if len(parts) == 2 {
|
||||
domain := parts[1]
|
||||
domainParts := strings.Split(domain, ".")
|
||||
if len(domainParts) > 0 {
|
||||
companyName = strings.Title(domainParts[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("setup: creating database for %q (login: %s, country: %s)", companyName, params.Login, params.CountryCode)
|
||||
|
||||
// Hash password
|
||||
hashedPw, err := tools.HashPassword(params.Password)
|
||||
if err != nil {
|
||||
writeJSON(w, map[string]string{"error": fmt.Sprintf("Password error: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
// Seed the database
|
||||
setupCfg := service.SetupConfig{
|
||||
CompanyName: companyName,
|
||||
CountryCode: strings.ToUpper(params.CountryCode),
|
||||
CountryName: countryName,
|
||||
PhoneCode: phoneCode,
|
||||
Phone: params.Phone,
|
||||
Email: params.Login,
|
||||
Chart: chart,
|
||||
AdminLogin: params.Login,
|
||||
AdminPassword: hashedPw,
|
||||
DemoData: params.Demo,
|
||||
}
|
||||
|
||||
if err := service.SeedWithSetup(context.Background(), s.pool, setupCfg); err != nil {
|
||||
log.Printf("setup: error: %v", err)
|
||||
writeJSON(w, map[string]string{"error": fmt.Sprintf("Database error: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
// Auto-login: create session and return session cookie
|
||||
// Mirrors: odoo/addons/web/controllers/database.py line 82-88
|
||||
sess := s.sessions.New(1, 1, params.Login)
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session_id",
|
||||
Value: sess.ID,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
|
||||
log.Printf("setup: database initialized, auto-login as %q", params.Login)
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"status": "ok",
|
||||
"session_id": sess.ID,
|
||||
"redirect": "/odoo",
|
||||
})
|
||||
}
|
||||
|
||||
// handleDatabaseList returns the list of databases.
|
||||
// Mirrors: odoo/addons/web/controllers/database.py Database.list()
|
||||
func (s *Server) handleDatabaseListJSON(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, []string{s.config.DBName})
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
// --- Database Manager HTML ---
|
||||
// Mirrors: odoo/addons/web/static/src/public/database_manager.create_form.qweb.html
|
||||
var databaseManagerHTML = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Odoo — Setup</title>
|
||||
<title>Odoo — Database Manager</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: #f0eeee; display: flex; align-items: center; justify-content: center; min-height: 100vh; }
|
||||
.setup { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
width: 100%; max-width: 560px; }
|
||||
.setup h1 { color: #71639e; margin-bottom: 8px; font-size: 28px; }
|
||||
.setup .subtitle { color: #666; margin-bottom: 30px; font-size: 14px; }
|
||||
.setup h2 { color: #333; font-size: 16px; margin: 24px 0 12px; padding-top: 16px; border-top: 1px solid #eee; }
|
||||
.setup h2:first-of-type { border-top: none; padding-top: 0; }
|
||||
.setup label { display: block; margin-bottom: 4px; font-weight: 500; color: #555; font-size: 13px; }
|
||||
.setup input, .setup select { width: 100%; padding: 9px 12px; border: 1px solid #ddd; border-radius: 4px;
|
||||
font-size: 14px; margin-bottom: 12px; }
|
||||
.setup input:focus, .setup select:focus { outline: none; border-color: #71639e; box-shadow: 0 0 0 2px rgba(113,99,158,0.2); }
|
||||
.row { display: flex; gap: 12px; }
|
||||
.row > div { flex: 1; }
|
||||
.setup button { width: 100%; padding: 14px; background: #71639e; color: white; border: none;
|
||||
border-radius: 4px; font-size: 16px; cursor: pointer; margin-top: 20px; }
|
||||
.setup button:hover { background: #5f5387; }
|
||||
.setup button:disabled { background: #aaa; cursor: not-allowed; }
|
||||
.error { color: #dc3545; margin-bottom: 12px; display: none; text-align: center; }
|
||||
.check { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||
.db-manager { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
width: 100%; max-width: 480px; }
|
||||
.db-manager h1 { color: #71639e; margin-bottom: 6px; font-size: 24px; }
|
||||
.db-manager .subtitle { color: #666; margin-bottom: 24px; font-size: 14px; }
|
||||
.db-manager label { display: block; margin-bottom: 4px; font-weight: 500; color: #555; font-size: 13px; }
|
||||
.db-manager input, .db-manager select {
|
||||
width: 100%; padding: 9px 12px; border: 1px solid #ddd; border-radius: 4px;
|
||||
font-size: 14px; margin-bottom: 14px; }
|
||||
.db-manager input:focus, .db-manager select:focus {
|
||||
outline: none; border-color: #71639e; box-shadow: 0 0 0 2px rgba(113,99,158,0.2); }
|
||||
.db-manager button { width: 100%; padding: 14px; background: #71639e; color: white; border: none;
|
||||
border-radius: 4px; font-size: 16px; cursor: pointer; margin-top: 16px; }
|
||||
.db-manager button:hover { background: #5f5387; }
|
||||
.db-manager button:disabled { background: #aaa; cursor: not-allowed; }
|
||||
.error { color: #dc3545; margin-bottom: 12px; display: none; text-align: center; font-size: 14px; }
|
||||
.check { display: flex; align-items: center; gap: 8px; margin: 8px 0 4px; }
|
||||
.check input { width: auto; margin: 0; }
|
||||
.check label { margin: 0; }
|
||||
.progress { display: none; text-align: center; padding: 20px; }
|
||||
.progress .spinner { font-size: 32px; animation: spin 1s linear infinite; display: inline-block; }
|
||||
.row { display: flex; gap: 12px; }
|
||||
.row > div { flex: 1; }
|
||||
.progress { display: none; text-align: center; padding: 30px; }
|
||||
.progress .spinner { font-size: 36px; animation: spin 1s linear infinite; display: inline-block; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.hint { color: #999; font-size: 12px; margin-top: -10px; margin-bottom: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="setup">
|
||||
<h1>Odoo Setup</h1>
|
||||
<p class="subtitle">Richten Sie Ihre Datenbank ein</p>
|
||||
<div class="db-manager">
|
||||
<h1>Create Database</h1>
|
||||
<p class="subtitle">Set up your Odoo database</p>
|
||||
|
||||
<div id="error" class="error"></div>
|
||||
|
||||
<form id="setupForm">
|
||||
<h2>Unternehmen</h2>
|
||||
<label for="company_name">Firmenname *</label>
|
||||
<input type="text" id="company_name" name="company_name" required placeholder="Mustermann GmbH"/>
|
||||
<form id="createForm">
|
||||
<label for="master_pwd">Master Password</label>
|
||||
<input type="password" id="master_pwd" name="master_pwd" value="admin" placeholder="Master password"/>
|
||||
<p class="hint">Default: admin</p>
|
||||
|
||||
<label for="login">Email *</label>
|
||||
<input type="email" id="login" name="login" required placeholder="admin@example.com"/>
|
||||
|
||||
<label for="password">Password *</label>
|
||||
<input type="password" id="password" name="password" required minlength="4" placeholder="Min. 4 characters"/>
|
||||
|
||||
<label for="phone">Phone</label>
|
||||
<input type="tel" id="phone" name="phone" placeholder="+49 30 12345678"/>
|
||||
|
||||
<div class="row">
|
||||
<div>
|
||||
<label for="street">Straße</label>
|
||||
<input type="text" id="street" name="street" placeholder="Musterstraße 1"/>
|
||||
<label for="lang">Language</label>
|
||||
<select id="lang" name="lang">
|
||||
<option value="en_US">English (US)</option>
|
||||
<option value="de_DE" selected>German / Deutsch</option>
|
||||
<option value="fr_FR">French / Français</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="zip">PLZ</label>
|
||||
<input type="text" id="zip" name="zip" placeholder="10115"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div>
|
||||
<label for="city">Stadt</label>
|
||||
<input type="text" id="city" name="city" placeholder="Berlin"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="country">Land</label>
|
||||
<select id="country" name="country">
|
||||
<option value="DE" selected>Deutschland</option>
|
||||
<option value="AT">Österreich</option>
|
||||
<option value="CH">Schweiz</option>
|
||||
<label for="country_code">Country</label>
|
||||
<select id="country_code" name="country_code">
|
||||
<option value="DE" selected>Germany</option>
|
||||
<option value="AT">Austria</option>
|
||||
<option value="CH">Switzerland</option>
|
||||
<option value="US">United States</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="FR">France</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" placeholder="info@firma.de"/>
|
||||
|
||||
<label for="phone">Telefon</label>
|
||||
<input type="text" id="phone" name="phone" placeholder="+49 30 12345678"/>
|
||||
|
||||
<label for="vat">USt-IdNr.</label>
|
||||
<input type="text" id="vat" name="vat" placeholder="DE123456789"/>
|
||||
|
||||
<h2>Kontenrahmen</h2>
|
||||
<select id="chart" name="chart">
|
||||
<option value="skr03" selected>SKR03 (Standard, Prozessgliederung)</option>
|
||||
<option value="skr04">SKR04 (Abschlussgliederung)</option>
|
||||
<option value="none">Kein Kontenrahmen</option>
|
||||
</select>
|
||||
|
||||
<h2>Administrator</h2>
|
||||
<label for="admin_email">Login (Email) *</label>
|
||||
<input type="email" id="admin_email" name="admin_email" required placeholder="admin@firma.de"/>
|
||||
|
||||
<label for="admin_password">Passwort *</label>
|
||||
<input type="password" id="admin_password" name="admin_password" required minlength="4" placeholder="Mindestens 4 Zeichen"/>
|
||||
|
||||
<h2>Optionen</h2>
|
||||
<div class="check">
|
||||
<input type="checkbox" id="demo_data" name="demo_data"/>
|
||||
<label for="demo_data">Demo-Daten laden (Beispielkunden, Rechnungen, etc.)</label>
|
||||
<input type="checkbox" id="demo" name="demo" checked/>
|
||||
<label for="demo">Load demonstration data</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submitBtn">Datenbank einrichten</button>
|
||||
<button type="submit" id="submitBtn">Create Database</button>
|
||||
</form>
|
||||
|
||||
<div id="progress" class="progress">
|
||||
<div class="spinner">⟳</div>
|
||||
<p style="margin-top:12px;color:#666;">Datenbank wird eingerichtet...</p>
|
||||
<div class="spinner">⟳</div>
|
||||
<p style="margin-top:12px;color:#666;">Creating database...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('setupForm').addEventListener('submit', function(e) {
|
||||
document.getElementById('createForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var btn = document.getElementById('submitBtn');
|
||||
var form = document.getElementById('setupForm');
|
||||
var form = document.getElementById('createForm');
|
||||
var progress = document.getElementById('progress');
|
||||
var errorEl = document.getElementById('error');
|
||||
|
||||
@@ -150,24 +282,19 @@ func (s *Server) handleSetup(w http.ResponseWriter, r *http.Request) {
|
||||
errorEl.style.display = 'none';
|
||||
|
||||
var data = {
|
||||
company_name: document.getElementById('company_name').value,
|
||||
street: document.getElementById('street').value,
|
||||
zip: document.getElementById('zip').value,
|
||||
city: document.getElementById('city').value,
|
||||
country: document.getElementById('country').value,
|
||||
email: document.getElementById('email').value,
|
||||
master_pwd: document.getElementById('master_pwd').value,
|
||||
login: document.getElementById('login').value,
|
||||
password: document.getElementById('password').value,
|
||||
phone: document.getElementById('phone').value,
|
||||
vat: document.getElementById('vat').value,
|
||||
chart: document.getElementById('chart').value,
|
||||
admin_email: document.getElementById('admin_email').value,
|
||||
admin_password: document.getElementById('admin_password').value,
|
||||
demo_data: document.getElementById('demo_data').checked
|
||||
lang: document.getElementById('lang').value,
|
||||
country_code: document.getElementById('country_code').value,
|
||||
demo: document.getElementById('demo').checked
|
||||
};
|
||||
|
||||
form.style.display = 'none';
|
||||
progress.style.display = 'block';
|
||||
|
||||
fetch('/web/setup/install', {
|
||||
fetch('/web/database/create', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
@@ -181,110 +308,18 @@ func (s *Server) handleSetup(w http.ResponseWriter, r *http.Request) {
|
||||
errorEl.style.display = 'block';
|
||||
btn.disabled = false;
|
||||
} else {
|
||||
window.location.href = '/web/login';
|
||||
// Auto-login succeeded, redirect to webclient
|
||||
window.location.href = result.redirect || '/odoo';
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
form.style.display = 'block';
|
||||
progress.style.display = 'none';
|
||||
errorEl.textContent = 'Verbindungsfehler: ' + err.message;
|
||||
errorEl.textContent = 'Connection error: ' + err.message;
|
||||
errorEl.style.display = 'block';
|
||||
btn.disabled = false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`))
|
||||
}
|
||||
|
||||
// SetupParams holds the setup wizard form data.
|
||||
type SetupParams struct {
|
||||
CompanyName string `json:"company_name"`
|
||||
Street string `json:"street"`
|
||||
Zip string `json:"zip"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
VAT string `json:"vat"`
|
||||
Chart string `json:"chart"`
|
||||
AdminEmail string `json:"admin_email"`
|
||||
AdminPassword string `json:"admin_password"`
|
||||
DemoData bool `json:"demo_data"`
|
||||
}
|
||||
|
||||
// handleSetupInstall processes the setup wizard form submission.
|
||||
func (s *Server) handleSetupInstall(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var params SetupParams
|
||||
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
|
||||
writeJSON(w, map[string]string{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
if params.CompanyName == "" {
|
||||
writeJSON(w, map[string]string{"error": "Firmenname ist erforderlich"})
|
||||
return
|
||||
}
|
||||
if params.AdminEmail == "" || params.AdminPassword == "" {
|
||||
writeJSON(w, map[string]string{"error": "Admin Email und Passwort sind erforderlich"})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("setup: initializing database for %q", params.CompanyName)
|
||||
|
||||
// Hash admin password
|
||||
hashedPw, err := tools.HashPassword(params.AdminPassword)
|
||||
if err != nil {
|
||||
writeJSON(w, map[string]string{"error": fmt.Sprintf("Password hash error: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
// Map country code to name
|
||||
countryName := "Germany"
|
||||
phoneCode := "49"
|
||||
switch params.Country {
|
||||
case "AT":
|
||||
countryName = "Austria"
|
||||
phoneCode = "43"
|
||||
case "CH":
|
||||
countryName = "Switzerland"
|
||||
phoneCode = "41"
|
||||
}
|
||||
|
||||
// Run the seed with user-provided data
|
||||
setupCfg := service.SetupConfig{
|
||||
CompanyName: params.CompanyName,
|
||||
Street: params.Street,
|
||||
Zip: params.Zip,
|
||||
City: params.City,
|
||||
CountryCode: params.Country,
|
||||
CountryName: countryName,
|
||||
PhoneCode: phoneCode,
|
||||
Email: params.Email,
|
||||
Phone: params.Phone,
|
||||
VAT: params.VAT,
|
||||
Chart: params.Chart,
|
||||
AdminLogin: params.AdminEmail,
|
||||
AdminPassword: hashedPw,
|
||||
DemoData: params.DemoData,
|
||||
}
|
||||
|
||||
if err := service.SeedWithSetup(context.Background(), s.pool, setupCfg); err != nil {
|
||||
log.Printf("setup: error: %v", err)
|
||||
writeJSON(w, map[string]string{"error": fmt.Sprintf("Setup error: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("setup: database initialized successfully for %q", params.CompanyName)
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
</html>` + fmt.Sprintf("<!-- generated at %s -->", time.Now().Format(time.RFC3339))
|
||||
|
||||
@@ -66,9 +66,10 @@ func loadAssetList(name string, fs embed.FS) []string {
|
||||
// handleWebClient serves the Odoo webclient HTML shell.
|
||||
// Mirrors: odoo/addons/web/controllers/home.py Home.web_client()
|
||||
func (s *Server) handleWebClient(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if setup is needed
|
||||
// Check if database needs initialization
|
||||
// Mirrors: odoo/addons/web/controllers/home.py ensure_db()
|
||||
if s.isSetupNeeded() {
|
||||
http.Redirect(w, r, "/web/setup", http.StatusFound)
|
||||
http.Redirect(w, r, "/web/database/manager", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user