diff --git a/README.md b/README.md
index bac2117..b78f186 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ A Java client library for interacting with the DatabunkerPro API. DatabunkerPro
- Complete implementation of the DatabunkerPro API
- User management (create, get, update, delete, patch)
- App data management
+- File storage (encrypted per-user files with tags and expiration)
- Legal basis and agreement management
- Connector management
- Group and role management
@@ -48,7 +49,7 @@ Add the repository and dependency to your `pom.xml`:
org.databunker
databunkerpro-java
- 1.0.0-SNAPSHOT
+ 1.1.0
```
@@ -67,11 +68,11 @@ Add the JitPack repository and dependency to your `pom.xml`:
com.github.securitybunker
databunkerpro-java
- v1.0.0
+ v1.1.0
```
-**Note**: Replace `v1.0.0` with your desired version tag (e.g., `v1.0.1`, `v2.0.0`, etc.)
+**Note**: Replace `v1.1.0` with your desired version tag (e.g., `v1.1.1`, `v2.0.0`, etc.)
### From Local Maven Repository
@@ -90,12 +91,24 @@ Then add the dependency to your `pom.xml`:
org.databunker
databunkerpro-java
- 1.0.0-SNAPSHOT
+ 1.1.0
```
## New Features in Latest Version
+### New in 1.1.0
+
+- **File API**: Store, retrieve, list, retag and delete encrypted per-user files
+ (`createFile`, `getFile`, `listUserFiles`, `replaceFileTags`, `deleteFile`), plus
+ `bulkListFilesByTag` for bulk lookups. Options are passed with the typed `FileOptions`
+ builder (mimetype, tags, `finaltime`, `slidingtime`).
+- **Apache HttpClient 5**: Migrated off end-of-life HttpClient 4.x. If your project pins
+ HttpClient transitively, it now resolves `org.apache.httpcomponents.client5:httpclient5`.
+- **Removed internal portal endpoints**: `preloginUser`, `loginUser`, `createCaptcha`,
+ `getUIConf` and `getTenantConf` were internal to the DatabunkerPro web portal and are
+ no longer part of the client.
+
### Enhanced API Methods
- **Wrapping Key Generation**: Generate wrapping keys from Shamir's Secret Sharing keys
- **Typed Patch Operations**: Use structured `PatchOperation` objects for user updates
@@ -175,6 +188,39 @@ api.createAppData("email", "user@example.com", "appname", data, null);
Map appData = api.getAppData("email", "user@example.com", "appname", null);
```
+### File Storage
+
+```java
+// Store a file. The content is passed base64-encoded.
+String filedata = Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get("passport.pdf")));
+FileOptions fileOptions = FileOptions.builder()
+ .mimetype("application/pdf")
+ .tags(Arrays.asList("kyc", "passport"))
+ .finaltime("365d")
+ .build();
+Map created = api.createFile("email", "user@example.com", "passport.pdf", filedata, fileOptions, null);
+String fileuuid = (String) created.get("fileuuid");
+
+// Get a file by uuid
+Map file = api.getFile("email", "user@example.com", fileuuid, null);
+
+// Get a file by name (the newest match is returned)
+Map byName = api.getFile("email", "user@example.com", null, "passport.pdf", false, null);
+
+// List the metadata of a user's files, optionally filtered by a single tag
+Map allFiles = api.listUserFiles("email", "user@example.com", null);
+Map kycFiles = api.listUserFiles("email", "user@example.com", "kyc", null);
+
+// Replace the complete tag set on a file
+api.replaceFileTags("email", "user@example.com", fileuuid, Arrays.asList("kyc", "verified"), null);
+
+// Delete a file
+api.deleteFile("email", "user@example.com", fileuuid, null);
+```
+
+Tags are lowercased and de-duplicated by the server, must match `^[a-z0-9][a-z0-9._-]{0,49}$`,
+and at most 16 are kept per file.
+
### System Configuration
```java
@@ -267,8 +313,8 @@ JitPack automatically builds and publishes your GitHub repository as a Maven dep
1. **Create a Git tag** for your release:
```bash
- git tag v1.0.0
- git push origin v1.0.0
+ git tag v1.1.0
+ git push origin v1.1.0
```
2. **JitPack automatically builds** and publishes the package
diff --git a/pom.xml b/pom.xml
index d1556e2..670f2dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.databunker
databunkerpro-java
- 1.1.0-SNAPSHOT
+ 1.1.0
jar
DatabunkerPro Java Client
diff --git a/src/main/java/org/databunker/options/FileOptions.java b/src/main/java/org/databunker/options/FileOptions.java
index 51680df..7128a4d 100644
--- a/src/main/java/org/databunker/options/FileOptions.java
+++ b/src/main/java/org/databunker/options/FileOptions.java
@@ -84,4 +84,12 @@ public FileOptions build() {
return new FileOptions(this);
}
}
+
+ /**
+ * Creates a new builder for FileOptions
+ * @return A new builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
}
diff --git a/src/test/java/org/databunker/DatabunkerproApiTest.java b/src/test/java/org/databunker/DatabunkerproApiTest.java
index ab97fb1..14ec70c 100644
--- a/src/test/java/org/databunker/DatabunkerproApiTest.java
+++ b/src/test/java/org/databunker/DatabunkerproApiTest.java
@@ -13,10 +13,15 @@
import org.junit.Test;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Random;
import org.databunker.options.BasicOptions;
+import org.databunker.options.FileOptions;
import org.databunker.options.SharedRecordOptions;
import static org.junit.Assert.*;
@@ -269,6 +274,89 @@ public void testSharedRecordManagement() throws IOException {
System.out.println("Successfully retrieved shared record: " + recorduuid);
}
+ @Test
+ public void testFileManagement() throws IOException {
+ System.out.println("\nTesting file management...");
+ String email = "test" + random.nextInt(1000000) + "@example.com";
+ Map userData = Map.of(
+ "email", email,
+ "name", "Test User " + random.nextInt(1000000),
+ "phone", String.valueOf(random.nextInt(1000000))
+ );
+ api.createUser(userData, null, null);
+
+ // Store a file
+ String content = "file content " + random.nextInt(1000000);
+ String filedata = Base64.getEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8));
+ String filename = "notes" + random.nextInt(1000000) + ".txt";
+ FileOptions options = FileOptions.builder()
+ .mimetype("text/plain")
+ .tags(Arrays.asList("kyc", "notes"))
+ .finaltime("1d")
+ .build();
+ Map createResult = api.createFile("email", email, filename, filedata, options, null);
+ assertNotNull(createResult);
+ assertEquals("ok", createResult.get("status"));
+ assertNotNull(createResult.get("fileuuid"));
+ String fileuuid = (String) createResult.get("fileuuid");
+ System.out.println("Successfully created file: " + fileuuid);
+
+ // Get the file by uuid and verify the content round-trips
+ Map getResult = api.getFile("email", email, fileuuid, null);
+ assertNotNull(getResult);
+ assertEquals("ok", getResult.get("status"));
+ assertEquals(filename, getResult.get("filename"));
+ assertEquals("text/plain", getResult.get("mimetype"));
+ assertEquals(content, new String(Base64.getDecoder().decode((String) getResult.get("filedata")),
+ StandardCharsets.UTF_8));
+ System.out.println("Successfully retrieved file by uuid: " + fileuuid);
+
+ // Get the same file by name
+ Map byName = api.getFile("email", email, null, filename, false, null);
+ assertNotNull(byName);
+ assertEquals("ok", byName.get("status"));
+ assertEquals(fileuuid, byName.get("fileuuid"));
+ System.out.println("Successfully retrieved file by name: " + filename);
+
+ // List all files of the user
+ Map listResult = api.listUserFiles("email", email, null);
+ assertNotNull(listResult);
+ assertEquals("ok", listResult.get("status"));
+ List