Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b199b24f01
|
||
|
|
b5308a8bab | ||
|
|
0caaa3717e | ||
|
|
464db76bc3 | ||
|
|
19a06166f5 | ||
|
|
61060e2793
|
||
|
|
4e88ed8303 | ||
|
|
7a7b804106
|
||
|
|
4c95151a86 | ||
|
|
41bbf2c428
|
||
|
|
2f6be765f2
|
||
|
|
92dd901409 | ||
|
|
6a4ea584f5
|
||
|
|
03b3bbaca2 |
No files matched your search
+11
-57
@@ -1,60 +1,14 @@
|
|||||||
version: 2.1
|
version: 2.1
|
||||||
|
setup: true
|
||||||
orbs:
|
orbs:
|
||||||
qqq-orb: kingsrook/qqq-orb@0.6.9
|
qqq-orb: kingsrook/qqq-orb@0.7.12
|
||||||
|
|
||||||
commands:
|
|
||||||
configure-package-install:
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Keep package installation unattended
|
|
||||||
command: |
|
|
||||||
# These build machines are discarded after the job.
|
|
||||||
sudo install -d /etc/needrestart/conf.d
|
|
||||||
printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
test_only:
|
setup:
|
||||||
jobs:
|
jobs:
|
||||||
- qqq-orb/mvn_test_only:
|
- qqq-orb/generate_pipeline:
|
||||||
pre-steps:
|
orb_version: 0.7.12
|
||||||
- configure-package-install
|
filters:
|
||||||
context: [qqq-maven-registry-credentials]
|
branches:
|
||||||
filters:
|
ignore: automation/qbit-rc-pending
|
||||||
branches:
|
tags:
|
||||||
ignore: /(develop|release\/.*)/
|
only: /^v\d+\.\d+\.\d+$/
|
||||||
|
|
||||||
publish_snapshot:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
pre-steps:
|
|
||||||
- configure-package-install
|
|
||||||
context: [qqq-maven-registry-credentials]
|
|
||||||
branch_type: snapshot
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: [develop]
|
|
||||||
|
|
||||||
publish_release_candidate:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
pre-steps:
|
|
||||||
- configure-package-install
|
|
||||||
context: [qqq-maven-registry-credentials]
|
|
||||||
branch_type: release_candidate
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: [/release\/.*/]
|
|
||||||
|
|
||||||
publish_release:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
pre-steps:
|
|
||||||
- configure-package-install
|
|
||||||
context: [qqq-maven-registry-credentials]
|
|
||||||
branch_type: release
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
tags:
|
|
||||||
only: [/^v\d+\.\d+\.\d+$/]
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: CodeQL
|
||||||
|
'on':
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
- release/**
|
||||||
|
- hotfix/**
|
||||||
|
pull_request: {}
|
||||||
|
schedule:
|
||||||
|
- cron: 41 16 * * 0
|
||||||
|
workflow_dispatch: {}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
security-events: write
|
||||||
|
packages: read
|
||||||
|
concurrency:
|
||||||
|
group: codeql-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
|
||||||
@@ -1,25 +1,74 @@
|
|||||||
# Cut the next release candidate (QRun-IO/qqq docs/QRUN-QBIT-REPO-STANDARD.md, C6):
|
|
||||||
# run this workflow by hand, or merge a develop PR labelled `release:rc`. It merges develop into
|
|
||||||
# the release line and the release/* push runs CircleCI publish_release_candidate.
|
|
||||||
name: Release candidate
|
name: Release candidate
|
||||||
|
'on':
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
dry_run:
|
dry_run:
|
||||||
description: Print the plan without merging or creating branches
|
description: Plan a cut without changing release state
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
finalize:
|
||||||
|
description: Retry finalization of an existing cut; do not cut another RC
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
recover:
|
||||||
|
description: Recover the same published RC after a deployment timeout; never
|
||||||
|
reupload
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
circle_build:
|
||||||
|
description: Original Circle publisher build number (recovery only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
merge_sha:
|
||||||
|
description: Existing cut merge_sha (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
bump_sha:
|
||||||
|
description: Existing cut bump_sha (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
branch:
|
||||||
|
description: Existing cut branch (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
version:
|
||||||
|
description: Existing cut version (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [closed]
|
types:
|
||||||
branches: [develop]
|
- closed
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
repository_dispatch:
|
||||||
|
types:
|
||||||
|
- qbit-rc-published
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
actions: read
|
||||||
|
checks: read
|
||||||
jobs:
|
jobs:
|
||||||
release-rc:
|
release-rc:
|
||||||
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release:rc'))
|
if: github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch'
|
||||||
uses: QRun-IO/.github/.github/workflows/qbit-release-rc.yml@main
|
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
|
||||||
|
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
|
||||||
|
'release:rc'))
|
||||||
|
uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@cc30f599e63c8e41629c99d23395bac1adb8bc44
|
||||||
with:
|
with:
|
||||||
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
|
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
|
||||||
|
}}
|
||||||
|
finalize: ${{ github.event_name == 'repository_dispatch' || (github.event_name
|
||||||
|
== 'workflow_dispatch' && inputs.finalize) }}
|
||||||
|
recover: ${{ github.event_name == 'workflow_dispatch' && inputs.recover || false
|
||||||
|
}}
|
||||||
|
circle_build: ${{ github.event_name == 'workflow_dispatch' && inputs.circle_build
|
||||||
|
|| '' }}
|
||||||
|
merge_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.merge_sha
|
||||||
|
|| inputs.merge_sha || '' }}
|
||||||
|
bump_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.bump_sha
|
||||||
|
|| inputs.bump_sha || '' }}
|
||||||
|
branch: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch
|
||||||
|
|| inputs.branch || '' }}
|
||||||
|
version: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.version
|
||||||
|
|| inputs.version || '' }}
|
||||||
|
secrets: inherit
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
profile: qbit
|
||||||
|
orb_version: 0.7.12
|
||||||
|
default_branch: develop
|
||||||
|
java_version: '21'
|
||||||
|
browsers: true
|
||||||
|
api_checks: false
|
||||||
|
docs_only: false
|
||||||
|
release_on_main: false
|
||||||
|
release_tag_policy: stable
|
||||||
|
test_branch_policy: non-release
|
||||||
|
publish:
|
||||||
|
snapshot: true
|
||||||
|
release_candidate: true
|
||||||
|
release: true
|
||||||
|
asciidoc: false
|
||||||
|
release_completion: true
|
||||||
@@ -13,6 +13,8 @@ All notable changes are documented here, following [Keep a Changelog](https://ke
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- UI API extension collaboration 1.1.0 (requires QQQ 4.1.0-RC.15, UI API 1.6.0): operationIds, standard error responses with BasicErrorResponseV1, extension tags and published presence request/snapshot/viewer schemas to resolve dangling references. Retain CollaborationErrorV1 as deprecated.
|
||||||
|
|
||||||
- Upgrade the default QQQ BOM to `4.1.0-RC.7` from Maven Central.
|
- Upgrade the default QQQ BOM to `4.1.0-RC.7` from Maven Central.
|
||||||
- Register the core followers provider for alerts and other qbits, with event preferences, future-mute exclusion, system reads scoped to one record, and caller-owned recipient access checks.
|
- Register the core followers provider for alerts and other qbits, with event preferences, future-mute exclusion, system reads scoped to one record, and caller-owned recipient access checks.
|
||||||
- Cache favorite/recent record labels only for FULL field access. Resolve comment/share notification labels per recipient with a table-label-and-id fallback. Use the typed core label-access API for email labels as well.
|
- Cache favorite/recent record labels only for FULL field access. Resolve comment/share notification labels per recipient with a table-label-and-id fallback. Use the typed core label-access API for email labels as well.
|
||||||
@@ -24,6 +26,8 @@ All notable changes are documented here, following [Keep a Changelog](https://ke
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- QBit metadata reports the Maven artifact version from a filtered resource, including release versions.
|
||||||
|
|
||||||
- Timeline audit summaries read "Edited by Alice Morgan: Title, Total" or "Created by Alice Morgan" instead of "Record was Edited"; detail rows are unchanged.
|
- Timeline audit summaries read "Edited by Alice Morgan: Title, Total" or "Created by Alice Morgan" instead of "Record was Edited"; detail rows are unchanged.
|
||||||
- The demo server chooses its user per browser session (`?user=bob`, remembered in a cookie) instead of once per JVM, and sends the person's name to the frontend so it no longer shows Anonymous.
|
- The demo server chooses its user per browser session (`?user=bob`, remembered in a cookie) instead of once per JVM, and sends the person's name to the frontend so it no longer shows Anonymous.
|
||||||
- The share recipient search and mention candidates no longer offer the caller to themself.
|
- The share recipient search and mention candidates no longer offer the caller to themself.
|
||||||
|
|||||||
@@ -410,13 +410,13 @@ The runtime provider adds this field and its search entry where TAGS is enabled.
|
|||||||
|
|
||||||
## UI API
|
## UI API
|
||||||
|
|
||||||
The producer registers extension `collaboration` version `1.0.0` at
|
The producer registers extension `collaboration` version `1.1.0` at
|
||||||
`/qqq/v1/qbit/collaboration`. It uses the signed-in user's session and existing
|
`/qqq/v1/qbit/collaboration`. It uses the signed-in user's session and existing
|
||||||
services. Disable registration with `new CollaborationQBitConfig().withUiApi(false)`.
|
services. Disable registration with `new CollaborationQBitConfig().withUiApi(false)`.
|
||||||
Presence, shares, tasks, my-work, direct upload and thumbnail routes are not part
|
Requires QQQ `4.1.0-RC.15` and UI API `1.6.0`. Operations carry stable operationIds,
|
||||||
of this extension version.
|
the collaboration tag and standard error responses.
|
||||||
|
|
||||||
All successes return HTTP 200. All bodies have named `Collaboration*V1` schemas
|
All successes return HTTP 200. Success and request bodies have named `Collaboration*V1` schemas
|
||||||
in [the extension contract](src/main/resources/openapi/qqq-v1/collaboration.yaml).
|
in [the extension contract](src/main/resources/openapi/qqq-v1/collaboration.yaml).
|
||||||
The server publishes these alongside core at `/qqq/v1/openapi.yaml` and `.json`.
|
The server publishes these alongside core at `/qqq/v1/openapi.yaml` and `.json`.
|
||||||
|
|
||||||
@@ -478,9 +478,11 @@ attachment filename; preview requests inline content. QQQ's current OpenAPI
|
|||||||
`Response` model cannot declare the `Location` header, so this contract uses the
|
`Response` model cannot declare the `Location` header, so this contract uses the
|
||||||
JSON URL alternative consistently.
|
JSON URL alternative consistently.
|
||||||
|
|
||||||
A missing or invisible parent returns identical `404 {"error":"Not found"}`.
|
A missing or invisible parent returns 404 with the same `Not found` error message.
|
||||||
Missing write permissions return 403; invalid input returns 400 with an `error`
|
Missing write permissions return 403; invalid input returns 400 through QQQ's
|
||||||
message through QQQ's exception mapper. Provider/backend failures remain server
|
exception mapper. Errors use `BasicErrorResponseV1` with `error`, `code`, `message`,
|
||||||
|
`traceId` and optional `fieldErrors`. `CollaborationErrorV1` remains defined but is
|
||||||
|
deprecated since 1.1.0. Provider/backend failures remain server
|
||||||
errors. Services continue to own author, record-lock and permission checks.
|
errors. Services continue to own author, record-lock and permission checks.
|
||||||
Mention candidates expose `canRead` as the string enum `"true"`, `"false"`,
|
Mention candidates expose `canRead` as the string enum `"true"`, `"false"`,
|
||||||
`"unknown"`; unknown does not imply the recipient can read the record.
|
`"unknown"`; unknown does not imply the recipient can read the record.
|
||||||
@@ -636,7 +638,7 @@ New routes, all under `/qqq/v1/qbit/collaboration`:
|
|||||||
| PUT | `/records/{table}/{pk}/assignment` |
|
| PUT | `/records/{table}/{pk}/assignment` |
|
||||||
| GET | `/my-work` |
|
| GET | `/my-work` |
|
||||||
|
|
||||||
Typed `CollaborationWorkSchemas` describe requests and results. Task PATCH preserves omitted fields, accepts OPEN/DONE/CANCELLED transitions, and permits explicit null for optional fields; null `remindAt` selects the service's due-date default. Assignment PUT replaces owner and assignee; null clears. Invalid input is 400, denied visible-record operations are 403, and missing/invisible parents are 404. Access requests deliberately never inspect parent existence. The extension stays at unreleased **1.0.0**; contract tooling accepts these additions without a bump.
|
Typed `CollaborationWorkSchemas` describe requests and results. Task PATCH preserves omitted fields, accepts OPEN/DONE/CANCELLED transitions, and permits explicit null for optional fields; null `remindAt` selects the service's due-date default. Assignment PUT replaces owner and assignee; null clears. Invalid input is 400, denied visible-record operations are 403, and missing/invisible parents are 404. Access requests deliberately never inspect parent existence. The extension is now **1.1.0**, including the UI API 1.6.0 error contract and published presence schemas.
|
||||||
|
|
||||||
`CollaborationRealtime` publishes `comment.added/updated/deleted`, `attachment.changed`, `tags.changed` and `task.changed` on `record:{table}:{pk}` after commit. Successful notification delivery publishes `notification.created` on `user:{userId}`. Payloads contain identifiers only. `CollaborationFollowersProvider` supplies the core followers SPI for alerts and other consumers. Selectable Tags values remain unavailable because the overlay has no output hook.
|
`CollaborationRealtime` publishes `comment.added/updated/deleted`, `attachment.changed`, `tags.changed` and `task.changed` on `record:{table}:{pk}` after commit. Successful notification delivery publishes `notification.created` on `user:{userId}`. Payloads contain identifiers only. `CollaborationFollowersProvider` supplies the core followers SPI for alerts and other consumers. Selectable Tags values remain unavailable because the overlay has no output hook.
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@
|
|||||||
<filtering>true</filtering>
|
<filtering>true</filtering>
|
||||||
<includes>
|
<includes>
|
||||||
<include>help/*.yaml</include>
|
<include>help/*.yaml</include>
|
||||||
|
<include>qbit-collaboration.properties</include>
|
||||||
</includes>
|
</includes>
|
||||||
</resource>
|
</resource>
|
||||||
<resource>
|
<resource>
|
||||||
@@ -127,6 +128,7 @@
|
|||||||
<filtering>false</filtering>
|
<filtering>false</filtering>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>help/*.yaml</exclude>
|
<exclude>help/*.yaml</exclude>
|
||||||
|
<exclude>qbit-collaboration.properties</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</resource>
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -223,14 +225,14 @@
|
|||||||
<profiles>
|
<profiles>
|
||||||
<!-- Built against qqq 4.1 (QBitMetaDataProducer). Until qbit-build-parent -->
|
<!-- Built against qqq 4.1 (QBitMetaDataProducer). Until qbit-build-parent -->
|
||||||
<!-- imports the released 4.1 BOM, this profile is active by default and -->
|
<!-- imports the released 4.1 BOM, this profile is active by default and -->
|
||||||
<!-- imports qqq-bom-pom 4.1.0-RC.7 ahead of the parent's 4.0.0 BOM. -->
|
<!-- imports qqq-bom-pom 4.1.0-RC.15 ahead of the parent's 4.0.0 BOM. -->
|
||||||
<profile>
|
<profile>
|
||||||
<id>qqq-snapshot</id>
|
<id>qqq-snapshot</id>
|
||||||
<activation>
|
<activation>
|
||||||
<activeByDefault>true</activeByDefault>
|
<activeByDefault>true</activeByDefault>
|
||||||
</activation>
|
</activation>
|
||||||
<properties>
|
<properties>
|
||||||
<qqq.snapshot.version>4.1.0-RC.7</qqq.snapshot.version>
|
<qqq.snapshot.version>4.1.0-RC.15</qqq.snapshot.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
package com.kingsrook.qbits.collaboration;
|
package com.kingsrook.qbits.collaboration;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Properties;
|
||||||
import com.kingsrook.qbits.collaboration.app.CollaborationHome;
|
import com.kingsrook.qbits.collaboration.app.CollaborationHome;
|
||||||
import com.kingsrook.qbits.collaboration.attachments.DeleteAttachmentsProcess;
|
import com.kingsrook.qbits.collaboration.attachments.DeleteAttachmentsProcess;
|
||||||
import com.kingsrook.qbits.collaboration.model.CollaborationTables;
|
import com.kingsrook.qbits.collaboration.model.CollaborationTables;
|
||||||
@@ -44,13 +47,41 @@ public class CollaborationQBitProducer implements QBitMetaDataProducer<Collabora
|
|||||||
{
|
{
|
||||||
public static final String GROUP_ID = "com.kingsrook.qbits";
|
public static final String GROUP_ID = "com.kingsrook.qbits";
|
||||||
public static final String ARTIFACT_ID = "qbit-collaboration";
|
public static final String ARTIFACT_ID = "qbit-collaboration";
|
||||||
public static final String VERSION = "0.1.0";
|
public static final String VERSION = loadVersion();
|
||||||
|
|
||||||
private CollaborationQBitConfig qBitConfig;
|
private CollaborationQBitConfig qBitConfig;
|
||||||
private String namespace;
|
private String namespace;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Read the artifact version from the Maven-filtered properties resource.
|
||||||
|
*******************************************************************************/
|
||||||
|
private static String loadVersion()
|
||||||
|
{
|
||||||
|
try(InputStream inputStream = CollaborationQBitProducer.class.getClassLoader().getResourceAsStream("qbit-collaboration.properties"))
|
||||||
|
{
|
||||||
|
if(inputStream == null)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Missing qbit-collaboration.properties; build with Maven to generate the artifact version.");
|
||||||
|
}
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.load(inputStream);
|
||||||
|
String version = properties.getProperty("version");
|
||||||
|
if(version == null || version.isBlank() || version.contains("${"))
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Missing or unfiltered version in qbit-collaboration.properties; build with Maven resource filtering.");
|
||||||
|
}
|
||||||
|
return (version);
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Could not read the artifact version from qbit-collaboration.properties.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Configure the qbit.
|
** Configure the qbit.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public final class CollaborationApi
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static MiddlewareSpecExtension extension()
|
public static MiddlewareSpecExtension extension()
|
||||||
{
|
{
|
||||||
return (new MiddlewareSpecExtension().withName("collaboration").withSemanticVersion("1.0.0")
|
return (new MiddlewareSpecExtension().withName("collaboration").withSemanticVersion("1.1.0")
|
||||||
.withDescription("Record collaboration: favorites, recents, attachments, tags, conversations, following, timeline, people and presence")
|
.withDescription("Record collaboration: favorites, recents, attachments, tags, conversations, following, timeline, people and presence")
|
||||||
.withSpec(PostPresenceSpec.class).withSpec(DeletePresenceSpec.class).withSpec(GetPresenceSpec.class)
|
.withSpec(PostPresenceSpec.class).withSpec(DeletePresenceSpec.class).withSpec(GetPresenceSpec.class)
|
||||||
.withSpec(ListFavoritesSpec.class)
|
.withSpec(ListFavoritesSpec.class)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
|
|||||||
import com.kingsrook.qqq.middleware.javalin.specs.BasicOperation;
|
import com.kingsrook.qqq.middleware.javalin.specs.BasicOperation;
|
||||||
import com.kingsrook.qqq.middleware.javalin.specs.BasicResponse;
|
import com.kingsrook.qqq.middleware.javalin.specs.BasicResponse;
|
||||||
import com.kingsrook.qqq.middleware.javalin.specs.extensions.AbstractJsonEndpointSpec;
|
import com.kingsrook.qqq.middleware.javalin.specs.extensions.AbstractJsonEndpointSpec;
|
||||||
|
import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.BasicErrorResponseV1;
|
||||||
import com.kingsrook.qqq.openapi.model.HttpMethod;
|
import com.kingsrook.qqq.openapi.model.HttpMethod;
|
||||||
import com.kingsrook.qqq.openapi.model.In;
|
import com.kingsrook.qqq.openapi.model.In;
|
||||||
import com.kingsrook.qqq.openapi.model.Parameter;
|
import com.kingsrook.qqq.openapi.model.Parameter;
|
||||||
@@ -112,10 +113,10 @@ abstract class CollaborationJsonSpec extends AbstractJsonEndpointSpec
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
static List<BasicResponse> errors()
|
static List<BasicResponse> errors()
|
||||||
{
|
{
|
||||||
return (List.of(new BasicResponse(HttpStatus.BAD_REQUEST, "Invalid input", "CollaborationErrorV1"),
|
return (List.of(new BasicResponse(HttpStatus.BAD_REQUEST, "Invalid input", BasicErrorResponseV1.class.getSimpleName()),
|
||||||
new BasicResponse(HttpStatus.UNAUTHORIZED, "Sign in required", "CollaborationErrorV1"),
|
new BasicResponse(HttpStatus.UNAUTHORIZED, "Sign in required", BasicErrorResponseV1.class.getSimpleName()),
|
||||||
new BasicResponse(HttpStatus.FORBIDDEN, "Permission denied", "CollaborationErrorV1"),
|
new BasicResponse(HttpStatus.FORBIDDEN, "Permission denied", BasicErrorResponseV1.class.getSimpleName()),
|
||||||
new BasicResponse(HttpStatus.NOT_FOUND, "Not found (including invisible parents)", "CollaborationErrorV1")));
|
new BasicResponse(HttpStatus.NOT_FOUND, "Not found (including invisible parents)", BasicErrorResponseV1.class.getSimpleName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ public final class CollaborationSchemas
|
|||||||
@Override
|
@Override
|
||||||
public Schema toSchema()
|
public Schema toSchema()
|
||||||
{
|
{
|
||||||
return (new Schema().withType(Type.OBJECT).withDescription("Standard QQQ error response.").withProperties(new TreeMap<>(Map.ofEntries(
|
return (new Schema().withType(Type.OBJECT).withDescription("Deprecated since 1.1.0: errors use BasicErrorResponseV1.").withProperties(new TreeMap<>(Map.ofEntries(
|
||||||
Map.entry("error", new Schema().withType(Type.STRING).withDescription("Human-readable reason the operation failed.").withNullable(true))))));
|
Map.entry("error", new Schema().withType(Type.STRING).withDescription("Human-readable reason the operation failed.").withNullable(true))))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -714,6 +714,10 @@ public final class CollaborationSchemas
|
|||||||
schemas.put("CollaborationCommentPostRequestV1", new CollaborationCommentPostRequestV1(null, null, null).toSchema());
|
schemas.put("CollaborationCommentPostRequestV1", new CollaborationCommentPostRequestV1(null, null, null).toSchema());
|
||||||
schemas.put("CollaborationCommentEditRequestV1", new CollaborationCommentEditRequestV1(null).toSchema());
|
schemas.put("CollaborationCommentEditRequestV1", new CollaborationCommentEditRequestV1(null).toSchema());
|
||||||
schemas.put("CollaborationFollowRequestV1", new CollaborationFollowRequestV1(null, null).toSchema());
|
schemas.put("CollaborationFollowRequestV1", new CollaborationFollowRequestV1(null, null).toSchema());
|
||||||
|
schemas.put("CollaborationPresenceRequestV1", new CollaborationPresenceSchemas.CollaborationPresenceRequestV1(null, null, null, null, null).toSchema());
|
||||||
|
schemas.put("CollaborationPresenceLeaveV1", new CollaborationPresenceSchemas.CollaborationPresenceLeaveV1(null, null, null).toSchema());
|
||||||
|
schemas.put("CollaborationPresenceSnapshotV1", new CollaborationPresenceSchemas.CollaborationPresenceSnapshotV1(null, null, null).toSchema());
|
||||||
|
schemas.put("CollaborationPresenceViewerV1", new CollaborationPresenceSchemas.CollaborationPresenceViewerV1(null, null, null, null).toSchema());
|
||||||
schemas.putAll(CollaborationWorkSchemas.components());
|
schemas.putAll(CollaborationWorkSchemas.components());
|
||||||
return (schemas);
|
return (schemas);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1 @@
|
|||||||
|
version=${project.version}
|
||||||
@@ -29,6 +29,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
|||||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||||
import com.kingsrook.qqq.middleware.javalin.specs.extensions.MiddlewareSpecExtensionsMetaData;
|
import com.kingsrook.qqq.middleware.javalin.specs.extensions.MiddlewareSpecExtensionsMetaData;
|
||||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
|
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
|
||||||
|
import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.BasicErrorResponseV1;
|
||||||
import com.kingsrook.qqq.middleware.javalin.tools.UiApiContract;
|
import com.kingsrook.qqq.middleware.javalin.tools.UiApiContract;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@@ -56,7 +57,7 @@ class CollaborationApiContractTest
|
|||||||
assertThat(UiApiContract.check(generated, Files.readString(path), null, 1,
|
assertThat(UiApiContract.check(generated, Files.readString(path), null, 1,
|
||||||
"mvnw.cmd -o -q -Dtest=CollaborationApiContractTest -Dcollaboration.openapi.update=true test")).isEmpty();
|
"mvnw.cmd -o -q -Dtest=CollaborationApiContractTest -Dcollaboration.openapi.update=true test")).isEmpty();
|
||||||
var model = version.generateExtensionOpenAPIModel("qqq", extension);
|
var model = version.generateExtensionOpenAPIModel("qqq", extension);
|
||||||
assertThat(model.getComponents().getSchemas().keySet()).allMatch(name -> name.startsWith("Collaboration"));
|
assertThat(model.getComponents().getSchemas().keySet()).allMatch(name -> name.startsWith("Collaboration") || name.equals(BasicErrorResponseV1.class.getSimpleName()));
|
||||||
assertThat(model.getPaths().keySet()).allMatch(pathName -> pathName.startsWith("/qqq/v1/qbit/collaboration/"));
|
assertThat(model.getPaths().keySet()).allMatch(pathName -> pathName.startsWith("/qqq/v1/qbit/collaboration/"));
|
||||||
assertThat(generated).contains("multipart/form-data", "format: \"binary\"", "clients follow url", "nextCursor");
|
assertThat(generated).contains("multipart/form-data", "format: \"binary\"", "clients follow url", "nextCursor");
|
||||||
assertThat(extension.getSpecs()).hasSize(45);
|
assertThat(extension.getSpecs()).hasSize(45);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import java.util.UUID;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.kingsrook.qbits.collaboration.attachments.AttachmentStorage;
|
import com.kingsrook.qbits.collaboration.attachments.AttachmentStorage;
|
||||||
import com.kingsrook.qbits.objectstorage.api.PresignGet;
|
import com.kingsrook.qbits.objectstorage.api.PresignGet;
|
||||||
import com.kingsrook.qbits.objectstorage.api.PutRequest;
|
import com.kingsrook.qbits.objectstorage.api.PutRequest;
|
||||||
@@ -431,8 +432,16 @@ class CollaborationApiHttpTest extends PersonalServiceFixture
|
|||||||
HttpResponse<String> hidden = request("GET", "/records/document/3/" + feature, null, "alice");
|
HttpResponse<String> hidden = request("GET", "/records/document/3/" + feature, null, "alice");
|
||||||
HttpResponse<String> absent = request("GET", "/records/document/999/" + feature, null, "alice");
|
HttpResponse<String> absent = request("GET", "/records/document/999/" + feature, null, "alice");
|
||||||
assertThat(hidden.statusCode()).isEqualTo(404);
|
assertThat(hidden.statusCode()).isEqualTo(404);
|
||||||
assertThat(hidden.body()).isEqualTo(absent.body());
|
ObjectNode hiddenError = (ObjectNode) json(hidden, 404);
|
||||||
assertThat(json(hidden, 404).get("error").asText()).isEqualTo("Not found");
|
ObjectNode absentError = (ObjectNode) json(absent, 404);
|
||||||
|
assertThat(hiddenError.path("traceId").asText()).isNotBlank();
|
||||||
|
assertThat(absentError.path("traceId").asText()).isNotBlank();
|
||||||
|
hiddenError.remove("traceId");
|
||||||
|
absentError.remove("traceId");
|
||||||
|
assertThat(hiddenError).isEqualTo(absentError);
|
||||||
|
assertThat(hiddenError.path("error").asText()).isEqualTo("Not found");
|
||||||
|
assertThat(hiddenError.path("code").asText()).isEqualTo("NOT_FOUND");
|
||||||
|
assertThat(hiddenError.path("message").asText()).isEqualTo("Not found");
|
||||||
}
|
}
|
||||||
assertError(404, "POST", "/records/document/3/comments", "not json", "alice");
|
assertError(404, "POST", "/records/document/3/comments", "not json", "alice");
|
||||||
assertError(404, "GET", "/mentions/candidates?table=document&pk=3", null, "alice");
|
assertError(404, "GET", "/mentions/candidates?table=document&pk=3", null, "alice");
|
||||||
@@ -500,15 +509,19 @@ class CollaborationApiHttpTest extends PersonalServiceFixture
|
|||||||
JsonNode version = json(client.send(HttpRequest.newBuilder(URI.create(serverUrl + "/qqq/v1/version")).GET().build(),
|
JsonNode version = json(client.send(HttpRequest.newBuilder(URI.create(serverUrl + "/qqq/v1/version")).GET().build(),
|
||||||
HttpResponse.BodyHandlers.ofString()), 200);
|
HttpResponse.BodyHandlers.ofString()), 200);
|
||||||
assertThat(version.at("/extensions/0/name").asText()).isEqualTo("collaboration");
|
assertThat(version.at("/extensions/0/name").asText()).isEqualTo("collaboration");
|
||||||
assertThat(version.at("/extensions/0/version").asText()).isEqualTo("1.0.0");
|
assertThat(version.at("/extensions/0/version").asText()).isEqualTo("1.1.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Framework errors always contain a human-readable error property.
|
** Framework errors include the standard message, code and request trace.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void assertError(int status, String method, String path, String body, String caller) throws Exception
|
private void assertError(int status, String method, String path, String body, String caller) throws Exception
|
||||||
{
|
{
|
||||||
assertThat(json(request(method, path, body, caller), status).get("error").asText()).isNotBlank();
|
var error = json(request(method, path, body, caller), status);
|
||||||
|
assertThat(error.path("error").asText()).isNotBlank();
|
||||||
|
assertThat(error.path("code").asText()).isNotBlank();
|
||||||
|
assertThat(error.path("message").asText()).isNotBlank();
|
||||||
|
assertThat(error.path("traceId").asText()).isNotBlank();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|||||||
+1
-1
@@ -146,7 +146,7 @@ class CollaborationTablesGuardrailTest extends PersonalServiceFixture
|
|||||||
assertThat(HelpBundles.of(instance).getBundles()).singleElement().satisfies(bundle ->
|
assertThat(HelpBundles.of(instance).getBundles()).singleElement().satisfies(bundle ->
|
||||||
{
|
{
|
||||||
assertThat(bundle.getId()).isEqualTo("qbit-collaboration");
|
assertThat(bundle.getId()).isEqualTo("qbit-collaboration");
|
||||||
assertThat(bundle.getVersion()).as("the help bundle carries the artifact version").isEqualTo(System.getProperty("qbit.artifactVersion"));
|
assertThat(bundle.getVersion()).as("the help bundle carries the artifact version").isEqualTo(System.getProperty("qbit.artifactVersion")).isEqualTo(CollaborationQBitProducer.VERSION);
|
||||||
assertThat(bundle.getEntries()).anyMatch(entry -> entry.getKey().equals("app:collaboration;slot:help"));
|
assertThat(bundle.getEntries()).anyMatch(entry -> entry.getKey().equals("app:collaboration;slot:help"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class CollaborationTest
|
|||||||
assertThat(producer.isEnabled()).isTrue();
|
assertThat(producer.isEnabled()).isTrue();
|
||||||
assertThat(producer.getQBitConfig()).isSameAs(config);
|
assertThat(producer.getQBitConfig()).isSameAs(config);
|
||||||
assertThat(producer.getNamespace()).isEqualTo("collaboration");
|
assertThat(producer.getNamespace()).isEqualTo("collaboration");
|
||||||
assertThat(producer.getQBitMetaData().getVersion()).isEqualTo("0.1.0");
|
assertThat(producer.getQBitMetaData().getVersion()).isEqualTo(System.getProperty("qbit.artifactVersion")).isEqualTo(CollaborationQBitProducer.VERSION);
|
||||||
assertThat(producer.getQBitMetaData().getArtifactId()).isEqualTo("qbit-collaboration");
|
assertThat(producer.getQBitMetaData().getArtifactId()).isEqualTo("qbit-collaboration");
|
||||||
assertThat(config.getDefaultBackendNameForTables()).isEqualTo("memory");
|
assertThat(config.getDefaultBackendNameForTables()).isEqualTo("memory");
|
||||||
assertThat(config.getAdminApp()).isTrue();
|
assertThat(config.getAdminApp()).isTrue();
|
||||||
|
|||||||
Reference in new issue
Block a user