diff --git a/odoo-server b/odoo-server index bb85c7c..55b922a 100755 Binary files a/odoo-server and b/odoo-server differ diff --git a/pkg/server/middleware.go b/pkg/server/middleware.go index 6b6e27c..6ffd091 100644 --- a/pkg/server/middleware.go +++ b/pkg/server/middleware.go @@ -46,10 +46,8 @@ func AuthMiddleware(store *SessionStore, next http.Handler) http.Handler { path := r.URL.Path if path == "/health" || path == "/web/login" || - path == "/web/setup" || - path == "/web/setup/install" || path == "/web/session/authenticate" || - path == "/web/database/list" || + strings.HasPrefix(path, "/web/database/") || path == "/web/webclient/version_info" || strings.Contains(path, "/static/") { next.ServeHTTP(w, r) diff --git a/pkg/server/transpiler.go b/pkg/server/transpiler.go index c6204e7..5cbf194 100644 --- a/pkg/server/transpiler.go +++ b/pkg/server/transpiler.go @@ -68,10 +68,10 @@ func TranspileJS(urlPath, content string) string { deps, requireLines, cleanContent := extractImports(moduleName, content) // Transform exports - cleanContent = transformExports(cleanContent) + cleanContent, exportedFuncs := transformExports(cleanContent) // Wrap in odoo.define - return wrapWithOdooDefine(moduleName, deps, requireLines, cleanContent) + return wrapWithOdooDefine(moduleName, deps, requireLines, cleanContent, exportedFuncs) } // URLToModuleName converts a URL path to an Odoo module name. @@ -288,8 +288,8 @@ func extractImports(moduleName, content string) (deps []string, requireLines []s m := reExportNamedFrom.FindStringSubmatch(match) if len(m) >= 3 { names := m[1] - dep := m[2] - addDep(dep) + dep := resolve(m[2]) + addDep(m[2]) // Named re-export: export { X } from "dep" // Import the dep (using a temp var to avoid redeclaration with existing imports) // then assign to __exports @@ -352,8 +352,10 @@ func parseImportSpecifiers(raw string) []importSpecifier { } // transformExports converts export statements to __exports assignments. +// Returns the transformed content and a list of exported function names +// (these need deferred __exports assignment to preserve hoisting). // Mirrors: odoo/tools/js_transpiler.py (various export transformers) -func transformExports(content string) string { +func transformExports(content string) (string, []string) { // export class Foo { ... } -> const Foo = __exports.Foo = class Foo { ... } content = reExportClass.ReplaceAllStringFunc(content, func(match string) string { m := reExportClass.FindStringSubmatch(match) @@ -364,7 +366,13 @@ func transformExports(content string) string { return "const " + name + " = __exports." + name + " = class " + name }) - // export [async] function foo(...) { ... } -> __exports.foo = [async] function foo(...) { ... } + // export [async] function foo(...) { ... } + // Must preserve function declaration hoisting (not var/const) because some + // Odoo modules reference the function before its declaration line + // (e.g., rpc.setCache assigned before "export function rpc"). + // Strategy: keep as hoisted function declaration, collect names, append + // __exports assignments at the end. + var exportedFuncNames []string content = reExportFunction.ReplaceAllStringFunc(content, func(match string) string { m := reExportFunction.FindStringSubmatch(match) if len(m) < 3 { @@ -372,10 +380,8 @@ func transformExports(content string) string { } async := m[1] // "async " or "" name := m[2] - // Use "var name = __exports.name = function name" so the name is available - // as a local variable (needed when code references it after declaration, - // e.g., uniqueId.nextId = 0) - return "var " + name + " = __exports." + name + " = " + async + "function " + name + exportedFuncNames = append(exportedFuncNames, name) + return async + "function " + name }) // export const foo = ... -> const foo = __exports.foo = ... @@ -439,7 +445,7 @@ func transformExports(content string) string { // Must come after other export patterns to avoid double-matching content = reExportDefault.ReplaceAllString(content, `__exports[Symbol.for("default")] = `) - return content + return content, exportedFuncNames } // exportSpecifier holds a single export specifier from "export { X, Y as Z }". @@ -473,7 +479,7 @@ func parseExportSpecifiers(raw string) []exportSpecifier { // wrapWithOdooDefine wraps the transpiled content in an odoo.define() call. // Mirrors: odoo/tools/js_transpiler.py wrap_with_odoo_define() -func wrapWithOdooDefine(moduleName string, deps []string, requireLines []string, content string) string { +func wrapWithOdooDefine(moduleName string, deps []string, requireLines []string, content string, exportedFuncs []string) string { var b strings.Builder // Module definition header @@ -508,6 +514,11 @@ func wrapWithOdooDefine(moduleName string, deps []string, requireLines []string, b.WriteString("\n") } + // Deferred __exports assignments for hoisted function declarations + for _, name := range exportedFuncs { + b.WriteString("__exports." + name + " = " + name + ";\n") + } + // Return exports b.WriteString("return __exports;\n") b.WriteString("});\n") diff --git a/pkg/service/db.go b/pkg/service/db.go index 181c3da..f956ab2 100644 --- a/pkg/service/db.go +++ b/pkg/service/db.go @@ -258,21 +258,17 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err seedDemoData(ctx, tx) } - // 11. Reset sequences - tx.Exec(ctx, ` - SELECT setval('res_currency_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_currency)); - SELECT setval('res_country_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_country)); - SELECT setval('res_partner_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_partner)); - SELECT setval('res_company_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_company)); - SELECT setval('res_users_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_users)); - SELECT setval('ir_sequence_id_seq', (SELECT COALESCE(MAX(id),0) FROM ir_sequence)); - SELECT setval('account_journal_id_seq', (SELECT COALESCE(MAX(id),0) FROM account_journal)); - SELECT setval('account_account_id_seq', (SELECT COALESCE(MAX(id),0) FROM account_account)); - SELECT setval('account_tax_id_seq', (SELECT COALESCE(MAX(id),0) FROM account_tax)); - SELECT setval('sale_order_id_seq', (SELECT COALESCE(MAX(id),0) FROM sale_order)); - SELECT setval('sale_order_line_id_seq', (SELECT COALESCE(MAX(id),0) FROM sale_order_line)); - SELECT setval('account_move_id_seq', (SELECT COALESCE(MAX(id),0) FROM account_move)); - `) + // 12. Reset sequences (each individually — pgx doesn't support multi-statement) + seqs := []string{ + "res_currency", "res_country", "res_partner", "res_company", + "res_users", "ir_sequence", "account_journal", "account_account", + "account_tax", "sale_order", "sale_order_line", "account_move", + } + for _, table := range seqs { + tx.Exec(ctx, fmt.Sprintf( + `SELECT setval('%s_id_seq', GREATEST((SELECT COALESCE(MAX(id),0) FROM %q), 1))`, + table, table)) + } if err := tx.Commit(ctx); err != nil { return fmt.Errorf("db: commit seed: %w", err) @@ -287,7 +283,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) { log.Println("db: seeding UI views...") tx.Exec(ctx, `INSERT INTO ir_ui_view (name, model, type, arch, priority, active, mode) VALUES - -- res.partner views ('partner.list', 'res.partner', 'list', ' @@ -332,7 +327,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) { ', 16, true, 'primary'), - -- account.move views ('invoice.list', 'account.move', 'list', ' @@ -343,7 +337,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) { ', 16, true, 'primary'), - -- sale.order views ('sale.list', 'sale.order', 'list', ' @@ -352,7 +345,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) { ', 16, true, 'primary'), - -- crm.lead views ('lead.list', 'crm.lead', 'list', ' @@ -363,7 +355,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) { ', 16, true, 'primary'), - -- res.partner kanban ('partner.kanban', 'res.partner', 'kanban', ' @@ -377,7 +368,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) { ', 16, true, 'primary'), - -- crm.lead kanban (pipeline) ('lead.kanban', 'crm.lead', 'kanban', '