Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 52 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -48,7 +49,7 @@ Add the repository and dependency to your `pom.xml`:
<dependency>
<groupId>org.databunker</groupId>
<artifactId>databunkerpro-java</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.1.0</version>
</dependency>
```

Expand All @@ -67,11 +68,11 @@ Add the JitPack repository and dependency to your `pom.xml`:
<dependency>
<groupId>com.github.securitybunker</groupId>
<artifactId>databunkerpro-java</artifactId>
<version>v1.0.0</version>
<version>v1.1.0</version>
</dependency>
```

**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

Expand All @@ -90,12 +91,24 @@ Then add the dependency to your `pom.xml`:
<dependency>
<groupId>org.databunker</groupId>
<artifactId>databunkerpro-java</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.1.0</version>
</dependency>
```

## 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
Expand Down Expand Up @@ -175,6 +188,39 @@ api.createAppData("email", "user@example.com", "appname", data, null);
Map<String, Object> 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<String, Object> created = api.createFile("email", "user@example.com", "passport.pdf", filedata, fileOptions, null);
String fileuuid = (String) created.get("fileuuid");

// Get a file by uuid
Map<String, Object> file = api.getFile("email", "user@example.com", fileuuid, null);

// Get a file by name (the newest match is returned)
Map<String, Object> 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<String, Object> allFiles = api.listUserFiles("email", "user@example.com", null);
Map<String, Object> 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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>org.databunker</groupId>
<artifactId>databunkerpro-java</artifactId>
<version>1.1.0-SNAPSHOT</version>
<version>1.1.0</version>
<packaging>jar</packaging>

<name>DatabunkerPro Java Client</name>
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/databunker/options/FileOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
88 changes: 88 additions & 0 deletions src/test/java/org/databunker/DatabunkerproApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> listResult = api.listUserFiles("email", email, null);
assertNotNull(listResult);
assertEquals("ok", listResult.get("status"));
List<Map<String, Object>> files = (List<Map<String, Object>>) listResult.get("files");
assertNotNull(files);
assertEquals(1, files.size());
assertEquals(fileuuid, files.get(0).get("fileuuid"));

// List files filtered by a tag, and confirm a foreign tag matches nothing
Map<String, Object> taggedResult = api.listUserFiles("email", email, "kyc", null);
assertEquals("ok", taggedResult.get("status"));
assertEquals(1, ((List<Map<String, Object>>) taggedResult.get("files")).size());
Map<String, Object> otherTagResult = api.listUserFiles("email", email, "invoice", null);
assertEquals("ok", otherTagResult.get("status"));
assertTrue(((List<Map<String, Object>>) otherTagResult.get("files")).isEmpty());
System.out.println("Successfully listed user files");

// Replace the tag set
Map<String, Object> retagResult = api.replaceFileTags("email", email, fileuuid,
Arrays.asList("kyc", "verified"), null);
assertNotNull(retagResult);
assertEquals("ok", retagResult.get("status"));
List<String> tags = (List<String>) retagResult.get("tags");
assertNotNull(tags);
assertEquals(2, tags.size());
assertTrue(tags.contains("verified"));
assertFalse(tags.contains("notes"));
System.out.println("Successfully replaced file tags: " + tags);

// Delete the file
Map<String, Object> deleteResult = api.deleteFile("email", email, fileuuid, null);
assertNotNull(deleteResult);
assertEquals("ok", deleteResult.get("status"));
Map<String, Object> afterDelete = api.listUserFiles("email", email, null);
assertTrue(((List<Map<String, Object>>) afterDelete.get("files")).isEmpty());
System.out.println("Successfully deleted file: " + fileuuid);
}

@Test
public void testDeleteUsersBulk() throws IOException {
System.out.println("\nTesting bulk user deletion...");
Expand Down
Loading