diff --git a/distribution/build.gradle.kts b/distribution/build.gradle.kts new file mode 100644 index 0000000..45dea42 --- /dev/null +++ b/distribution/build.gradle.kts @@ -0,0 +1,52 @@ +// distribution — assembles the runnable vibe_erp fat-jar. +// +// This module is the only one that pulls every PBC and platform module +// together into a bootable Spring Boot application. It is referenced by +// the Dockerfile (stage 1) which produces the shipping image documented +// in the architecture spec, sections 10 and 11. + +plugins { + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.spring) + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependency.management) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } +} + +dependencies { + implementation(project(":platform:platform-bootstrap")) + implementation(project(":platform:platform-persistence")) + implementation(project(":platform:platform-plugins")) + implementation(project(":pbc:pbc-identity")) + + implementation(libs.spring.boot.starter) + implementation(libs.spring.boot.starter.web) + implementation(libs.spring.boot.starter.data.jpa) + implementation(libs.spring.boot.starter.actuator) + implementation(libs.kotlin.stdlib) + implementation(libs.kotlin.reflect) + implementation(libs.jackson.module.kotlin) + runtimeOnly(libs.postgres) + runtimeOnly(libs.liquibase.core) + + testImplementation(libs.spring.boot.starter.test) +} + +// The fat-jar produced here is what the Dockerfile copies into the +// runtime image as /app/vibe-erp.jar. +tasks.bootJar { + mainClass.set("org.vibeerp.platform.bootstrap.VibeErpApplicationKt") + archiveFileName.set("vibe-erp.jar") +} + +// `./gradlew :distribution:bootRun` — used by `make run` for local dev. +// Activates the dev profile so application-dev.yaml on the classpath is +// layered on top of application.yaml. +tasks.bootRun { + systemProperty("spring.profiles.active", "dev") +} diff --git a/distribution/src/main/resources/application-dev.yaml b/distribution/src/main/resources/application-dev.yaml new file mode 100644 index 0000000..0d66f6f --- /dev/null +++ b/distribution/src/main/resources/application-dev.yaml @@ -0,0 +1,21 @@ +# vibe_erp — developer overrides (active under -Dspring.profiles.active=dev). +# +# Activated by `./gradlew :distribution:bootRun` (see distribution/build.gradle.kts). +# These values point at a local Postgres started via `make up` or any other +# locally-running Postgres on port 5432. + +spring: + datasource: + url: jdbc:postgresql://localhost:5432/vibeerp + username: vibeerp + password: vibeerp + +vibeerp: + plugins: + directory: ./plugins-dev + files: + local-path: ./files-dev + +logging: + level: + org.vibeerp: DEBUG diff --git a/distribution/src/main/resources/application.yaml b/distribution/src/main/resources/application.yaml new file mode 100644 index 0000000..f6cb728 --- /dev/null +++ b/distribution/src/main/resources/application.yaml @@ -0,0 +1,56 @@ +# vibe_erp — production-ish defaults. +# +# This is the baseline configuration baked into the shipping image. It is +# deliberately non-secret: every sensitive value is read from an environment +# variable so the same image works for self-hosted and (eventually) hosted +# deployments. See architecture spec sections 10 and 11. +# +# Customer overrides live in /opt/vibe-erp/config/vibe-erp.yaml on the +# mounted volume. Plug-in configuration lives in metadata__plugin_config, +# never here. + +spring: + application: + name: vibe-erp + datasource: + url: ${VIBEERP_DB_URL} + username: ${VIBEERP_DB_USER} + password: ${VIBEERP_DB_PASSWORD} + driver-class-name: org.postgresql.Driver + jpa: + # Liquibase owns the schema; Hibernate must never touch DDL. + hibernate: + ddl-auto: validate + open-in-view: false + liquibase: + change-log: classpath:db/changelog/master.xml + +server: + port: 8080 + shutdown: graceful + +management: + endpoints: + web: + exposure: + include: health,info,prometheus + +vibeerp: + instance: + mode: ${VIBEERP_INSTANCE_MODE:self-hosted} + default-tenant: ${VIBEERP_DEFAULT_TENANT:default} + plugins: + directory: ${VIBEERP_PLUGINS_DIR:/opt/vibe-erp/plugins} + auto-load: true + i18n: + default-locale: en-US + fallback-locale: en-US + available-locales: en-US,zh-CN,de-DE,ja-JP,es-ES + files: + backend: local + local-path: ${VIBEERP_FILES_DIR:/opt/vibe-erp/files} + +logging: + level: + org.vibeerp: INFO + org.springframework: WARN diff --git a/distribution/src/main/resources/db/changelog/master.xml b/distribution/src/main/resources/db/changelog/master.xml new file mode 100644 index 0000000..32931c9 --- /dev/null +++ b/distribution/src/main/resources/db/changelog/master.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/distribution/src/main/resources/db/changelog/pbc-identity/001-identity-init.xml b/distribution/src/main/resources/db/changelog/pbc-identity/001-identity-init.xml new file mode 100644 index 0000000..c2284f5 --- /dev/null +++ b/distribution/src/main/resources/db/changelog/pbc-identity/001-identity-init.xml @@ -0,0 +1,125 @@ + + + + + + + Create identity__user table + + CREATE TABLE identity__user ( + id uuid PRIMARY KEY, + tenant_id varchar(64) NOT NULL, + username varchar(128) NOT NULL, + display_name varchar(256) NOT NULL, + email varchar(320), + enabled boolean NOT NULL DEFAULT true, + ext jsonb NOT NULL DEFAULT '{}'::jsonb, + created_at timestamptz NOT NULL, + created_by varchar(128) NOT NULL, + updated_at timestamptz NOT NULL, + updated_by varchar(128) NOT NULL, + version bigint NOT NULL DEFAULT 0 + ); + CREATE UNIQUE INDEX identity__user_tenant_username_uk + ON identity__user (tenant_id, username); + CREATE INDEX identity__user_ext_gin + ON identity__user USING GIN (ext jsonb_path_ops); + + + DROP TABLE identity__user; + + + + + Enable Row-Level Security on identity__user (advisory until RlsTransactionHook lands) + + ALTER TABLE identity__user ENABLE ROW LEVEL SECURITY; + CREATE POLICY identity__user_tenant_isolation ON identity__user + USING (tenant_id = current_setting('vibeerp.current_tenant', true)); + + + DROP POLICY IF EXISTS identity__user_tenant_isolation ON identity__user; + ALTER TABLE identity__user DISABLE ROW LEVEL SECURITY; + + + + + Create identity__role table + + CREATE TABLE identity__role ( + id uuid PRIMARY KEY, + tenant_id varchar(64) NOT NULL, + code varchar(64) NOT NULL, + name varchar(256) NOT NULL, + description text, + ext jsonb NOT NULL DEFAULT '{}'::jsonb, + created_at timestamptz NOT NULL, + created_by varchar(128) NOT NULL, + updated_at timestamptz NOT NULL, + updated_by varchar(128) NOT NULL, + version bigint NOT NULL DEFAULT 0 + ); + CREATE UNIQUE INDEX identity__role_tenant_code_uk + ON identity__role (tenant_id, code); + CREATE INDEX identity__role_ext_gin + ON identity__role USING GIN (ext jsonb_path_ops); + ALTER TABLE identity__role ENABLE ROW LEVEL SECURITY; + CREATE POLICY identity__role_tenant_isolation ON identity__role + USING (tenant_id = current_setting('vibeerp.current_tenant', true)); + + + DROP TABLE identity__role; + + + + + Create identity__user_role join table + + CREATE TABLE identity__user_role ( + id uuid PRIMARY KEY, + tenant_id varchar(64) NOT NULL, + user_id uuid NOT NULL REFERENCES identity__user(id) ON DELETE CASCADE, + role_id uuid NOT NULL REFERENCES identity__role(id) ON DELETE CASCADE, + created_at timestamptz NOT NULL, + created_by varchar(128) NOT NULL, + updated_at timestamptz NOT NULL, + updated_by varchar(128) NOT NULL, + version bigint NOT NULL DEFAULT 0 + ); + CREATE UNIQUE INDEX identity__user_role_uk + ON identity__user_role (tenant_id, user_id, role_id); + ALTER TABLE identity__user_role ENABLE ROW LEVEL SECURITY; + CREATE POLICY identity__user_role_tenant_isolation ON identity__user_role + USING (tenant_id = current_setting('vibeerp.current_tenant', true)); + + + DROP TABLE identity__user_role; + + + + diff --git a/distribution/src/main/resources/db/changelog/platform/000-platform-init.xml b/distribution/src/main/resources/db/changelog/platform/000-platform-init.xml new file mode 100644 index 0000000..536378a --- /dev/null +++ b/distribution/src/main/resources/db/changelog/platform/000-platform-init.xml @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +