Skip to content

Commit 2ade026

Browse files
authored
Merge branch 'main' into multi-level-bug
2 parents 33b1351 + f6ede42 commit 2ade026

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2410
-2571
lines changed

.github/actions/integration-tests/action.yml

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ runs:
5454
done
5555
5656
- uses: actions/checkout@v5
57+
with:
58+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
5759
- name: Use Node.js ${{ inputs.NODE_VERSION}}
5860
uses: actions/setup-node@v6
5961
with:
@@ -67,8 +69,12 @@ runs:
6769
- name: Set node env for HANA
6870
run: echo "NODE_VERSION_HANA=$(echo ${{ inputs.NODE_VERSION }} | tr . _)" >> $GITHUB_ENV
6971
shell: bash
72+
- name: CDS Versions being used
73+
run: cds v -i
74+
shell: bash
75+
7076
# Deploy model to HANA
71-
- name: Create Object store
77+
- name: Create Object store
7278
shell: bash
7379
run: cf create-service objectstore standard cap-js-attachments-object-store-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA
7480
- name: Create Basic Auth Malware scanner
@@ -79,32 +85,38 @@ runs:
7985
run: cf create-service hana hdi-shared cap-js-attachments-hana-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA
8086
- run: cd tests/incidents-app/ && cds deploy --to hana:cap-js-attachments-hana-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA
8187
shell: bash
88+
8289
# Create service key
83-
- run: cf create-service-key cap-js-attachments-hana-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA cap-js-attachments-hana-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA-key
90+
- run: cf create-service-key cap-js-attachments-scanner-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA cap-js-attachments-scanner-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA-key
8491
if: ${{ inputs.SCANNER_AUTH == 'basic' }}
85-
shell: bash
86-
- run: cf create-service-key cap-js-attachments-hana-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA gcp-cap-js-attachments-hana-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA-key -c '{"auth":"mtls"}'
92+
shell: bash
93+
- run: cf create-service-key cap-js-attachments-scanner-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA cap-js-attachments-scanner-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA-key -c '{"auth":"mtls"}'
8794
if: ${{ inputs.SCANNER_AUTH == 'mtls' }}
8895
shell: bash
96+
8997
# Bind against BTP services
9098
- run: cds bind db -2 cap-js-attachments-hana-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA -o package.json
9199
shell: bash
92-
- run: cds bind objectStore -2 cap-js-attachments-object-store-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA -o package.json
100+
- name: Bind object store
93101
shell: bash
102+
run: |
103+
for i in {1..3}; do
104+
cds bind objectStore -2 cap-js-attachments-object-store-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA -o package.json && break
105+
echo "cds bind objectStore failed, retrying ($i/3)..."
106+
sleep 100
107+
if [ "$i" -eq 3 ]; then
108+
echo "❌ cds bind objectStore failed after 3 attempts."
109+
exit 1
110+
fi
111+
done
94112
- run: cds bind malware-scanner -2 cap-js-attachments-scanner-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA -o package.json
95113
shell: bash
96114

97-
# Set Hyperscaler for attachment plugin in package.json
98-
- run: |
99-
cd tests/incidents-app
100-
npx -y json -I -f package.json -e "this['cds']['requires']['attachments'] = { 'kind': '${{inputs.OBJECT_STORE_KIND}}' }"
101-
shell: bash
102-
103115
# Run tests in hybrid mode
104116
- run: cds bind --exec npm run test
105117
shell: bash
106118

107-
# Cleanup
119+
# Cleanup BTP services
108120
- name: Delete Malware Scanner instance
109121
if: ${{ always() }}
110122
run: cf delete-service cap-js-attachments-scanner-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA -f
@@ -117,7 +129,8 @@ runs:
117129
if: ${{ always() }}
118130
run: cf delete-service-key cap-js-attachments-hana-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA cap-js-attachments-hana-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ inputs.SCANNER_AUTH }}-$NODE_VERSION_HANA-key -f
119131
shell: bash
120-
# Somehow first delete always fails with a "ongoing operation on service binding" error
132+
133+
# Note: The initial delete attempt often fails due to an "ongoing operation on service binding" error.
121134
- name: Delete HDI Container
122135
if: ${{ always() }}
123136
shell: bash

.github/workflows/release.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55

66
permissions:
77
contents: write
8+
id-token: write
89

910
jobs:
1011

@@ -15,7 +16,7 @@ jobs:
1516
- uses: actions/checkout@v4
1617
- uses: actions/setup-node@v4
1718
with:
18-
node-version: 20
19+
node-version: 24
1920
registry-url: https://registry.npmjs.org/
2021

2122
- name: Run Tests
@@ -39,6 +40,4 @@ jobs:
3940
with:
4041
tag: 'v${{ steps.package-version.outputs.current-version }}'
4142
body: '${{ steps.parse-changelog.outputs.body }}'
42-
- run: npm publish --access public
43-
env:
44-
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
43+
- run: npm publish --access public --provenance

.github/workflows/test.yml

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ name: CI
33
on:
44
workflow_dispatch:
55
push:
6-
branches: [ main ]
6+
branches: [main]
77
pull_request_target:
8-
branches: [ main ]
9-
types: [ reopened, synchronize, opened ]
8+
branches: [main]
9+
types: [reopened, synchronize, opened]
1010

1111
jobs:
1212
requires-approval:
@@ -26,7 +26,9 @@ jobs:
2626
matrix:
2727
node-version: [20.x, 22.x]
2828
steps:
29-
- uses: actions/checkout@v2
29+
- uses: actions/checkout@v5
30+
with:
31+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
3032
- name: Use Node.js ${{ matrix.node-version }}
3133
uses: actions/setup-node@v2
3234
with:
@@ -35,6 +37,7 @@ jobs:
3537
- run: npm i
3638
- run: cd tests/incidents-app && npm i
3739
- run: npm run test
40+
3841
integration-tests:
3942
runs-on: ubuntu-latest
4043
needs: requires-approval
@@ -47,16 +50,18 @@ jobs:
4750
hyperscaler: [AWS, AZURE, GCP]
4851
scanner-auth: [basic, mtls]
4952
steps:
50-
- name: Checkout repository
51-
uses: actions/checkout@v5
52-
- name: Integration tests
53-
uses: ./.github/actions/integration-tests
54-
with:
55-
CF_API: ${{ secrets[format('CF_API_{0}', matrix.hyperscaler)] }}
56-
CF_USERNAME: ${{ secrets['CF_USERNAME'] }}
57-
CF_PASSWORD: ${{ secrets['CF_PASSWORD'] }}
58-
CF_ORG: ${{ secrets[format('CF_ORG_{0}', matrix.hyperscaler)] }}
59-
CF_SPACE: ${{ secrets[format('CF_SPACE_{0}', matrix.hyperscaler)] }}
60-
OBJECT_STORE_KIND: ${{ vars[format('OBJECT_STORE_KIND_{0}', matrix.hyperscaler)] }}
61-
NODE_VERSION: ${{ matrix.node-version }}
62-
SCANNER_AUTH: ${{ matrix.scanner-auth }}
53+
- name: Checkout repository
54+
uses: actions/checkout@v5
55+
with:
56+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
57+
- name: Integration tests
58+
uses: ./.github/actions/integration-tests
59+
with:
60+
CF_API: ${{ secrets[format('CF_API_{0}', matrix.hyperscaler)] }}
61+
CF_USERNAME: ${{ secrets['CF_USERNAME'] }}
62+
CF_PASSWORD: ${{ secrets['CF_PASSWORD'] }}
63+
CF_ORG: ${{ secrets[format('CF_ORG_{0}', matrix.hyperscaler)] }}
64+
CF_SPACE: ${{ secrets[format('CF_SPACE_{0}', matrix.hyperscaler)] }}
65+
OBJECT_STORE_KIND: ${{ vars[format('OBJECT_STORE_KIND_{0}', matrix.hyperscaler)] }}
66+
NODE_VERSION: ${{ matrix.node-version }}
67+
SCANNER_AUTH: ${{ matrix.scanner-auth }}

CHANGELOG.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,47 @@ All notable changes to this project will be documented in this file.
44
This project adheres to [Semantic Versioning](http://semver.org/).
55
The format is based on [Keep a Changelog](http://keepachangelog.com/).
66

7+
## Version 3.4.0
8+
9+
### Added
10+
11+
- Introduced support for the `@Core.AcceptableMediaTypes` annotation, allowing specification of permitted MIME types for attachment uploads:
12+
```cds
13+
annotate my.Books.attachments with {
14+
content @Core.AcceptableMediaTypes: ['image/jpeg'];
15+
}
16+
```
17+
- Added support for the `@Validation.Maximum` annotation to define the maximum allowed file size for attachments:
18+
```cds
19+
annotate my.Books.attachments with {
20+
content @Validation.Maximum: '2MB';
21+
}
22+
```
23+
24+
### Fixed
25+
26+
- Removed the previous hard limit of `400 MB` for file uploads. Files exceeding this size may still fail during malware scanning and will be marked with a `Failed` status.
27+
- Resolved issues with generic handler registration, enabling services to intercept the attachments plugin using middleware.
28+
29+
## Version 3.3.0
30+
31+
### Added
32+
33+
- Added [`standard`](./README.md#supported-storage-provider) kind and set it as the default so that the configuration needs no adjustment when switching hyper-scalers.
34+
- Added support for uploading and updating attachments via `srv.run(INSERT.into(Attachments).entries())` or `srv.run(UPDATE.entity(Attachments).set())`
35+
36+
### Fixed
37+
38+
- Fixed an issue that in multi-tenancy scenarios with separate object stores duplicate object stores per tenant were created when updating the tenant binding via the SaaS dependency service.
39+
- Fixed a race-condition where tenant isolation in separate object store mode could be broken.
40+
- Fixed a case where attachments were not correctly deleted.
41+
- Fixed a server crash when using the `AttachmentsSrv.put` API to upload an attachment.
42+
- Fixed a server crash when no object store would be bound to the application on BTP.
43+
- Fixed a server crash when the filename would not be given when creating new attachment metadata.
44+
- Fixed an issue where attachment handlers would be missing when all Attachments entity were behind feature toggles.
45+
- Fixed an issue where with storage kind `db` attachments could not be uploaded as drafts.
46+
- Fixed an issue where the content could be uploaded for a not existing attachments entity.
47+
748
## Version 3.2.0
849
950
### Added
@@ -15,8 +56,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
1556
- Added criticality status to the attachment scan status.
1657
- Provided translations for all SAP-supported languages.
1758
18-
### Fixed
19-
2059
## Version 3.1.0
2160
2261
### Added

README.md

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ For a quick local development setup with in-memory storage:
5656
// (...)
5757
"[hybrid]": {
5858
"attachments": {
59-
"kind": "s3"
59+
"kind": "standard"
6060
// (...)
6161
}
6262
}
@@ -200,9 +200,11 @@ Scan status codes:
200200
- `Failed`: Scanning failed.
201201

202202
> [!Note]
203-
> The plugin currently supports file uploads up to 400 MB in size per attachment as this is a limitation of the [malware scanning service](https://help.sap.com/docs/malware-scanning-servce/sap-malware-scanning-service/what-is-sap-malware-scanning-service). Please note: this limitation remains even with the malware scanner disabled.
204203
> The malware scanner supports mTLS authentication which requires an annual renewal of the certificate. Previously, basic authentication was used which has now been deprecated.
205204
205+
> [!Note]
206+
> If the malware scanner reports a file size larger than the limit specified via [@Validation.Maximum](#specify-the-maximum-file-size) it removes the file and sets the status of the attachment metadata to failed.
207+
206208

207209
### Visibility Control for Attachments UI Facet Generation
208210

@@ -243,6 +245,56 @@ The typical sequence includes:
243245
1. **POST** -> create attachment metadata, returns ID
244246
2. **PUT** -> upload file content using the ID
245247

248+
### Specify the maximum file size
249+
250+
You can specify the maximum file size by annotating the attachments content property with `@Validation.Maximum`
251+
252+
```cds
253+
entity Incidents {
254+
...
255+
attachments: Composition of many Attachments;
256+
}
257+
258+
annotate Incidents.attachments with {
259+
content @Validation.Maximum : '20MB';
260+
}
261+
```
262+
263+
The default is 400MB
264+
265+
### Restrict allowed MIME types
266+
267+
You can restrict which MIME types are allowed for attachments by annotating the content property with `@Core.AcceptableMediaTypes`. This validation is performed during file upload.
268+
269+
```cds
270+
entity Incidents {
271+
...
272+
attachments: Composition of many Attachments;
273+
}
274+
275+
annotate Incidents.attachments with {
276+
content @Core.AcceptableMediaTypes : ['image/jpeg', 'image/png', 'application/pdf'];
277+
}
278+
```
279+
280+
Wildcard patterns are supported:
281+
282+
```cds
283+
annotate Incidents.attachments with {
284+
content @Core.AcceptableMediaTypes : ['image/*', 'application/pdf'];
285+
}
286+
```
287+
288+
To allow all MIME types (default behavior), either omit the annotation or use:
289+
290+
```cds
291+
annotate Incidents.attachments with {
292+
content @Core.AcceptableMediaTypes : ['*/*'];
293+
}
294+
```
295+
296+
When a file with a disallowed MIME type is uploaded, the request will be rejected with a `400` error.
297+
246298
## Releases
247299

248300
- The plugin is released to [NPM Registry](https://www.npmjs.com/package/@cap-js/attachments).
@@ -258,12 +310,21 @@ The typical sequence includes:
258310
## Architecture Overview
259311
### Multitenancy
260312

261-
The plugin supports multitenancy scenarios, allowing both shared and tenant-specific object store instances.
313+
The plugin supports multi-tenancy scenarios, allowing both shared and tenant-specific object store instances.
262314

263315
> [!Note]
264-
> Starting from version 2.1.0, **separate mode** for object store instances is the default setting for multitenancy.
316+
> Starting from version 2.1.0, **separate mode** for object store instances is the default setting for multi-tenancy.
265317
266-
For multitenant applications, `@cap-js/attachments` must be included in the dependencies of both the application-level and _mtx/sidecar/package.json_ files.
318+
For multi-tenant applications, `@cap-js/attachments` must be included in the dependencies of both the application-level and _mtx/sidecar/package.json_ files.
319+
320+
#### Separate object store instances
321+
322+
By default the plugin creates for each tenant its own object store instance during the tenants subscription.
323+
324+
When the tenant unsubscribes the object store instance is deleted.
325+
326+
> [!WARNING]
327+
> When you remove the plugin from an application after separate object stores already have been created, the object stores are not automatically removed!
267328
268329
#### Shared Object Store Instance
269330

@@ -333,10 +394,10 @@ To set the binding, please see the section [Storage Targets](#storage-targets).
333394

334395
##### Supported Storage Provider
335396

336-
- **AWS S3** (`kind: "s3"`)
337-
- **Azure Blob Storage** (`kind: "azure"`)
338-
- **Google Cloud Platform** (`kind: "gcp"`)
339-
397+
- **Standard** (`kind: "standard"`) | Depending on the bound object store credentials, uses AWS S3, Azure Blob Storage or GCP Cloud Storage. You can manually specify the implementation by adjusting the type to:
398+
- **AWS S3** (`kind: "s3"`)
399+
- **Azure Blob Storage** (`kind: "azure"`)
400+
- **GCP Cloud Storage** (`kind: "gcp"`)
340401

341402
### Model Texts
342403

_i18n/messages.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
UnableToDownloadAttachmentScanStatusNotClean=Unable to download the attachment as scan status is not clean.
2+
AttachmentSizeExceeded=File size limit exceeded beyond {0}.
3+
MultiUpdateNotSupported=Multi update is not supported.
4+
AttachmentMimeTypeDisallowed=The attachment file type '{mimeType}' is not allowed.

db/data/sap.attachments-ScanStates.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ Unscanned;Unscanned;The file is not yet scanned for malware.;2
33
Scanning;Scanning;The file is currently being scanned for malware.;2
44
Infected;Infected;The file contains malware! Do not download!;1
55
Clean;Clean;The file does not contain any malware.;3
6-
Failed;Failed;The file could not be scanned for malware.;1
6+
Failed;Failed;The file could not be scanned for malware.;1

db/data/sap.attachments-ScanStates_texts.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ en;Unscanned;Unscanned;The file is not yet scanned for malware.
33
en;Scanning;Scanning;The file is currently being scanned for malware.
44
en;Infected;Infected;The file contains malware! Do not download!
55
en;Clean;Clean;The file does not contain any malware.
6-
en;Failed;Failed;The file could not be scanned for malware.
6+
en;Failed;Failed;The file could not be scanned for malware.

jest.config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
const config = {
2-
testTimeout: 60000,
2+
testTimeout: 120000,
33
testMatch: ['**/*.test.js'],
4-
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
54
forceExit: true,
65
detectOpenHandles: true
76
}

0 commit comments

Comments
 (0)