diff --git a/appinfo/info.xml b/appinfo/info.xml
index ccc72ed9..494958f8 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -22,7 +22,7 @@ Vrij en open source onder de EUPL-1.2-licentie.
**Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl.
]]>
- 0.3.3
+ 0.3.5
agpl
Conduction
OpenBuilt
diff --git a/lib/Controller/ApplicationVersionsController.php b/lib/Controller/ApplicationVersionsController.php
index 6e1f75b5..30e41d10 100644
--- a/lib/Controller/ApplicationVersionsController.php
+++ b/lib/Controller/ApplicationVersionsController.php
@@ -137,13 +137,17 @@ public function index(string $slug): JSONResponse
_multitenancy: false
)->getId();
+ // OR's searchObjects doesn't reliably filter by relation-string equality
+ // on the `application` field (the value is stored both inline AND in
+ // @self.relations, and the matcher matches neither shape consistently).
+ // Fetch every ApplicationVersion row and filter client-side by the
+ // parent Application UUID. Cheap — we expect ~3 rows per app.
$rows = $this->objectService->searchObjects(
query: [
- '@self' => [
+ '@self' => [
'register' => $registerId,
'schema' => $schemaId,
],
- 'application' => $applicationUuid,
]
);
@@ -152,10 +156,16 @@ public function index(string $slug): JSONResponse
$rowsList = $rows;
}
- $normalised = array_map(
- fn ($row): array => $this->normaliseObject(object: $row),
- $rowsList
- );
+ $normalised = [];
+ foreach ($rowsList as $row) {
+ $normalisedRow = $this->normaliseObject(object: $row);
+ $rowAppUuid = (string) ($normalisedRow['application'] ?? '');
+ if ($rowAppUuid !== $applicationUuid) {
+ continue;
+ }
+
+ $normalised[] = $normalisedRow;
+ }
return new JSONResponse(data: $normalised, statusCode: Http::STATUS_OK);
} catch (Throwable $e) {
diff --git a/lib/Repair/MigrateToVersionedModel.php b/lib/Repair/MigrateToVersionedModel.php
index bfc2642f..ac017c65 100644
--- a/lib/Repair/MigrateToVersionedModel.php
+++ b/lib/Repair/MigrateToVersionedModel.php
@@ -147,25 +147,37 @@ public function run(IOutput $output): void
*/
private function isAlreadyVersioned(): bool
{
- // Test 1 — does the versioned schema exist?
+ // The previous check was "does the versioned schema exist?" — but
+ // InitializeSettings imports the schema register BEFORE this step
+ // runs, so the `applicationVersion` schema is always present by
+ // the time we reach here. That made the short-circuit always fire
+ // and the migration always skip (issue #69).
+ //
+ // Correct check: examine the Application rows themselves. If any
+ // row carries a legacy top-level `manifest` / `version` / `status`
+ // / `currentVersion` field (the pre-spec-C shape), the install
+ // still has pre-migration data. If no Application rows exist OR
+ // all surviving rows already match the post-C shape, we're
+ // versioned.
try {
- $this->schemaMapper->find(self::VERSIONED_SCHEMA, _multitenancy: false);
- return true;
+ $applications = $this->enumerateApplications();
} catch (Throwable) {
- // Not found — fall through to Test 2.
+ // No openbuilt register or no Application schema — fresh
+ // install. Nothing to migrate.
+ return true;
}
- // Test 2 — do any Application rows carry the legacy `currentVersion` field?
- try {
- $applications = $this->enumerateApplications();
- } catch (Throwable) {
- // If the register or schema do not exist yet, we are on a
- // fresh install — no pre-migration data to migrate.
+ if ($applications === []) {
return true;
}
foreach ($applications as $row) {
- if (array_key_exists('currentVersion', $row) === true) {
+ // Pre-C shape keys we need to migrate away from.
+ if (array_key_exists('currentVersion', $row) === true
+ || array_key_exists('manifest', $row) === true
+ || array_key_exists('version', $row) === true
+ || array_key_exists('status', $row) === true
+ ) {
return false;
}
}
diff --git a/src/components/applicationDetail/ApplicationDetailHeader.vue b/src/components/applicationDetail/ApplicationDetailHeader.vue
index 9547825e..61c263c1 100644
--- a/src/components/applicationDetail/ApplicationDetailHeader.vue
+++ b/src/components/applicationDetail/ApplicationDetailHeader.vue
@@ -523,7 +523,14 @@ export default {
try {
const url = generateUrl(`/apps/openregister/api/objects/openbuilt/application/${encodeURIComponent(uuid)}`)
const { data } = await axios.get(url)
- this.application = (data && (data['@self'] ? { ...data, ...(data['@self'] || {}) } : data)) || null
+ // Keep user-visible fields from `data` and stash OR's internal
+ // metadata block separately. The previous merge spread `@self`
+ // OVER `data`, which overwrote `data.description` (the real
+ // app description) with `@self.description` (an internal OR
+ // metadata field carrying e.g. node IDs). See issue #73.
+ this.application = data
+ ? { ...data, '@self': data['@self'] || {} }
+ : null
this.loadVersions()
} catch (e) {
this.error = e instanceof Error ? e : new Error(String(e))
@@ -544,7 +551,13 @@ export default {
const list = Array.isArray(data)
? data
: (data && Array.isArray(data.results) ? data.results : [])
- this.versions = list
+ // OR's object-list shape carries the UUID in `id` (mirrored
+ // from `@self.id`) but the chain/pill logic reads `uuid`.
+ // Normalise once so every consumer sees `v.uuid`.
+ this.versions = list.map((v) => ({
+ ...v,
+ uuid: v.uuid || v.id || (v['@self'] && v['@self'].id) || null,
+ }))
const versionSlugFromRoute = (this.$route && this.$route.query && this.$route.query._version) || ''
const match = versionSlugFromRoute
diff --git a/src/store/schemas.js b/src/store/schemas.js
index 59f36ef1..841773f1 100644
--- a/src/store/schemas.js
+++ b/src/store/schemas.js
@@ -27,12 +27,21 @@ import { createObjectStore } from '@conduction/nextcloud-vue'
const STORE_ID = 'openbuilt-schemas'
-// Runtime schema CRUD lives under /api/registers (chain #3) — not the
-// /api/objects surface that backs ordinary OR object CRUD.
-const RUNTIME_SCHEMA_BASE_URL = '/apps/openregister/api/registers'
+// We point at OR's existing schemas CRUD (`/api/schemas/{id}`) because
+// the proposed runtime per-register schema endpoint (`/api/registers/{r}/
+// schemas/{slug}`) hasn't shipped on the OR floor yet (spec C chain #3).
+//
+// `useObjectStore._buildUrl` concatenates `${baseUrl}/${register}/${schema}
+// /${id}`. We satisfy that template by splitting `/apps/openregister`,
+// `api`, and `schemas` across the baseUrl + register + schema slots, then
+// fetching by slug in the id position. The store's `register` slot is no
+// longer carrying the OR register name; we keep the per-version register
+// info on the type config under `slugs.registerSlug` for callers that
+// need to filter the collection.
+const SCHEMA_API_BASE_URL = '/apps/openregister'
const useSchemasStoreRaw = createObjectStore(STORE_ID, {
- baseUrl: RUNTIME_SCHEMA_BASE_URL,
+ baseUrl: SCHEMA_API_BASE_URL,
})
/**
@@ -77,14 +86,16 @@ export function useSchemasStore(appSlug, versionSlug) {
const register = registerSlugForApp(appSlug, versionSlug)
const type = 'schema'
- // Register the object type once per (store, type, register) tuple. The base
- // store records the register/schema pair under the type slug; here
- // we use literal `schema` for both because chain #3's endpoint shape
- // expects `/registers/{register}/schemas[/{slug}]`.
- // Re-register when register changes (different version selected).
+ // Slot the URL segments so `_buildUrl` produces
+ // `/apps/openregister/api/schemas[/{slug}]`
+ // (OR's existing schemas CRUD).
+ // We keep the per-version OR register name on `slugs.registerSlug`
+ // so the consumer can filter the global schema collection client-side
+ // to only the schemas owned by the selected version's register.
if (!store.objectTypeRegistry[type]
- || store.objectTypeRegistry[type].register !== register) {
- store.registerObjectType(type, 'schemas', register)
+ || !store.objectTypeRegistry[type].slugs
+ || store.objectTypeRegistry[type].slugs.registerSlug !== register) {
+ store.registerObjectType(type, 'schemas', 'api', { registerSlug: register })
}
return store
}
diff --git a/src/views/SchemaDesigner.vue b/src/views/SchemaDesigner.vue
index f727b6df..58654533 100644
--- a/src/views/SchemaDesigner.vue
+++ b/src/views/SchemaDesigner.vue
@@ -329,7 +329,19 @@ export default {
this.loadingList = true
try {
const results = await this.store.fetchCollection(SCHEMA_TYPE)
- this.schemas = Array.isArray(results) ? results : []
+ const all = Array.isArray(results) ? results : []
+ // OR's schemas endpoint returns every schema in the
+ // organisation. Filter to the namespaced subset that
+ // belongs to this app+version register so the designer
+ // only shows the user's relevant schemas. Per the wizard
+ // (issue #71) seed slugs are `{appSlug}-{versionSlug}-X`.
+ const prefix = this.versionSlug
+ ? `${this.appSlug}-${this.versionSlug}-`
+ : `${this.appSlug}-`
+ this.schemas = all.filter((s) => {
+ const slug = s.slug || (s['@self'] && s['@self'].slug) || ''
+ return typeof slug === 'string' && slug.startsWith(prefix)
+ })
const err = this.store.errors[SCHEMA_TYPE]
if (err) {
showError(this.t('openbuilt', 'Failed to load schemas: {error}', { error: err }))