diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..b2707f61 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +* @xernobyl +* @JcMinarro diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..7bcc6037 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: build + +on: [pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + build: + name: 🧪 Test & lint + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Commit message lint + uses: wagoid/commitlint-github-action@v4 + + - name: Restore cache + uses: actions/cache@v3 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Test + env: + STREAM_KEY: ${{ secrets.STREAM_KEY }} + STREAM_SECRET: ${{ secrets.STREAM_SECRET }} + STREAM_APP_ID: ${{ secrets.STREAM_APP_ID }} + run: | + ./gradlew spotlessCheck --no-daemon + ./gradlew test --no-daemon diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml new file mode 100644 index 00000000..59c1c98c --- /dev/null +++ b/.github/workflows/initiate_release.yml @@ -0,0 +1,51 @@ +name: Create release PR + +on: + workflow_dispatch: + inputs: + version: + description: "The new version number with a 'v' prefix. Example: v1.40.1" + required: true + +jobs: + init_release: + name: 🚀 Create release PR + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # gives the changelog generator access to all previous commits + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Update CHANGELOG.md, build.gradle and push release branch + env: + VERSION: ${{ github.event.inputs.version }} + run: | + npx --yes standard-version@9.3.2 --release-as "$VERSION" --skip.tag --skip.commit --tag-prefix=v + git config --global user.name 'github-actions' + git config --global user.email 'release@getstream.io' + git checkout -q -b "release-$VERSION" + git commit -am "chore(release): $VERSION" + git push -q -u origin "release-$VERSION" + + - name: Get changelog diff + uses: actions/github-script@v5 + with: + script: | + const get_change_log_diff = require('./scripts/get_changelog_diff.js') + core.exportVariable('CHANGELOG', get_change_log_diff()) + + - name: Open pull request + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr create \ + -t "Release ${{ github.event.inputs.version }}" \ + -b "# :rocket: ${{ github.event.inputs.version }} + Make sure to use squash & merge when merging! + Once this is merged, another job will kick off automatically and publish the package. + # :memo: Changelog + ${{ env.CHANGELOG }}" diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml new file mode 100644 index 00000000..30938502 --- /dev/null +++ b/.github/workflows/javadoc.yml @@ -0,0 +1,29 @@ +name: javadoc +on: + push: + branches: + - main +jobs: + javadoc: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + persist-credentials: false + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - name: Set up Node.js 16 + uses: actions/setup-node@v2 + with: + node-version: 16 + - name: Generate doc + run: ./gradlew --no-daemon javadoc + - name: Deploy + uses: JamesIves/github-pages-deploy-action@releases/v3 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: gh-pages + FOLDER: build/docs/javadoc/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..da5eba72 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,57 @@ +name: Release + +on: + pull_request: + types: [closed] + branches: + - main + +jobs: + Release: + name: 🚀 Release + if: github.event.pull_request.merged && startsWith(github.head_ref, 'release-') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - uses: actions/github-script@v5 + with: + script: | + const get_change_log_diff = require('./scripts/get_changelog_diff.js') + core.exportVariable('CHANGELOG', get_change_log_diff()) + + // Getting the release version from the PR source branch + // Source branch looks like this: release-1.0.0 + const version = context.payload.pull_request.head.ref.split('-')[1] + core.exportVariable('VERSION', version) + + - name: Build artifacts + run: ./gradlew jar javadocJar sourcesJar + + - name: Publish to MavenCentral + run: | + sudo bash -c "echo '$GPG_KEY_CONTENTS' | base64 -d > '$SIGNING_SECRET_KEY_RING_FILE'" + ./gradlew publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository + env: + STREAM_KEY: ${{ secrets.STREAM_KEY }} + STREAM_SECRET: ${{ secrets.STREAM_SECRET }} + STREAM_APP_ID: ${{ secrets.STREAM_APP_ID }} + GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }} + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} + SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} + SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} + + - name: Create release on GitHub + uses: ncipollo/release-action@v1 + with: + body: ${{ env.CHANGELOG }} + tag: ${{ env.VERSION }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index cd968238..f5614bdd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,22 @@ *.class -.idea -*.iml - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # *.jar *.war *.ear +build/ -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -# Maven +.gradle/ +gradle.properties +gradle/* +!gradle/wrapper +!gradle/wrapper/*.jar +out/ target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties +bin/ +.classpath +.project +.settings/ + +.idea/ +.vscode/ +.envrc +/local.properties diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 32a3b469..00000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: java -jdk: -- oraclejdk8 -env: - global: - - secure: TmI+K6gSOfDy5oa5EgLCGijimKdR5cHAwM6RASDegTjlRQ/sWVf8BNF4IPJHnAYFWNCgyX6vLdfPS3IJNrYS9iI1Er2a1Ctcq1TbCZfZpn4eIj8mcUXxr2Fo6hLJh2KeuXj3OQwfUy/Jul1Own/9R5YoVDJYD0ntnt1HJgFqq+0= - - secure: DethMgIykOufLLQn9Jykmg7rdvZp4ONXD4A1XVQNaEVQytv20Fb2QidEPmVEocuDmpmU6SjWNMhAeBdlWnyGB4lECMG9594HJpSnNkyFQzzsfSWdOwHixiUCuD+rMJlEPRJDM4ayxhilPnK1gkycKBAZ1gtutriC4O/BdEOCN6A= - - secure: F6jGxeoyhD1tmC0ovRW2RqeEQDN5t5Kfqktmtcj0Lz3S4ddgzMwC6RYKyYiiu9T3FSfh/hEMrx2SrMK+Neu2ctfYflNibUjgZJVPZHR7DA3S+g30teyj10XWspb6+OLC7U0DnGbdLee/w0KkuugQHYNv1aM9oEPtLWi+3VpvIIU= -after_success: -- ".travis/publish.sh" diff --git a/.travis/publish.sh b/.travis/publish.sh deleted file mode 100755 index 6f5021e6..00000000 --- a/.travis/publish.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# -# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo. -# -# Adapted from https://coderwall.com/p/9b_lfq and -# http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ - -SLUG="GetStream/stream-java" -JDK="oraclejdk8" -BRANCH="master" - -set -e - -if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then - echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'." -elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then - echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'." -elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - echo "Skipping snapshot deployment: was pull request." -elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then - echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'." -else - echo "Deploying snapshot..." - mvn clean deploy --settings=".travis/settings.xml" -Dmaven.test.skip=true - echo "Snapshot deployed!" -fi \ No newline at end of file diff --git a/.travis/settings.xml b/.travis/settings.xml deleted file mode 100644 index de371552..00000000 --- a/.travis/settings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - ossrh - ${env.CI_DEPLOY_USERNAME} - ${env.CI_DEPLOY_PASSWORD} - - - \ No newline at end of file diff --git a/.versionrc.js b/.versionrc.js new file mode 100644 index 00000000..2367c93c --- /dev/null +++ b/.versionrc.js @@ -0,0 +1,16 @@ +const gradleUpdater = { + VERSION_REGEX: /version = '(.+)'/, + + readVersion: function (contents) { + const version = this.VERSION_REGEX.exec(contents)[1]; + return version; + }, + + writeVersion: function (contents, version) { + return contents.replace(this.VERSION_REGEX.exec(contents)[0], `version = '${version}'`); + } +} + +module.exports = { + bumpFiles: [{ filename: './build.gradle', updater: gradleUpdater }], +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..31eb8b43 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,130 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [3.25.0](https://github.com/GetStream/stream-java/compare/v3.24.0...v3.25.0) (2026-01-16) + +## [3.24.0](https://github.com/GetStream/stream-java/compare/v3.23.0...v3.24.0) (2025-11-27) + +## [3.23.0](https://github.com/GetStream/stream-java/compare/v3.22.0...v3.23.0) (2025-10-03) + +## [3.22.0](https://github.com/GetStream/stream-java/compare/v3.20.0...v3.22.0) (2025-07-09) + +## [3.21.0](https://github.com/GetStream/stream-java/compare/v3.20.0...v3.21.0) (2025-07-02) + +## [3.20.0](https://github.com/GetStream/stream-java/compare/v3.19.0...v3.20.0) (2025-04-17) + +## [3.19.0](https://github.com/GetStream/stream-java/compare/v3.17.1...v3.19.0) (2025-04-14) + +### [3.17.1](https://github.com/GetStream/stream-java/compare/v3.17.0...v3.17.1) (2025-04-11) + + +### Bug Fixes + +* update reaction should support moderation template ([08b282b](https://github.com/GetStream/stream-java/commit/08b282b64170961e3b2cc25f4cc434674651bc84)) + +## [3.17.0](https://github.com/GetStream/stream-java/compare/v3.16.0...v3.17.0) (2025-03-21) + +## [3.16.0](https://github.com/GetStream/stream-java/compare/v3.15.2...v3.16.0) (2025-03-10) + +### [3.15.2](https://github.com/GetStream/stream-java/compare/v3.15.1...v3.15.2) (2025-03-07) + + +### Bug Fixes + +* moderation response ([ec7db00](https://github.com/GetStream/stream-java/commit/ec7db0040062c2af6c9db9b3337d240594abf9b4)) + +### [3.15.1](https://github.com/GetStream/stream-java/compare/v3.15.0...v3.15.1) (2024-12-27) + + +### Bug Fixes + +* pass custom var to request ([99ea58e](https://github.com/GetStream/stream-java/commit/99ea58ef057d7a5eaa1f20aaceb6b28b819e825e)) + +## [3.15.0](https://github.com/GetStream/stream-java/compare/v3.14.0...v3.15.0) (2024-11-21) + +## [3.14.0](https://github.com/GetStream/stream-java/compare/v3.13.0...v3.14.0) (2024-11-11) + +## [3.13.0](https://github.com/GetStream/stream-java/compare/v3.12.0...v3.13.0) (2024-11-06) + +## [3.12.0](https://github.com/GetStream/stream-java/compare/v3.11.0...v3.12.0) (2024-11-05) + + +### Bug Fixes + +* fix tests ([4ae4d24](https://github.com/GetStream/stream-java/commit/4ae4d2434c81334c82115fd610caf507ce32ac5c)) +* fix tests, fix token moderation ([f4f6596](https://github.com/GetStream/stream-java/commit/f4f6596c240349c67f66094ccd5117a7fd3d60e6)) +* fix tests, fix token moderation ([1ea89d9](https://github.com/GetStream/stream-java/commit/1ea89d90e19c773dd120d96883d8bd08252e6d86)) + +## [3.11.0](https://github.com/GetStream/stream-java/compare/v3.10.0...v3.11.0) (2024-10-08) + +## [3.10.0](https://github.com/GetStream/stream-java/compare/v3.9.2...v3.10.0) (2024-03-12) + +### [3.9.2](https://github.com/GetStream/stream-java/compare/v3.9.1...v3.9.2) (2024-02-19) +* Ranking Variables in the Enriched Activities Endpoint + +### [3.9.1](https://github.com/GetStream/stream-java/compare/v3.8.2...v3.9.1) (2024-01-09) + +### [3.8.2](https://github.com/GetStream/stream-java/compare/v3.8.1...v3.8.2) (2024-01-03) + +### [3.8.1](https://github.com/GetStream/stream-java/compare/v3.8.0...v3.8.1) (2023-11-17) + +## [3.8.0](https://github.com/GetStream/stream-java/compare/v3.7.0...v3.8.0) (2023-10-23) +* Added support to force refresh +* Added support for soft deletions + +## [3.7.0](https://github.com/GetStream/stream-java/compare/v3.6.2...v3.7.0) (2023-08-16) + + +### Features + +* add capability in batch client to get enrichment activities to use enrichment flags ([cfcc86a](https://github.com/GetStream/stream-java/commit/cfcc86ae3b62fd16cbf733912e7b484a91bb7d8b)) + + +### Bug Fixes + +* spotless ([dad14c7](https://github.com/GetStream/stream-java/commit/dad14c7abedd6ca6d9cef7ea4c1a0133cb648e72)) + +### [3.6.2](https://github.com/GetStream/stream-java/compare/v3.5.0...v3.6.2) (2023-01-26) + + +### Features + +* **faye:** add websocket client ([#110](https://github.com/GetStream/stream-java/issues/110)) ([c0b29e5](https://github.com/GetStream/stream-java/commit/c0b29e51708e424f44686e20d9b2b426da661b4c)) + +### [3.6.1](https://github.com/GetStream/stream-java/compare/v3.5.0...v3.6.1) (2023-01-26) + + +### Features + +* **faye:** add websocket client ([#110](https://github.com/GetStream/stream-java/issues/110)) ([c0b29e5](https://github.com/GetStream/stream-java/commit/c0b29e51708e424f44686e20d9b2b426da661b4c)) + +## [3.6.0](https://github.com/GetStream/stream-java/compare/v3.5.0...v3.6.0) (2022-05-26) + + +### Features + +* **faye:** add websocket client ([#110](https://github.com/GetStream/stream-java/issues/110)) ([c0b29e5](https://github.com/GetStream/stream-java/commit/c0b29e51708e424f44686e20d9b2b426da661b4c)) + +## [3.5.0](https://github.com/GetStream/stream-java/compare/v3.4.1...v3.5.0) (2022-04-26) + + +### Features + +* **stats:** add followstats ([#108](https://github.com/GetStream/stream-java/issues/108)) ([56afa90](https://github.com/GetStream/stream-java/commit/56afa9098d6d21eac5e6c0b75975b32c6684358b)) + +## [3.4.1] - 2022-03-22 + +- Fix unmarshal of empty custom data + +## [3.4.0] - 2022-02-10 + +- Add unread/unseed counts into notification feed payloads + +## [3.3.0] - 2022-01-07 + +- Relax id checks for custom data in collections +- Add withOwnChildren support into reaction filtering +- Move to GitHub actions and add release automation +- Add proguard notes into readme and bump some deps due to security notices +- Add changelog diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3655d4e0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2016-2021, Stream.io Inc, and individual contributors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index ddbee397..cd5b41d4 100644 --- a/README.md +++ b/README.md @@ -1,101 +1,92 @@ -stream-java -=========== -[![Build Status](https://travis-ci.org/GetStream/stream-java.svg?branch=master)](https://travis-ci.org/GetStream/stream-java) +# Official Java SDK for [Stream Feeds](https://getstream.io/activity-feeds/) -stream-java is a Java client for [Stream](https://getstream.io/). +[![build](https://github.com/GetStream/stream-java/workflows/build/badge.svg)](https://github.com/GetStream/stream-java/actions) +

+ +

+

+ Official Java API client for Stream Feeds, a web service for building scalable newsfeeds and activity streams. +
+ Explore the docs » +
+
+ JavaDoc + · + Report Bug + · + Request Feature +

-### Installation +## 📝 About Stream -Download the latest JAR or grab via Maven: +You can sign up for a Stream account at our [Get Started](https://getstream.io/activity-feeds/docs/java/?language=java) page. + +You can use this library to access feeds API endpoints server-side. + +For the client-side integrations (web and mobile) have a look at the JavaScript, iOS and Android SDK libraries ([docs](https://getstream.io/activity-feeds/)). + +> 💡 Note: this is a library for the **Feeds** product. The Chat SDKs can be found [here](https://getstream.io/chat/docs/). + +## ⚙️ Installation + +Add the following dependency to your `pom.xml`: ```xml io.getstream.client - stream-repo-okhttp - 0.1-RC2 + stream-java + ${stream_version} ``` -Snapshots of the development version are available in Sonatype's snapshots repository. +or in your `build.gradle`: -### Usage - -```java -// Instantiate a new client to connect to us east API endpoint -// Find your API keys here https://getstream.io/dashboard/ +```gradle +implementation 'io.getstream.client:stream-java:$stream_version' +``` -ClientConfiguration streamConfig = new ClientConfiguration().setRegion(StreamRegion.US_EAST); -StreamClient streamClient = new StreamClientImpl(streamConfig, 'API_KEY', 'API_SECRET'); +In case you want to download the artifact and put it manually into your project, +you can download it from [here](https://github.com/GetStream/stream-java/releases). -// Instantiate a feed object -Feed feed = streamClient.newFeed("user", "1"); +Snapshots of the development version are available in [Sonatype](https://oss.sonatype.org/content/repositories/snapshots/io/getstream/client/) snapshots repository. -// Create an activity service -FlatActivityServiceImpl flatActivityService = feed.newFlatActivityService(SimpleActivity.class); +> 💡This API Client project requires Java SE 7. -// Get activities from 5 to 10 (using offset pagination) -FeedFilter filter = new FeedFilter.Builder().withLimit(5).withOffset(5).build(); -List activities = flatActivityService.getActivities(filter).getResults(); +## 🙋 FAQ -// Filter on an id less than the given UUID -aid = "e561de8f-00f1-11e4-b400-0cc47a024be0"; -FeedFilter filter = new FeedFilter.Builder().withIdLowerThan(aid).withLimit(5).build(); -List activities = flatActivityService.getActivities(filter).getResults(); +1. Is Android supported? -// Create a new activity -SimpleActivity activity = new SimpleActivity(); -activity.setActor("user:1"); -activity.setObject("tweet:1"); -activity.setVerb("tweet"); -activity.setForeignId("tweet:1"); -SimpleActivity response = flatActivityService.addActivity(activity); -``` +Yes. Use `client` for your backend and use `CloudClient` for your mobile application. -The API client allows you to send activities with custom field as well, you can find a complete example [here](https://github.com/GetStream/stream-java/blob/master/stream-repo-apache/src/test/java/io/getstream/client/example/mixtype/MixedType.java) +2. Cannot construct an instance of `io.getstream.core.models.*`, a model object in android. What is the problem? -```java -// Remove an activity by its id -feed.deleteActivity("e561de8f-00f1-11e4-b400-0cc47a024be0"); +If you're using proguard, ensure having following: `-keep class io.getstream.core.models.** { *; }` -// Remove activities by their foreign_id -feed.deleteActivityByForeignId("tweet:1"); +Additionally, we're using Jackson JSON processor and see [their definitions](https://github.com/FasterXML/jackson-docs/wiki/JacksonOnAndroid) too unless you're already using it. -// Follow another feed -feed.follow(flat", "42"); +## 📚 Full documentation -// Stop following another feed -feed.unfollow(flat", "42"); +Documentation for this Java client are available at the [Stream website](https://getstream.io/docs/?language=java). -// Batch adding activities -// This is not supported yet +For examples have a look [here](./example/Example.java). -// Batch following many feeds -// This is not supported yet +Docs are available on [GetStream.io](https://getstream.io/docs/?language=java). -// Add an activity and push it to other feeds too using the `to` field -// This is not supported yet +JavaDoc is available [here](https://getstream.github.io/stream-java/). -// Remove a feed and its content -// This is not supported yet +## 🧪 Building & Testing -// Generating tokens for client side usage -String token = feed.getToken(); +Run `gradlew test` to execute integration tests -// Javascript client side feed initialization -// user1 = client.feed('user', '1', '{{ token }}'); -// Retrieve first 10 followers of a feed -FeedFilter filter = new FeedFilter.Builder().withLimit(10).build(); -List followingPaged = feed.getFollowing(filter); +## ✍️ Contributing -// Retrieve the first 10 followed feeds -FeedFilter filter = new FeedFilter.Builder().withLimit(10).build(); -List followingPaged = feed.getFollowing(filter); +We welcome code changes that improve this library or fix a problem, please make sure to follow all best practices and add tests if applicable before submitting a Pull Request on Github. We are very happy to merge your code in the official repository. Make sure to sign our [Contributor License Agreement (CLA)](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) first. See our [license file](./LICENSE) for more details. -// Check if specific feeds are followed -// This is not supported yet +## 🧑‍💻 We are hiring! -``` +We've recently closed a [$38 million Series B funding round](https://techcrunch.com/2021/03/04/stream-raises-38m-as-its-chat-and-activity-feed-apis-power-communications-for-1b-users/) and we keep actively growing. +Our APIs are used by more than a billion end-users, and you'll have a chance to make a huge impact on the product within a team of the strongest engineers all over the world. -Docs are available on [GetStream.io](http://getstream.io/docs/). +Check out our current openings and apply via [Stream's website](https://getstream.io/team/#jobs). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..4094801e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Reporting a Vulnerability +At Stream we are committed to the security of our Software. We appreciate your efforts in disclosing vulnerabilities responsibly and we will make every effort to acknowledge your contributions. + +Report security vulnerabilities at the following email address: +``` +[security@getstream.io](mailto:security@getstream.io) +``` +Alternatively it is also possible to open a new issue in the affected repository, tagging it with the `security` tag. + +A team member will acknowledge the vulnerability and will follow-up with more detailed information. A representative of the security team will be in touch if more information is needed. + +# Information to include in a report +While we appreciate any information that you are willing to provide, please make sure to include the following: +* Which repository is affected +* Which branch, if relevant +* Be as descriptive as possible, the team will replicate the vulnerability before working on a fix. diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 00000000..1c68c5cc --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,16 @@ + + + + STREAM MARK + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..1b6fd533 --- /dev/null +++ b/build.gradle @@ -0,0 +1,89 @@ +plugins { + id 'java-library' + id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' + id 'com.diffplug.spotless' version '5.14.0' +} + +group 'io.getstream.client' +version = '3.25.0' +description = 'Stream Feeds official Java SDK' + +repositories { + mavenLocal() + mavenCentral() + maven { url "https://plugins.gradle.org/m2/" } + maven { url uri('https://repo.maven.apache.org/maven2/') } +} + +var jackson_version = '2.14.2' + +dependencies { + java { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + testImplementation 'junit:junit:4.13.1' + testImplementation 'com.pholser:junit-quickcheck-core:0.8.1' + testImplementation 'com.pholser:junit-quickcheck-generators:0.8.1' + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.2.0' + + implementation 'com.google.guava:guava:31.1-jre' + implementation 'com.squareup.okhttp3:okhttp:4.10.0' + implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version" + implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version" + implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" + implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version" + implementation 'com.auth0:java-jwt:4.2.2' + + api 'net.sourceforge.streamsupport:streamsupport:1.7.0' + api 'net.sourceforge.streamsupport:streamsupport-cfuture:1.7.0' +} + +def localProperties = new Properties() +def localPropertiesFile = project.rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + FileInputStream stream = new FileInputStream(localPropertiesFile) + localProperties.load(stream) +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = 'full' + events 'standard_out', 'standard_error', "passed", "skipped", "failed" + } + + doFirst { + // Inject local properties into tests runtime system properties + for (String key : localProperties.stringPropertyNames) { + systemProperty key, localProperties.getProperty(key).toString() + } + } +} + +def generatedVersionDir = "${buildDir}/generated-version" + +sourceSets { + main { + output.dir(generatedVersionDir, builtBy: 'generateVersionProperties') + } +} +/*spotless { + java { + googleJavaFormat() + } +}*/ +task generateVersionProperties { + doLast { + def propertiesFile = file "$generatedVersionDir/version.properties" + propertiesFile.parentFile.mkdirs() + def properties = new Properties() + properties.setProperty("version", rootProject.version.toString()) + propertiesFile.withWriter { properties.store(it, null) } + } +} +processResources.dependsOn generateVersionProperties + +apply from: "publish.gradle" diff --git a/data/test.jpg b/data/test.jpg new file mode 100644 index 00000000..fd87cca4 Binary files /dev/null and b/data/test.jpg differ diff --git a/data/test.txt b/data/test.txt new file mode 100644 index 00000000..f0c79c33 --- /dev/null +++ b/data/test.txt @@ -0,0 +1 @@ +Hello Stream! \ No newline at end of file diff --git a/example/Example.java b/example/Example.java new file mode 100644 index 00000000..772aad5b --- /dev/null +++ b/example/Example.java @@ -0,0 +1,523 @@ +package example; + +import static io.getstream.core.utils.Enrichment.createCollectionReference; +import static io.getstream.core.utils.Enrichment.createUserReference; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import io.getstream.client.Client; +import io.getstream.client.FlatFeed; +import io.getstream.client.NotificationFeed; +import io.getstream.core.KeepHistory; +import io.getstream.core.LookupKind; +import io.getstream.core.Region; +import io.getstream.core.models.*; +import io.getstream.core.options.*; +import java.io.File; +import java.net.URL; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; +import java.util.Map; + +class Example { + private static final String apiKey = System.getenv("STREAM_KEY") != null ? System.getenv("STREAM_KEY") + : System.getProperty("STREAM_KEY"); + private static final String secret = System.getenv("STREAM_SECRET") != null ? System.getenv("STREAM_SECRET") + : System.getProperty("STREAM_SECRET"); + + public static void main(String[] args) throws Exception { + Client client = Client.builder(apiKey, secret).build(); + + FlatFeed chris = client.flatFeed("user", "chris"); + // Add an Activity; message is a custom field - tip: you can add unlimited + // custom fields! + chris.addActivity(Activity.builder().actor("chris").verb("add").object("picture:10").foreignID("picture:10") + .extraField("message", "Beautiful bird!").build()); + + // Create a following relationship between Jack's "timeline" feed and Chris' + // "user" feed: + FlatFeed jack = client.flatFeed("timeline", "jack"); + jack.follow(chris); + + // Read Jack's timeline and Chris' post appears in the feed: + List response = jack.getActivities(new Pagination().limit(10)).join(); + for (Activity activity : response) { + // ... + } + + // Remove an Activity by referencing it's foreign_id + chris.removeActivityByForeignID("picture:10"); + + /* -------------------------------------------------------- */ + + // Instantiate a feed object + FlatFeed userFeed = client.flatFeed("user", "1"); + + // Add an activity to the feed, where actor, object and target are references to + // objects + // (`Eric`, `Hawaii`, `Places to Visit`) + Activity activity = Activity.builder().actor("User:1").verb("pin").object("Place:42").target("Board:1").build(); + userFeed.addActivity(activity); + + // Create a bit more complex activity + activity = Activity.builder().actor("User:1").verb("run").object("Exercise:42").foreignID("run:1") + .extra(new ImmutableMap.Builder() + .put("course", + new ImmutableMap.Builder().put("name", "Golden Gate park") + .put("distance", 10).build()) + .put("participants", new String[] { "Thierry", "Tommaso", }) + .put("started_at", LocalDateTime.now()) + .put("location", + new ImmutableMap.Builder().put("type", "point") + .put("coordinates", new double[] { 37.769722, -122.476944 }).build()) + .build()) + .build(); + userFeed.addActivity(activity); + + // Remove an activity by its id + userFeed.removeActivityByID("e561de8f-00f1-11e4-b400-0cc47a024be0"); + + // Remove activities with foreign_id 'run:1' + userFeed.removeActivityByForeignID("run:1"); + + activity = Activity.builder().actor("1").verb("like").object("3").time(new Date()).foreignID("like:3") + .extraField("popularity", 100).build(); + + // first time the activity is added + userFeed.addActivity(activity); + + // update the popularity value for the activity + activity = Activity.builder().fromActivity(activity).extraField("popularity", 10).build(); + + client.batch().updateActivities(activity); + + /* -------------------------------------------------------- */ + + // partial update by activity ID + + // prepare the set operations + Map set = new ImmutableMap.Builder().put("product.price", 19.99) + .put("shares", + new ImmutableMap.Builder().put("facebook", "...").put("twitter", "...").build()) + .build(); + // prepare the unset operations + String[] unset = new String[] { "daily_likes", "popularity" }; + + String id = "54a60c1e-4ee3-494b-a1e3-50c06acb5ed4"; + client.updateActivityByID(id, set, unset); + + String foreignID = "product:123"; + Date timestamp = new Date(); + client.updateActivityByForeignID(foreignID, timestamp, set, unset); + + FeedID[] add = new FeedID[0]; + FeedID[] remove = new FeedID[0]; + userFeed.updateActivityToTargets(activity, add, remove); + + FeedID[] newTargets = new FeedID[0]; + userFeed.replaceActivityToTargets(activity, newTargets); + + /* -------------------------------------------------------- */ + + Date now = new Date(); + Activity firstActivity = userFeed + .addActivity( + Activity.builder().actor("1").verb("like").object("3").time(now).foreignID("like:3").build()) + .join(); + Activity secondActivity = userFeed.addActivity(Activity.builder().actor("1").verb("like").object("3").time(now) + .extraField("extra", "extra_value").foreignID("like:3").build()).join(); + // foreign ID and time are the same for both activities + // hence only one activity is created and first and second IDs are equal + // firstActivity.ID == secondActivity.ID + + /* -------------------------------------------------------- */ + + // Get 5 activities with id less than the given UUID (Faster - Recommended!) + response = userFeed.getActivities(new Filter().idLessThan("e561de8f-00f1-11e4-b400-0cc47a024be0").limit(5)) + .join(); + // Get activities from 5 to 10 (Pagination-based - Slower) + response = userFeed.getActivities(new Pagination().offset(0).limit(5)).join(); + // Get activities sorted by rank (Ranked Feeds Enabled): + response = userFeed.getActivities(new Pagination().limit(5), "popularity").join(); + + /* -------------------------------------------------------- */ + + // timeline:timeline_feed_1 follows user:user_42 + FlatFeed user = client.flatFeed("user", "user_42"); + FlatFeed timeline = client.flatFeed("timeline", "timeline_feed_1"); + timeline.follow(user); + + // follow feed without copying the activities: + timeline.follow(user, 0); + + /* -------------------------------------------------------- */ + + // user := client.FlatFeed("user", "42") + + // Stop following feed user:user_42 + timeline.unfollow(user); + + // Stop following feed user:user_42 but keep history of activities + timeline.unfollow(user, KeepHistory.YES); + + // list followers + List followers = userFeed.getFollowers(new Pagination().offset(0).limit(10)).join(); + for (FollowRelation follow : followers) { + System.out.format("%s -> %s", follow.getSource(), follow.getTarget()); + // ... + } + + // Retrieve last 10 feeds followed by user_feed_1 + List followed = userFeed.getFollowed(new Pagination().offset(0).limit(10)).join(); + + // Retrieve 10 feeds followed by user_feed_1 starting from the 11th + followed = userFeed.getFollowed(new Pagination().offset(10).limit(10)).join(); + + // Check if user_feed_1 follows specific feeds + followed = userFeed + .getFollowed(new Pagination().offset(0).limit(2), new FeedID("user:42"), new FeedID("user", "43")) + .join(); + + /* -------------------------------------------------------- */ + + NotificationFeed notifications = client.notificationFeed("notifications", "1"); + // Mark all activities in the feed as seen + List> activityGroups = notifications.getActivities(new ActivityMarker().allSeen()) + .join(); + for (NotificationGroup group : activityGroups) { + // ... + } + // Mark some activities as read via specific Activity Group Ids + activityGroups = notifications.getActivities(new ActivityMarker().read("groupID1", "groupID2" /* ... */)) + .join(); + + /* -------------------------------------------------------- */ + + // Add an activity to the feed, where actor, object and target are references to + // objects - + // adding your ranking method as a parameter (in this case, "popularity"): + activity = Activity.builder().actor("User:1").verb("pin").object("place:42").target("board:1") + .extraField("popularity", 5).build(); + userFeed.addActivity(activity); + + // Get activities sorted by the ranking method labelled 'activity_popularity' + // (Ranked Feeds + // Enabled) + response = userFeed.getActivities(new Pagination().limit(5), "activity_popularity").join(); + + /* -------------------------------------------------------- */ + + // Add the activity to Eric's feed and to Jessica's notification feed + activity = Activity.builder().actor("User:Eric").verb("tweet").object("tweet:id") + .to(Lists.newArrayList(new FeedID("notification:Jessica"))) + .extraField("message", "@Jessica check out getstream.io it's so dang awesome.").build(); + userFeed.addActivity(activity); + + // The TO field ensures the activity is send to the player, match and team feed + activity = Activity.builder().actor("Player:Suarez").verb("foul").object("Player:Ramos") + .to(Lists.newArrayList(new FeedID("team:barcelona"), new FeedID("match:1"))) + .extraField("match", ImmutableMap.of("El Classico", 10)).build(); + // playerFeed.addActivity(activity); + userFeed.addActivity(activity); + + // Get activities sorted by the ranking method along with externalRankingVars + Map mp=new LinkedHashMap(); + + mp.put("boolVal",true); + mp.put("music",1); + mp.put("sports",2.1); + mp.put("string","str"); + response = userFeed.feed.getActivities( + new Limit(69), + new Offset(13), + DefaultOptions.DEFAULT_FILTER, + "rank", + new RankingVars(mp) + ); + /* -------------------------------------------------------- */ + + // Batch following many feeds + // Let timeline:1 will follow user:1, user:2 and user:3 + FollowRelation[] follows = new FollowRelation[] { new FollowRelation("timeline:1", "user:1"), + new FollowRelation("timeline:1", "user:2"), new FollowRelation("timeline:1", "user:3") }; + client.batch().followMany(follows); + // copy only the last 10 activities from every feed + client.batch().followMany(10, follows); + + /* -------------------------------------------------------- */ + + Activity[] activities = new Activity[] { + Activity.builder().actor("User:1").verb("tweet").object("Tweet:1").build(), + Activity.builder().actor("User:2").verb("watch").object("Movie:1").build() }; + userFeed.addActivities(activities); + + /* -------------------------------------------------------- */ + + // adds 1 activity to many feeds in one request + activity = Activity.builder().actor("User:2").verb("pin").object("Place:42").target("Board:1").build(); + FeedID[] feeds = new FeedID[] { new FeedID("timeline", "1"), new FeedID("timeline", "2"), + new FeedID("timeline", "3"), new FeedID("timeline", "4") }; + client.batch().addToMany(activity, feeds); + + /* -------------------------------------------------------- */ + + // retrieve two activities by ID + client.batch().getActivitiesByID("01b3c1dd-e7ab-4649-b5b3-b4371d8f7045", "ed2837a6-0a3b-4679-adc1-778a1704852"); + + // retrieve an activity by foreign ID and time + client.batch().getActivitiesByForeignID(new ForeignIDTimePair("foreignID1", new Date()), + new ForeignIDTimePair("foreignID2", new Date())); + + /* -------------------------------------------------------- */ + + // connect to the us-east region + client = Client.builder(apiKey, secret).region(Region.US_EAST).build(); + + /* -------------------------------------------------------- */ + Reaction like = new Reaction.Builder().kind("like").activityID(activity.getID()).build(); + + // add a like reaction to the activity with id activityId + like = client.reactions().add("john-doe", like).join(); + + Reaction comment = new Reaction.Builder().kind("comment").activityID(activity.getID()) + .extraField("text", "awesome post!").build(); + + // adds a comment reaction to the activity with id activityId + comment = client.reactions().add("john-doe", comment).join(); + + /* -------------------------------------------------------- */ + + // first let's read current user's timeline feed and pick one activity + response = client.flatFeed("timeline", "mike").getActivities().join(); + activity = response.get(0); + + // then let's add a like reaction to that activity + client.reactions().add("john-doe", Reaction.builder().kind("like").activityID(activity.getID()).build()); + + /* -------------------------------------------------------- */ + + comment = new Reaction.Builder().kind("comment").activityID(activity.getID()) + .extraField("text", "awesome post!").build(); + + // adds a comment reaction to the activity and notify Thierry's notification + // feed + client.reactions().add("john-doe", comment, new FeedID("notification:thierry")); + + /* -------------------------------------------------------- */ + + // read bob's timeline and include most recent reactions to all activities and + // their total count + client.flatFeed("timeline", "bob") + .getEnrichedActivities(new EnrichmentFlags().withRecentReactions().withReactionCounts()); + + // read bob's timeline and include most recent reactions to all activities and + // her own reactions + client.flatFeed("timeline", "bob").getEnrichedActivities( + new EnrichmentFlags().withOwnReactions().withRecentReactions().withReactionCounts()); + + /* -------------------------------------------------------- */ + + // retrieve all kind of reactions for an activity + List reactions = client.reactions() + .filter(LookupKind.ACTIVITY, "ed2837a6-0a3b-4679-adc1-778a1704852d").join(); + + // retrieve first 10 likes for an activity + reactions = client.reactions() + .filter(LookupKind.ACTIVITY, "ed2837a6-0a3b-4679-adc1-778a1704852d", new Filter().limit(10), "like") + .join(); + + // retrieve the next 10 likes using the id_lt param + reactions = client.reactions().filter(LookupKind.ACTIVITY, "ed2837a6-0a3b-4679-adc1-778a1704852d", + new Filter().idLessThan("e561de8f-00f1-11e4-b400-0cc47a024be0"), "like").join(); + + /* -------------------------------------------------------- */ + + // adds a like to the previously created comment + Reaction reaction = client.reactions() + .addChild("john-doe", comment.getId(), Reaction.builder().kind("like").build()).join(); + + /* -------------------------------------------------------- */ + + client.reactions().update(Reaction.builder().id(reaction.getId()).extraField("text", "love it!").build()); + + /* -------------------------------------------------------- */ + + client.reactions().delete(reaction.getId()); + + /* -------------------------------------------------------- */ + + client.collections().add("food", + new CollectionData("cheese-burger").set("name", "Cheese Burger").set("rating", "4 stars")); + + // if you don't have an id on your side, just use null as the ID and Stream will + // generate a + // unique ID + client.collections().add("food", new CollectionData().set("name", "Cheese Burger").set("rating", "4 stars")); + + /* -------------------------------------------------------- */ + + CollectionData collection = client.collections().get("food", "cheese-burger").join(); + + /* -------------------------------------------------------- */ + + client.collections().delete("food", "cheese-burger"); + + /* -------------------------------------------------------- */ + + client.collections().update("food", + new CollectionData("cheese-burger").set("name", "Cheese Burger").set("rating", "1 star")); + + /* -------------------------------------------------------- */ + + client.collections().upsert("visitor", + new CollectionData("123").set("name", "John").set("favorite_color", "blue"), + new CollectionData("124").set("name", "Jane").set("favorite_color", "purple").set("interests", + Lists.newArrayList("fashion", "jazz"))); + + /* -------------------------------------------------------- */ + + // select the entries with ID 123 and 124 from items collection + List objects = client.collections().select("items", "123", "124").join(); + + /* -------------------------------------------------------- */ + + // delete the entries with ID 123 and 124 from visitor collection + client.collections().deleteMany("visitor", "123", "124"); + + /* -------------------------------------------------------- */ + + // first we add our object to the food collection + CollectionData cheeseBurger = client.collections() + .add("food", new CollectionData("123").set("name", "Cheese Burger").set("ingredients", + Lists.newArrayList("cheese", "burger", "bread", "lettuce", "tomato"))) + .join(); + + // the object returned by .add can be embedded directly inside of an activity + userFeed.addActivity(Activity.builder().actor(createUserReference("john-doe")).verb("grill") + .object(createCollectionReference(cheeseBurger.getCollection(), cheeseBurger.getID())).build()); + + // if we now read the feed, the activity we just added will include the entire + // full object + userFeed.getEnrichedActivities(); + + // we can then update the object and Stream will propagate the change to all + // activities + client.collections() + .update(cheeseBurger.getCollection(), cheeseBurger.set("name", "Amazing Cheese Burger") + .set("ingredients", Lists.newArrayList("cheese", "burger", "bread", "lettuce", "tomato"))) + .join(); + + /* -------------------------------------------------------- */ + + // First create a collection entry with upsert api + client.collections().upsert("food", new CollectionData().set("name", "Cheese Burger")); + + // Then create a user + client.user("john-doe").create( + new Data().set("name", "John Doe").set("occupation", "Software Engineer").set("gender", "male")); + + // Since we know their IDs we can create references to both without reading from + // APIs + String cheeseBurgerRef = createCollectionReference("food", "cheese-burger"); + String johnDoeRef = createUserReference("john-doe"); + + client.flatFeed("user", "john") + .addActivity(Activity.builder().actor(johnDoeRef).verb("eat").object(cheeseBurgerRef).build()); + + /* -------------------------------------------------------- */ + + // create a new user, if the user already exist an error is returned + client.user("john-doe").create( + new Data().set("name", "John Doe").set("occupation", "Software Engineer").set("gender", "male")); + + // get or create a new user, if the user already exist the user is returned + client.user("john-doe").getOrCreate( + new Data().set("name", "John Doe").set("occupation", "Software Engineer").set("gender", "male")); + + /* -------------------------------------------------------- */ + + client.user("123").get(); + + /* -------------------------------------------------------- */ + + client.user("123").delete(); + + /* -------------------------------------------------------- */ + + client.user("123").update( + new Data().set("name", "Jane Doe").set("occupation", "Software Engineer").set("gender", "female")); + + /* -------------------------------------------------------- */ + + // Read the personalization feed for a given user + client.personalization().get("personalized_feed", + new ImmutableMap.Builder().put("user_id", 123).put("feed_slug", "timeline").build()); + + // Our data science team will typically tell you which endpoint to use + client.personalization().get("discovery_feed", new ImmutableMap.Builder().put("user_id", 123) + .put("source_feed_slug", "timeline").put("target_feed_slug", "user").build()); + + /* -------------------------------------------------------- */ + + client.analytics() + .trackEngagement(Engagement + .builder().feedID("user:thierry").content(new Content("message:34349698").set("verb", "share") + .set("actor", ImmutableMap.of("1", "user1"))) + .boost(2).location("profile_page").position(3).build()); + + /* -------------------------------------------------------- */ + + client.analytics().trackImpression(Impression.builder() + .contentList( + new Content("tweet:34349698").set("verb", "share").set("actor", ImmutableMap.of("1", "user1")), + new Content("tweet:34349699"), new Content("tweet:34349700")) + .feedID("flat:tommaso").location("android-app").build()); + + /* -------------------------------------------------------- */ + + // the URL to direct to + URL targetURL = new URL("http://mysite.com/detail"); + + // track the impressions and a click + List impressions = Lists.newArrayList( + Impression.builder().contentList(new Content("tweet:1"), new Content("tweet:2"), new Content("tweet:3")) + .userData(new UserData("tommaso", null)).location("email").feedID("user:global").build()); + List engagements = Lists + .newArrayList(Engagement.builder().content(new Content("tweet:2")).label("click").position(1) + .userData(new UserData("tommaso", null)).location("email").feedID("user:global").build()); + + // when the user opens the tracking URL in their browser gets redirected to the + // target URL + // the events are added to our analytics platform + URL trackingURL = client.analytics().createRedirectURL(targetURL, impressions, engagements); + + /* -------------------------------------------------------- */ + + File image = new File("..."); + URL imageURL = client.images().upload(image).join(); + + File file = new File("..."); + URL fileURL = client.files().upload(file).join(); + + /* -------------------------------------------------------- */ + + // deleting an image using the url returned by the APIs + client.images().delete(imageURL); + + // deleting a file using the url returned by the APIs + client.files().delete(fileURL); + + /* -------------------------------------------------------- */ + + // create a 50x50 thumbnail and crop from center + client.images().process(imageURL, new Resize(50, 50, Resize.Type.CROP)); + + // create a 50x50 thumbnail using clipping (keeps aspect ratio) + client.images().process(imageURL, new Resize(50, 50, Resize.Type.CLIP)); + + /* -------------------------------------------------------- */ + + OGData urlPreview = client.openGraph(new URL("http://www.imdb.com/title/tt0117500/")).join(); + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..033e24c4 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..9f4197d5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..fcb6fca1 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..6689b85b --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/local.properties.example b/local.properties.example new file mode 100644 index 00000000..fdfc7f1f --- /dev/null +++ b/local.properties.example @@ -0,0 +1,6 @@ +ossrhUsername= +ossrhPassword= +signing.keyId= +signing.password= +signing.secretKeyRingFile= +sonatypeStagingProfileId= diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 3cf263c7..00000000 --- a/pom.xml +++ /dev/null @@ -1,216 +0,0 @@ - - - 4.0.0 - - io.getstream.client - stream-java - pom - 0.1-RC6-SNAPSHOT - - stream-java - stream-java is a Java client for http://getstream.io. - https://github.com/GetStream/stream-java - - - - The New BSD License - http://www.opensource.org/licenses/bsd-license.html - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - - - - - sirio7g - Alessandro Pieri - https://github.com/sirio7g - - - tbarbugli - Tommaso Barbugli - https://github.com/tbarbugli - - - - - scm:git:git@github.com:GetStream/stream-java.git - scm:git:git@github.com:GetStream/stream-java.git - git@github.com:GetStream/stream-java.git - HEAD - - - - stream-core - stream-repo-apache - stream-repo-okhttp - - - - UTF-8 - 1.6.6 - 1.1.2 - 18.0 - 2.4.3 - 1.0 - 4.11 - - - - - - org.slf4j - slf4j-api - ${slf4j-api.version} - - - ch.qos.logback - logback-classic - ${logback.version} - - - ch.qos.logback - logback-core - ${logback.version} - - - com.google.guava - guava - ${guava.version} - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - org.tomitribe - tomitribe-http-signatures - ${tomitribe-http-signatures.version} - - - - - junit - junit - ${junit.version} - test - - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - - - org.apache.maven.plugins - maven-release-plugin - 2.5 - - false - release - deploy - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 - true - - ossrh - https://oss.sonatype.org/ - true - - - - maven-compiler-plugin - 2.3.2 - - 1.7 - 1.7 - - - - maven-release-plugin - 2.5 - - - - - - - release - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - - diff --git a/publish.gradle b/publish.gradle new file mode 100644 index 00000000..1d7563bf --- /dev/null +++ b/publish.gradle @@ -0,0 +1,95 @@ +apply plugin: 'maven-publish' +apply plugin: 'signing' + +// Create variables with empty default values +ext["ossrhUsername"] = '' +ext["ossrhPassword"] = '' +ext["signing.keyId"] = '' +ext["signing.password"] = '' +ext["signing.secretKeyRingFile"] = '' +ext["sonatypeStagingProfileId"] = '' + +File secretPropsFile = project.rootProject.file('local.properties') +if (secretPropsFile.exists()) { + // Read local.properties file first if it exists + Properties p = new Properties() + new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } + p.each { name, value -> ext[name] = value } +} else { + // Use system environment variables + ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') + ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') + ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') + ext["signing.password"] = System.getenv('SIGNING_PASSWORD') + ext["signing.secretKeyRingFile"] = System.getenv('SIGNING_SECRET_KEY_RING_FILE') + ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') +} + +nexusPublishing { + repositories { + sonatype { + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) + stagingProfileId = sonatypeStagingProfileId + username = ossrhUsername + password = ossrhPassword + } + } +} + +task javadocJar(type: Jar) { + archiveClassifier = 'javadoc' + from javadoc +} + +task sourcesJar(type: Jar) { + archiveClassifier = 'sources' + from sourceSets.main.allSource +} + +artifacts { + archives javadocJar, sourcesJar +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.java + artifactId 'stream-java' + + artifact sourcesJar + artifact javadocJar + + pom { + name = "Stream Feeds official Java API Client" + description = "Stream Feeds Java Client for backend and android integrations" + url = 'https://github.com/getstream/stream-chat-java' + licenses { + license { + name = 'The 3-Clause BSD License' + url = 'https://opensource.org/licenses/BSD-3-Clause' + distribution = 'repo' + } + } + developers { + developer { + id = 'getstream-support' + name = 'Stream Support' + email = 'support@getstream.io' + } + } + scm { + connection = 'scm:git:github.com/getstream/stream-java.git' + developerConnection = 'scm:git:ssh://github.com/getstream/stream-java.git' + url = 'https://github.com/getstream/stream-java' + } + } + } + } + } +} + +signing { + sign publishing.publications +} diff --git a/scripts/get_changelog_diff.js b/scripts/get_changelog_diff.js new file mode 100644 index 00000000..ce034389 --- /dev/null +++ b/scripts/get_changelog_diff.js @@ -0,0 +1,26 @@ +/* +Here we're trying to parse the latest changes from CHANGELOG.md file. +The changelog looks like this: + +## 0.0.3 +- Something #3 +## 0.0.2 +- Something #2 +## 0.0.1 +- Something #1 + +In this case we're trying to extract "- Something #3" since that's the latest change. +*/ +module.exports = () => { + const fs = require('fs') + + changelog = fs.readFileSync('CHANGELOG.md', 'utf8') + releases = changelog.match(/## [?[0-9](.+)/g) + + current_release = changelog.indexOf(releases[0]) + previous_release = changelog.indexOf(releases[1]) + + latest_changes = changelog.substr(current_release, previous_release - current_release) + + return latest_changes +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..d320843b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'stream-java' diff --git a/src/main/java/io/getstream/client/AggregatedFeed.java b/src/main/java/io/getstream/client/AggregatedFeed.java new file mode 100644 index 00000000..107e7229 --- /dev/null +++ b/src/main/java/io/getstream/client/AggregatedFeed.java @@ -0,0 +1,731 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Group; +import io.getstream.core.options.*; +import io.getstream.core.utils.DefaultOptions; +import java.io.IOException; +import java.util.List; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public class AggregatedFeed extends Feed { + AggregatedFeed(Client client, FeedID id) { + super(client, id); + } + + public CompletableFuture>> getActivities() + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Limit limit) + throws StreamException { + return getActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Offset offset) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Filter filter) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities( + ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker); + } + + public CompletableFuture>> getActivities( + Limit limit, Offset offset) throws StreamException { + return getActivities( + limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities( + Limit limit, Filter filter) throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities( + Limit limit, ActivityMarker marker) throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getActivities( + Filter filter, ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture>> getActivities( + Offset offset, ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getActivities( + Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture>> getActivities( + Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture>> getActivities( + Limit limit, Offset offset, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), limit, offset, filter, marker) + .thenApply( + response -> { + try { + return deserializeContainer(response, Group.class, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getCustomActivities( + Class type) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit) throws StreamException { + return getCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Offset offset) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Filter filter) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Offset offset, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, limit, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, limit, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture>> getCustomActivities( + Class type, Limit limit, Offset offset, Filter filter, ActivityMarker marker) + throws StreamException { + return getClient() + .getActivities(getID(), limit, offset, filter, marker) + .thenApply( + response -> { + try { + return deserializeContainer(response, Group.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> + getEnrichedActivities() throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Offset offset) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Filter filter) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Filter filter) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Offset offset, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getClient() + .getEnrichedActivities(getID(), limit, offset, filter, marker, flags) + .thenApply( + response -> { + try { + return deserializeContainer(response, Group.class, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getEnrichedActivities( + RequestOption... options) throws StreamException { + // If no options provided, use defaults + if (options == null || options.length == 0) { + options = new RequestOption[] { + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + DefaultOptions.DEFAULT_MARKER + }; + } + + return getClient() + .getEnrichedActivities(getID(), options) + .thenApply( + response -> { + try { + return deserializeContainer(response, Group.class, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture>> getEnrichedCustomActivities( + Class type, + Limit limit, + Offset offset, + Filter filter, + ActivityMarker marker, + EnrichmentFlags flags) + throws StreamException { + return getClient() + .getEnrichedActivities(getID(), limit, offset, filter, marker, flags) + .thenApply( + response -> { + try { + return deserializeContainer(response, Group.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/client/AnalyticsClient.java b/src/main/java/io/getstream/client/AnalyticsClient.java new file mode 100644 index 00000000..7c03cbd3 --- /dev/null +++ b/src/main/java/io/getstream/client/AnalyticsClient.java @@ -0,0 +1,62 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Auth.buildAnalyticsRedirectToken; +import static io.getstream.core.utils.Auth.buildAnalyticsToken; + +import com.google.common.collect.Iterables; +import io.getstream.core.StreamAnalytics; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.Engagement; +import io.getstream.core.models.Impression; +import io.getstream.core.utils.Auth.TokenAction; +import java.net.URL; +import java8.util.concurrent.CompletableFuture; + +public final class AnalyticsClient { + private final String secret; + private final StreamAnalytics analytics; + + AnalyticsClient(String secret, StreamAnalytics analytics) { + this.secret = secret; + this.analytics = analytics; + } + + public CompletableFuture trackEngagement(Iterable events) + throws StreamException { + return trackEngagement(Iterables.toArray(events, Engagement.class)); + } + + public CompletableFuture trackEngagement(Engagement... events) throws StreamException { + final Token token = buildAnalyticsToken(secret, TokenAction.WRITE); + return analytics.trackEngagement(token, events); + } + + public CompletableFuture trackImpression(Impression event) throws StreamException { + final Token token = buildAnalyticsToken(secret, TokenAction.WRITE); + return analytics.trackImpression(token, event); + } + + public URL createRedirectURL(URL url, Engagement... engagements) throws StreamException { + return createRedirectURL(url, new Impression[0], engagements); + } + + public URL createRedirectURL(URL url, Impression... impressions) throws StreamException { + return createRedirectURL(url, impressions, new Engagement[0]); + } + + public URL createRedirectURL( + URL url, Iterable impressions, Iterable engagements) + throws StreamException { + return createRedirectURL( + url, + Iterables.toArray(impressions, Impression.class), + Iterables.toArray(engagements, Engagement.class)); + } + + public URL createRedirectURL(URL url, Impression[] impressions, Engagement[] engagements) + throws StreamException { + final Token token = buildAnalyticsRedirectToken(secret); + return analytics.createRedirectURL(token, url, impressions, engagements); + } +} diff --git a/src/main/java/io/getstream/client/AuditLogsClient.java b/src/main/java/io/getstream/client/AuditLogsClient.java new file mode 100644 index 00000000..18e592a0 --- /dev/null +++ b/src/main/java/io/getstream/client/AuditLogsClient.java @@ -0,0 +1,97 @@ +package io.getstream.client; + +import io.getstream.core.Stream; +import io.getstream.core.http.Token; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.AuditLog; +import io.getstream.core.options.RequestOption; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.utils.Auth.TokenAction; +import java8.util.concurrent.CompletableFuture; + +import java.util.ArrayList; +import java.util.List; + +import static io.getstream.core.utils.Auth.buildAuditLogsToken; + +/** + * Client for querying Stream audit logs. + * Audit logs record changes to various entities within your Stream app. + */ +public final class AuditLogsClient { + private final String secret; + private final Stream stream; + + public AuditLogsClient(String secret, Stream stream) { + this.secret = secret; + this.stream = stream; + } + + /** + * Query audit logs with the specified filters and default pagination. + * + * @param filters Filters to apply to the query (either entityType+entityID OR userID is required) + * @return CompletableFuture with the query response + * @throws StreamException if the filters are invalid or if there's an API error + */ + public CompletableFuture queryAuditLogs(QueryAuditLogsFilters filters) throws StreamException { + return queryAuditLogs(filters, new QueryAuditLogsPager()); + } + + /** + * Query audit logs with the specified filters and pagination. + * + * @param filters Filters to apply to the query (either entityType+entityID OR userID is required) + * @param pager Pagination settings for the query + * @return CompletableFuture with the query response + * @throws StreamException if the filters are invalid or if there's an API error + */ + public CompletableFuture queryAuditLogs(QueryAuditLogsFilters filters, QueryAuditLogsPager pager) throws StreamException { + // Validate filters before making the API call + if (filters == null) { + throw new StreamException("Filters cannot be null for audit logs queries"); + } + + final Token token = buildAuditLogsToken(secret, TokenAction.READ); + + RequestOption[] options = buildRequestOptions(filters, pager); + return stream.queryAuditLogs(token, options); + } + + /** + * Builds request options from filters and pagination settings. + * + * @param filters Filters to apply to the query + * @param pager Pagination settings + * @return Array of RequestOption for the API call + */ + private RequestOption[] buildRequestOptions(QueryAuditLogsFilters filters, QueryAuditLogsPager pager) { + List options = new ArrayList<>(); + + if (filters.getEntityType() != null && !filters.getEntityType().isEmpty() && + filters.getEntityID() != null && !filters.getEntityID().isEmpty()) { + options.add(new CustomQueryParameter("entity_type", filters.getEntityType())); + options.add(new CustomQueryParameter("entity_id", filters.getEntityID())); + } + + if (filters.getUserID() != null && !filters.getUserID().isEmpty()) { + options.add(new CustomQueryParameter("user_id", filters.getUserID())); + } + + if (pager != null) { + if (pager.getNext() != null && !pager.getNext().isEmpty()) { + options.add(new CustomQueryParameter("next", pager.getNext())); + } + + if (pager.getPrev() != null && !pager.getPrev().isEmpty()) { + options.add(new CustomQueryParameter("prev", pager.getPrev())); + } + + if (pager.getLimit() > 0) { + options.add(new CustomQueryParameter("limit", Integer.toString(pager.getLimit()))); + } + } + + return options.toArray(new RequestOption[0]); + } +} \ No newline at end of file diff --git a/src/main/java/io/getstream/client/BatchClient.java b/src/main/java/io/getstream/client/BatchClient.java new file mode 100644 index 00000000..e9cd30be --- /dev/null +++ b/src/main/java/io/getstream/client/BatchClient.java @@ -0,0 +1,137 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Auth.*; + +import com.google.common.collect.Iterables; +import io.getstream.core.KeepHistory; +import io.getstream.core.StreamBatch; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.*; +import io.getstream.core.options.EnrichmentFlags; +import io.getstream.core.utils.DefaultOptions; +import java.util.List; +import java8.util.J8Arrays; +import java8.util.concurrent.CompletableFuture; + +public final class BatchClient { + private final String secret; + private final StreamBatch batch; + + BatchClient(String secret, StreamBatch batch) { + this.secret = secret; + this.batch = batch; + } + + public CompletableFuture addToMany(Activity activity, FeedID... feeds) + throws StreamException { + final Token token = buildFeedToken(secret, TokenAction.WRITE); + return batch.addToMany(token, activity, feeds); + } + + public CompletableFuture followMany(int activityCopyLimit, FollowRelation... follows) + throws StreamException { + final Token token = buildFollowToken(secret, TokenAction.WRITE); + return batch.followMany(token, activityCopyLimit, follows); + } + + public CompletableFuture followMany(int activityCopyLimit, Iterable follows) + throws StreamException { + return followMany(activityCopyLimit, Iterables.toArray(follows, FollowRelation.class)); + } + + public CompletableFuture followMany(FollowRelation... follows) throws StreamException { + return followMany(DefaultOptions.DEFAULT_ACTIVITY_COPY_LIMIT, follows); + } + + public CompletableFuture followMany(Iterable follows) + throws StreamException { + return followMany(Iterables.toArray(follows, FollowRelation.class)); + } + + public CompletableFuture unfollowMany(FollowRelation... follows) throws StreamException { + final Token token = buildFollowToken(secret, TokenAction.WRITE); + final UnfollowOperation[] ops = + J8Arrays.stream(follows) + .map(follow -> new UnfollowOperation(follow, io.getstream.core.KeepHistory.YES)) + .toArray(UnfollowOperation[]::new); + return batch.unfollowMany(token, ops); + } + + public CompletableFuture unfollowMany(KeepHistory keepHistory, FollowRelation... follows) + throws StreamException { + final Token token = buildFollowToken(secret, TokenAction.WRITE); + final UnfollowOperation[] ops = + J8Arrays.stream(follows) + .map(follow -> new UnfollowOperation(follow, keepHistory)) + .toArray(UnfollowOperation[]::new); + return batch.unfollowMany(token, ops); + } + + public CompletableFuture unfollowMany(UnfollowOperation... unfollows) + throws StreamException { + final Token token = buildFollowToken(secret, TokenAction.WRITE); + return batch.unfollowMany(token, unfollows); + } + + public CompletableFuture> getActivitiesByID(Iterable activityIDs) + throws StreamException { + return getActivitiesByID(Iterables.toArray(activityIDs, String.class)); + } + + public CompletableFuture> getActivitiesByID(String... activityIDs) + throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.READ); + return batch.getActivitiesByID(token, activityIDs); + } + + public CompletableFuture> getEnrichedActivitiesByID( + Iterable activityIDs) throws StreamException { + return getEnrichedActivitiesByID(Iterables.toArray(activityIDs, String.class)); + } + + public CompletableFuture> getEnrichedActivitiesByID(String... activityIDs) + throws StreamException { + return getEnrichedActivitiesByID(DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, activityIDs); + } + + public CompletableFuture> getEnrichedActivitiesByID( + EnrichmentFlags flags, String... activityIDs) throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.READ); + return batch.getEnrichedActivitiesByID(token, flags, activityIDs); + } + + public CompletableFuture> getActivitiesByForeignID( + Iterable activityIDTimePairs) throws StreamException { + return getActivitiesByForeignID( + Iterables.toArray(activityIDTimePairs, ForeignIDTimePair.class)); + } + + public CompletableFuture> getActivitiesByForeignID( + ForeignIDTimePair... activityIDTimePairs) throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.READ); + return batch.getActivitiesByForeignID(token, activityIDTimePairs); + } + + public CompletableFuture> getEnrichedActivitiesByForeignID( + Iterable activityIDTimePairs) throws StreamException { + return getEnrichedActivitiesByForeignID( + Iterables.toArray(activityIDTimePairs, ForeignIDTimePair.class)); + } + + public CompletableFuture> getEnrichedActivitiesByForeignID( + ForeignIDTimePair... activityIDTimePairs) throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.READ); + return batch.getEnrichedActivitiesByForeignID(token, activityIDTimePairs); + } + + public CompletableFuture updateActivities(Iterable activities) + throws StreamException { + return updateActivities(Iterables.toArray(activities, Activity.class)); + } + + public CompletableFuture updateActivities(Activity... activities) throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.WRITE); + return batch.updateActivities(token, activities); + } +} diff --git a/src/main/java/io/getstream/client/Client.java b/src/main/java/io/getstream/client/Client.java new file mode 100644 index 00000000..49fba80c --- /dev/null +++ b/src/main/java/io/getstream/client/Client.java @@ -0,0 +1,387 @@ +package io.getstream.client; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Auth.*; + +import com.google.common.collect.Iterables; +import io.getstream.core.Region; +import io.getstream.core.Stream; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.OKHTTPClientAdapter; +import io.getstream.core.http.Response; +import io.getstream.core.http.Token; +import io.getstream.core.models.*; +import io.getstream.core.options.RequestOption; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import io.getstream.core.utils.Auth; +import java8.util.concurrent.CompletableFuture; + +public final class Client { + private final String secret; + private final Stream stream; + + private Client(String key, String secret, URL baseURL, HTTPClient httpClient) { + this.secret = secret; + this.stream = new Stream(key, baseURL, httpClient); + } + + public static Builder builder(String apiKey, String secret) { + return new Builder(apiKey, secret); + } + + public CompletableFuture updateActivityByID( + String id, Map set, Iterable unset) throws StreamException { + return updateActivityByID(id, set, Iterables.toArray(unset, String.class)); + } + + public CompletableFuture updateActivityByID(ActivityUpdate update) + throws StreamException { + return updateActivityByID(update.getID(), update.getSet(), update.getUnset()); + } + + public CompletableFuture updateActivityByID( + String id, Map set, String[] unset) throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.WRITE); + return stream.updateActivityByID(token, id, set, unset); + } + + public CompletableFuture updateActivityByForeignID( + ForeignIDTimePair foreignIDTimePair, Map set, Iterable unset) + throws StreamException { + checkNotNull(foreignIDTimePair, "No activity to update"); + return updateActivityByForeignID( + foreignIDTimePair.getForeignID(), foreignIDTimePair.getTime(), set, unset); + } + + public CompletableFuture updateActivityByForeignID( + ForeignIDTimePair foreignIDTimePair, Map set, String[] unset) + throws StreamException { + checkNotNull(foreignIDTimePair, "No activity to update"); + return updateActivityByForeignID( + foreignIDTimePair.getForeignID(), foreignIDTimePair.getTime(), set, unset); + } + + public CompletableFuture updateActivityByForeignID( + String foreignID, Date timestamp, Map set, Iterable unset) + throws StreamException { + return updateActivityByForeignID( + foreignID, timestamp, set, Iterables.toArray(unset, String.class)); + } + + public CompletableFuture updateActivityByForeignID(ActivityUpdate update) + throws StreamException { + return updateActivityByForeignID( + update.getForeignID(), update.getTime(), update.getSet(), update.getUnset()); + } + + public CompletableFuture updateActivityByForeignID( + String foreignID, Date timestamp, Map set, String[] unset) + throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.WRITE); + return stream.updateActivityByForeignID(token, foreignID, timestamp, set, unset); + } + + public CompletableFuture openGraph(URL url) throws StreamException { + final Token token = buildOpenGraphToken(secret); + return stream.openGraph(token, url); + } + + public CompletableFuture> updateActivitiesByID(Iterable updates) + throws StreamException { + return updateActivitiesByID(Iterables.toArray(updates, ActivityUpdate.class)); + } + + public CompletableFuture> updateActivitiesByID(ActivityUpdate... updates) + throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.WRITE); + return stream.updateActivitiesByID(token, updates); + } + + public CompletableFuture> updateActivitiesByForeignID( + Iterable updates) throws StreamException { + return updateActivitiesByForeignID(Iterables.toArray(updates, ActivityUpdate.class)); + } + + public CompletableFuture> updateActivitiesByForeignID(ActivityUpdate... updates) + throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.WRITE); + return stream.updateActivitiesByForeignID(token, updates); + } + + public static final class Builder { + private static final String DEFAULT_HOST = "stream-io-api.com"; + + private final String apiKey; + private final String secret; + private HTTPClient httpClient; + + private String scheme = "https"; + private String region = Region.US_EAST.toString(); + private String host = DEFAULT_HOST; + private int port = 443; + + public Builder(String apiKey, String secret) { + checkNotNull(apiKey, "API key can't be null"); + checkNotNull(secret, "Secret can't be null"); + checkArgument(!apiKey.isEmpty(), "API key can't be empty"); + checkArgument(!secret.isEmpty(), "Secret can't be empty"); + this.apiKey = apiKey; + this.secret = secret; + } + + public Builder httpClient(HTTPClient httpClient) { + checkNotNull(httpClient, "HTTP client can't be null"); + this.httpClient = httpClient; + return this; + } + + public Builder scheme(String scheme) { + checkNotNull(scheme, "Scheme can't be null"); + checkArgument(!scheme.isEmpty(), "Scheme can't be empty"); + this.scheme = scheme; + return this; + } + + public Builder host(String host) { + checkNotNull(host, "Host can't be null"); + checkArgument(!host.isEmpty(), "Host can't be empty"); + this.host = host; + return this; + } + + public Builder port(int port) { + checkArgument(port > 0, "Port has to be a non-zero positive number"); + this.port = port; + return this; + } + + public Builder region(Region region) { + checkNotNull(region, "Region can't be null"); + this.region = region.toString(); + return this; + } + + public Builder region(String region) { + checkNotNull(region, "Region can't be null"); + checkArgument(!region.isEmpty(), "Region can't be empty"); + this.region = region; + return this; + } + + private String buildHost() { + final StringBuilder sb = new StringBuilder(); + if (host.equals(DEFAULT_HOST)) { + sb.append(region).append("."); + } + sb.append(host); + return sb.toString(); + } + + public Client build() throws MalformedURLException { + if (httpClient == null) { + httpClient = new OKHTTPClientAdapter(); + } + return new Client(apiKey, secret, new URL(scheme, buildHost(), port, ""), httpClient); + } + } + + public T getHTTPClientImplementation() { + return stream.getHTTPClientImplementation(); + } + + public Token frontendToken(String userID) { + return buildFrontendToken(secret, userID); + } + + public Token frontendToken(String userID, Date expiresAt) { + return buildFrontendToken(secret, userID, expiresAt); + } + + public FlatFeed flatFeed(FeedID id) { + return new FlatFeed(this, id); + } + + public FlatFeed flatFeed(String slug, String userID) { + return flatFeed(new FeedID(slug, userID)); + } + + public AggregatedFeed aggregatedFeed(FeedID id) { + return new AggregatedFeed(this, id); + } + + public AggregatedFeed aggregatedFeed(String slug, String userID) { + return aggregatedFeed(new FeedID(slug, userID)); + } + + public NotificationFeed notificationFeed(FeedID id) { + return new NotificationFeed(this, id); + } + + public NotificationFeed notificationFeed(String slug, String userID) { + return notificationFeed(new FeedID(slug, userID)); + } + + public User user(String userID) { + return new User(this, userID); + } + + public BatchClient batch() { + return new BatchClient(secret, stream.batch()); + } + + public CollectionsClient collections() { + return new CollectionsClient(secret, stream.collections()); + } + + public PersonalizationClient personalization() { + return new PersonalizationClient(secret, stream.personalization()); + } + + public AnalyticsClient analytics() { + return new AnalyticsClient(secret, stream.analytics()); + } + + public ReactionsClient reactions() { + return new ReactionsClient(secret, stream.reactions()); + } + + public ModerationClient moderation() { + return new ModerationClient(secret, stream.moderation()); + } + + public AuditLogsClient auditLogs() { + return new AuditLogsClient(secret, stream); + } + + public FileStorageClient files() { + return new FileStorageClient(secret, stream.files()); + } + + public ImageStorageClient images() { + return new ImageStorageClient(secret, stream.images()); + } + + CompletableFuture getActivities(FeedID feed, RequestOption... options) + throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.READ); + return stream.getActivities(token, feed, options); + } + + CompletableFuture getEnrichedActivities(FeedID feed, RequestOption... options) + throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.READ); + return stream.getEnrichedActivities(token, feed, options); + } + + CompletableFuture addActivity(FeedID feed, Activity activity) throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.WRITE); + return stream.addActivity(token, feed, activity); + } + + CompletableFuture addActivities(FeedID feed, Activity... activities) + throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.WRITE); + return stream.addActivities(token, feed, activities); + } + + CompletableFuture removeActivityByID(FeedID feed, String id) throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.DELETE); + return stream.removeActivityByID(token, feed, id); + } + + CompletableFuture removeActivityByForeignID(FeedID feed, String foreignID) + throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.DELETE); + return stream.removeActivityByForeignID(token, feed, foreignID); + } + + CompletableFuture follow(FeedID source, FeedID target, int activityCopyLimit) + throws StreamException { + final Token token = buildFollowToken(secret, source, TokenAction.WRITE); + final Token targetToken = buildFeedToken(secret, target, TokenAction.READ); + return stream.follow(token, targetToken, source, target, activityCopyLimit); + } + + CompletableFuture getFollowers(FeedID feed, RequestOption... options) + throws StreamException { + final Token token = buildFollowToken(secret, feed, TokenAction.READ); + return stream.getFollowers(token, feed, options); + } + + CompletableFuture getFollowed(FeedID feed, RequestOption... options) + throws StreamException { + final Token token = buildFollowToken(secret, feed, TokenAction.READ); + return stream.getFollowed(token, feed, options); + } + + CompletableFuture unfollow(FeedID source, FeedID target, RequestOption... options) + throws StreamException { + final Token token = buildFollowToken(secret, source, TokenAction.DELETE); + return stream.unfollow(token, source, target, options); + } + + CompletableFuture getFollowStats( + FeedID feed, String[] followerSlugs, String[] followingSlugs) throws StreamException { + final Token token = buildFollowToken(secret, TokenAction.READ); + return stream.getFollowStats(token, feed, followerSlugs, followingSlugs); + } + + CompletableFuture updateActivityToTargets( + FeedID feed, Activity activity, FeedID[] add, FeedID[] remove, FeedID[] newTargets) + throws StreamException { + final Token token = buildToTargetUpdateToken(secret, feed, TokenAction.WRITE); + return stream.updateActivityToTargets(token, feed, activity, add, remove, newTargets); + } + + CompletableFuture getUser(String id) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.READ); + return stream.getUser(token, id, false); + } + + CompletableFuture deleteUser(String id) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.DELETE); + return stream.deleteUser(token, id); + } + + CompletableFuture getOrCreateUser(String id, Data data) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.WRITE); + return stream.createUser(token, id, data, true); + } + + CompletableFuture createUser(String id, Data data) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.WRITE); + return stream.createUser(token, id, data, false); + } + + CompletableFuture updateUser(String id, Data data) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.WRITE); + return stream.updateUser(token, id, data); + } + + CompletableFuture userProfile(String id) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.READ); + return stream.getUser(token, id, true); + } + + public CompletableFuture deleteActivities(BatchDeleteActivitiesRequest request) throws StreamException { + final Token token = buildDataPrivacyToken(secret, Auth.TokenAction.WRITE); + return stream.deleteActivities(token, request); + } + + public CompletableFuture deleteReactions(BatchDeleteReactionsRequest request) throws StreamException { + final Token token = buildDataPrivacyToken(secret, Auth.TokenAction.WRITE); + return stream.deleteReactions(token, request); + } + + public CompletableFuture exportUserActivities(String userId) throws StreamException { + final Token token = buildDataPrivacyToken(secret, Auth.TokenAction.READ); + return stream.exportUserActivities(token, userId); + } +} diff --git a/src/main/java/io/getstream/client/CollectionsClient.java b/src/main/java/io/getstream/client/CollectionsClient.java new file mode 100644 index 00000000..39f2bee4 --- /dev/null +++ b/src/main/java/io/getstream/client/CollectionsClient.java @@ -0,0 +1,151 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Auth.buildCollectionsToken; +import static io.getstream.core.utils.Serialization.convert; + +import com.google.common.collect.Iterables; +import io.getstream.core.StreamCollections; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.CollectionData; +import io.getstream.core.utils.Auth.TokenAction; +import io.getstream.core.utils.Streams; +import java.util.List; +import java8.util.J8Arrays; +import java8.util.concurrent.CompletableFuture; +import java8.util.stream.Collectors; +import java8.util.stream.StreamSupport; + +public final class CollectionsClient { + private final String secret; + private final StreamCollections collections; + + CollectionsClient(String secret, StreamCollections collections) { + this.secret = secret; + this.collections = collections; + } + + public CompletableFuture addCustom(String collection, T item) throws StreamException { + return addCustom(null, collection, item); + } + + public CompletableFuture addCustom(String userID, String collection, T item) + throws StreamException { + return add(userID, collection, convert(item, CollectionData.class)) + .thenApply(data -> convert(data, (Class) item.getClass())); + } + + public CompletableFuture add(String collection, CollectionData item) + throws StreamException { + return add(null, collection, item); + } + + public CompletableFuture add( + String userID, String collection, CollectionData item) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.WRITE); + return collections.add(token, userID, collection, item); + } + + public CompletableFuture updateCustom(String collection, T item) throws StreamException { + return updateCustom(null, collection, item); + } + + public CompletableFuture updateCustom(String userID, String collection, T item) + throws StreamException { + return update(userID, collection, convert(item, CollectionData.class)) + .thenApply(data -> convert(data, (Class) item.getClass())); + } + + public CompletableFuture update(String collection, CollectionData item) + throws StreamException { + return update(null, collection, item); + } + + public CompletableFuture update( + String userID, String collection, CollectionData item) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.WRITE); + return collections.update(token, userID, collection, item); + } + + public CompletableFuture upsertCustom(String collection, Iterable items) + throws StreamException { + final CollectionData[] custom = + Streams.stream(items) + .map(item -> CollectionData.buildFrom(item)) + .toArray(CollectionData[]::new); + return upsert(collection, custom); + } + + public CompletableFuture upsertCustom(String collection, T... items) + throws StreamException { + final CollectionData[] custom = + J8Arrays.stream(items) + .map(item -> CollectionData.buildFrom(item)) + .toArray(CollectionData[]::new); + return upsert(collection, custom); + } + + public CompletableFuture upsert(String collection, Iterable items) + throws StreamException { + return upsert(collection, Iterables.toArray(items, CollectionData.class)); + } + + public CompletableFuture upsert(String collection, CollectionData... items) + throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.WRITE); + return collections.upsert(token, collection, items); + } + + public CompletableFuture getCustom(Class type, String collection, String id) + throws StreamException { + return get(collection, id).thenApply(data -> convert(data, type)); + } + + public CompletableFuture get(String collection, String id) + throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.READ); + return collections.get(token, collection, id); + } + + public CompletableFuture> selectCustom( + Class type, String collection, Iterable ids) throws StreamException { + return selectCustom(type, collection, Iterables.toArray(ids, String.class)); + } + + public CompletableFuture> selectCustom( + Class type, String collection, String... ids) throws StreamException { + return select(collection, ids) + .thenApply( + data -> + StreamSupport.stream(data) + .map(item -> convert(item, type)) + .collect(Collectors.toList())); + } + + public CompletableFuture> select(String collection, Iterable ids) + throws StreamException { + return select(collection, Iterables.toArray(ids, String.class)); + } + + public CompletableFuture> select(String collection, String... ids) + throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.READ); + return collections.select(token, collection, ids); + } + + public CompletableFuture delete(String collection, String id) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.DELETE); + return collections.delete(token, collection, id); + } + + public CompletableFuture deleteMany(String collection, Iterable ids) + throws StreamException { + return deleteMany(collection, Iterables.toArray(ids, String.class)); + } + + public CompletableFuture deleteMany(String collection, String... ids) + throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.DELETE); + return collections.deleteMany(token, collection, ids); + } +} diff --git a/src/main/java/io/getstream/client/Feed.java b/src/main/java/io/getstream/client/Feed.java new file mode 100644 index 00000000..26cede4d --- /dev/null +++ b/src/main/java/io/getstream/client/Feed.java @@ -0,0 +1,388 @@ +package io.getstream.client; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.*; +import static io.getstream.core.utils.Serialization.deserializeContainer; + +import com.google.common.collect.Iterables; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.*; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.Limit; +import io.getstream.core.options.Offset; +import io.getstream.core.options.RequestOption; +import io.getstream.core.utils.DefaultOptions; +import io.getstream.core.utils.Streams; +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.util.List; +import java8.util.J8Arrays; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public class Feed { + private final Client client; + private final FeedID id; + + Feed(Client client, FeedID id) { + checkNotNull(client, "Can't create feed w/o a client"); + checkNotNull(id, "Can't create feed w/o an ID"); + checkNotNull(id.getSlug(), "Feed slug can't be null"); + + this.client = client; + this.id = id; + } + + protected final Client getClient() { + return client; + } + + public final FeedID getID() { + return id; + } + + public final String getSlug() { + return id.getSlug(); + } + + public final String getUserID() { + return id.getUserID(); + } + + public final CompletableFuture addActivity(Activity activity) throws StreamException { + return getClient() + .addActivity(id, activity) + .thenApply( + response -> { + try { + return deserialize(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture addCustomActivity(T activity) throws StreamException { + return getClient() + .addActivity(id, Activity.builder().fromCustomActivity(activity).build()) + .thenApply( + response -> { + try { + return deserialize(response, (Class) activity.getClass()); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addActivities(Iterable activities) + throws StreamException { + return addActivities(Iterables.toArray(activities, Activity.class)); + } + + public final CompletableFuture> addCustomActivities(Iterable activities) + throws StreamException { + final Activity[] custom = + Streams.stream(activities) + .map(activity -> Activity.builder().fromCustomActivity(activity).build()) + .toArray(Activity[]::new); + return getClient() + .addActivities(id, custom) + .thenApply( + response -> { + try { + Class element = + (Class) + ((ParameterizedType) getClass().getGenericSuperclass()) + .getActualTypeArguments()[0]; + return deserializeContainer(response, "activities", element); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addActivities(Activity... activities) + throws StreamException { + return getClient() + .addActivities(id, activities) + .thenApply( + response -> { + try { + return deserializeContainer(response, "activities", Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addCustomActivities(T... activities) + throws StreamException { + final Activity[] custom = + J8Arrays.stream(activities) + .map(activity -> Activity.builder().fromCustomActivity(activity).build()) + .toArray(Activity[]::new); + return getClient() + .addActivities(id, custom) + .thenApply( + response -> { + try { + Class element = (Class) activities.getClass().getComponentType(); + return deserializeContainer(response, "activities", element); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture removeActivityByID(String id) throws StreamException { + return client + .removeActivityByID(this.id, id) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture removeActivityByForeignID(String foreignID) + throws StreamException { + return client + .removeActivityByForeignID(id, foreignID) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture follow(FlatFeed feed) throws StreamException { + return follow(feed, DefaultOptions.DEFAULT_ACTIVITY_COPY_LIMIT); + } + + public final CompletableFuture follow(FlatFeed feed, int activityCopyLimit) + throws StreamException { + checkArgument( + activityCopyLimit <= DefaultOptions.MAX_ACTIVITY_COPY_LIMIT, + String.format( + "Activity copy limit should be less then %d", DefaultOptions.MAX_ACTIVITY_COPY_LIMIT)); + + return client + .follow(id, feed.getID(), activityCopyLimit) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> getFollowers(Iterable feedIDs) + throws StreamException { + return getFollowers( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers(FeedID... feedIDs) + throws StreamException { + return getFollowers(DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, feedIDs); + } + + public final CompletableFuture> getFollowers( + Limit limit, Iterable feedIDs) throws StreamException { + return getFollowers( + limit, DefaultOptions.DEFAULT_OFFSET, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers(Limit limit, FeedID... feedIDs) + throws StreamException { + return getFollowers(limit, DefaultOptions.DEFAULT_OFFSET, feedIDs); + } + + public final CompletableFuture> getFollowers( + Offset offset, Iterable feedIDs) throws StreamException { + return getFollowers( + DefaultOptions.DEFAULT_LIMIT, offset, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers( + Offset offset, FeedID... feedIDs) throws StreamException { + return getFollowers(DefaultOptions.DEFAULT_LIMIT, offset, feedIDs); + } + + public final CompletableFuture> getFollowers( + Limit limit, Offset offset, Iterable feedIDs) throws StreamException { + return getFollowers(limit, offset, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers( + Limit limit, Offset offset, FeedID... feeds) throws StreamException { + checkNotNull(feeds, "No feed ids to filter on"); + + final String[] feedIDs = J8Arrays.stream(feeds).map(id -> id.toString()).toArray(String[]::new); + final RequestOption[] options = + feedIDs.length == 0 + ? new RequestOption[] {limit, offset} + : new RequestOption[] { + limit, offset, new CustomQueryParameter("filter", String.join(",", feedIDs)) + }; + return client + .getFollowers(id, options) + .thenApply( + response -> { + try { + return deserializeContainer(response, FollowRelation.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> getFollowed(Iterable feedIDs) + throws StreamException { + return getFollowed( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(FeedID... feedIDs) + throws StreamException { + return getFollowed(DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, feedIDs); + } + + public final CompletableFuture> getFollowed( + Offset offset, Iterable feedIDs) throws StreamException { + return getFollowed( + DefaultOptions.DEFAULT_LIMIT, offset, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(Offset offset, FeedID... feedIDs) + throws StreamException { + return getFollowed(DefaultOptions.DEFAULT_LIMIT, offset, feedIDs); + } + + public final CompletableFuture> getFollowed( + Limit limit, Iterable feedIDs) throws StreamException { + return getFollowed( + limit, DefaultOptions.DEFAULT_OFFSET, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(Limit limit, FeedID... feedIDs) + throws StreamException { + return getFollowed(limit, DefaultOptions.DEFAULT_OFFSET, feedIDs); + } + + public final CompletableFuture> getFollowed( + Limit limit, Offset offset, Iterable feedIDs) throws StreamException { + return getFollowed(limit, offset, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed( + Limit limit, Offset offset, FeedID... feeds) throws StreamException { + checkNotNull(feeds, "No feed ids to filter on"); + + final String[] feedIDs = J8Arrays.stream(feeds).map(id -> id.toString()).toArray(String[]::new); + final RequestOption[] options = + feedIDs.length == 0 + ? new RequestOption[] {limit, offset} + : new RequestOption[] { + limit, offset, new CustomQueryParameter("filter", String.join(",", feedIDs)) + }; + return client + .getFollowed(id, options) + .thenApply( + response -> { + try { + return deserializeContainer(response, FollowRelation.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture unfollow(FlatFeed feed) throws StreamException { + return unfollow(feed, io.getstream.core.KeepHistory.NO); + } + + public final CompletableFuture unfollow( + FlatFeed feed, io.getstream.core.KeepHistory keepHistory) throws StreamException { + return client + .unfollow(id, feed.getID(), new io.getstream.core.options.KeepHistory(keepHistory)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture getFollowStats( + Iterable followerSlugs, Iterable followingSlugs) throws StreamException { + return client + .getFollowStats( + id, + Iterables.toArray(followerSlugs, String.class), + Iterables.toArray(followingSlugs, String.class)) + .thenApply( + response -> { + try { + return deserializeContainerSingleItem(response, FollowStats.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture updateActivityToTargets( + Activity activity, Iterable add, Iterable remove) throws StreamException { + return updateActivityToTargets( + activity, Iterables.toArray(add, FeedID.class), Iterables.toArray(remove, FeedID.class)); + } + + public final CompletableFuture updateActivityToTargets( + Activity activity, FeedID[] add, FeedID[] remove) throws StreamException { + return client + .updateActivityToTargets(id, activity, add, remove, new FeedID[0]) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture replaceActivityToTargets( + Activity activity, Iterable newTargets) throws StreamException { + return replaceActivityToTargets(activity, Iterables.toArray(newTargets, FeedID.class)); + } + + public final CompletableFuture replaceActivityToTargets( + Activity activity, FeedID... newTargets) throws StreamException { + return client + .updateActivityToTargets(id, activity, new FeedID[0], new FeedID[0], newTargets) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/client/FileStorageClient.java b/src/main/java/io/getstream/client/FileStorageClient.java new file mode 100644 index 00000000..d8acb38e --- /dev/null +++ b/src/main/java/io/getstream/client/FileStorageClient.java @@ -0,0 +1,36 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Auth.buildFilesToken; + +import io.getstream.core.StreamFiles; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.utils.Auth.TokenAction; +import java.io.File; +import java.net.URL; +import java8.util.concurrent.CompletableFuture; + +public final class FileStorageClient { + private final String secret; + private final StreamFiles files; + + FileStorageClient(String secret, StreamFiles files) { + this.secret = secret; + this.files = files; + } + + public CompletableFuture upload(String fileName, byte[] content) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.WRITE); + return files.upload(token, fileName, content); + } + + public CompletableFuture upload(File content) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.WRITE); + return files.upload(token, content); + } + + public CompletableFuture delete(URL url) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.DELETE); + return files.delete(token, url); + } +} diff --git a/src/main/java/io/getstream/client/FlatFeed.java b/src/main/java/io/getstream/client/FlatFeed.java new file mode 100644 index 00000000..b0df451f --- /dev/null +++ b/src/main/java/io/getstream/client/FlatFeed.java @@ -0,0 +1,732 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.options.*; +import io.getstream.core.utils.DefaultOptions; + +import java.io.IOException; +import java.util.List; + +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class FlatFeed extends Feed { + FlatFeed(Client client, FeedID id) { + super(client, id); + } + + public CompletableFuture> getActivities() throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + null); + } + + public CompletableFuture> getActivities(Limit limit) throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(String ranking) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + ranking); + } + + public CompletableFuture> getActivities(Filter filter) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, null); + } + + public CompletableFuture> getActivities(Offset offset) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(Limit limit, String ranking) + throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getActivities(Limit limit, Filter filter) + throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, null); + } + + public CompletableFuture> getActivities(Limit limit, Offset offset) + throws StreamException { + return getActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(Filter filter, String ranking) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, ranking); + } + + public CompletableFuture> getActivities(Offset offset, String ranking) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getActivities(Limit limit, Filter filter, String ranking) + throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, ranking); + } + + public CompletableFuture> getActivities(Limit limit, Offset offset, String ranking) + throws StreamException { + return getActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, ranking); + } + + CompletableFuture> getActivities( + Limit limit, Offset offset, Filter filter, String ranking) throws StreamException { + final RequestOption[] options = + ranking == null + ? new RequestOption[] {limit, offset, filter, DefaultOptions.DEFAULT_MARKER} + : new RequestOption[] { + limit, offset, filter, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) + }; + return getClient() + .getActivities(getID(), options) + .thenApply( + response -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + CompletableFuture> getActivities( + Limit limit, Offset offset, Filter filter, String ranking, RankingVars rankingVars) throws StreamException { + + final RequestOption[] options = + ranking == null + ? new RequestOption[] {limit, offset, filter, DefaultOptions.DEFAULT_MARKER} + : new RequestOption[] { + limit, offset, filter, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking), rankingVars + }; + return getClient() + .getActivities(getID(), options) + .thenApply( + response -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getActivities(RequestOption... options) + throws StreamException { + // If no options provided, use defaults + if (options == null || options.length == 0) { + options = new RequestOption[] { + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER + }; + } + + return getClient() + .getActivities(getID(), options) + .thenApply( + response -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getCustomActivities(Class type) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + null); + } + + public CompletableFuture> getCustomActivities(Class type, Limit limit) + throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities(Class type, String ranking) + throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + ranking); + } + + public CompletableFuture> getCustomActivities(Class type, Filter filter) + throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, null); + } + + public CompletableFuture> getCustomActivities(Class type, Offset offset) + throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, String ranking) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getCustomActivities(type, limit, DefaultOptions.DEFAULT_OFFSET, filter, null); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getCustomActivities(type, limit, offset, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities( + Class type, Offset offset, String ranking) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities( + Class type, Filter filter, String ranking) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, ranking); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset, String ranking) throws StreamException { + return getCustomActivities(type, limit, offset, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Filter filter, String ranking) throws StreamException { + return getCustomActivities(type, limit, DefaultOptions.DEFAULT_OFFSET, filter, ranking); + } + + CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset, Filter filter, String ranking) + throws StreamException { + final RequestOption[] options = + ranking == null + ? new RequestOption[] {limit, offset, filter, DefaultOptions.DEFAULT_MARKER} + : new RequestOption[] { + limit, offset, filter, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) + }; + return getClient() + .getActivities(getID(), options) + .thenApply( + response -> { + try { + return deserializeContainer(response, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedActivities() throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities(Limit limit) + throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities(EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + flags, + null); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities(String ranking) + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, String ranking) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + flags, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities(Filter filter) + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities(Limit limit, Filter filter) + throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, flags, null); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, flags, null); + } + + public CompletableFuture> getEnrichedActivities(Offset offset) + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities(Limit limit, Offset offset) + throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, String ranking) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, String ranking) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, Filter filter, EnrichmentFlags flags, String ranking) + throws StreamException { + final RequestOption[] options = + ranking == null + ? new RequestOption[] {limit, offset, filter, flags, DefaultOptions.DEFAULT_MARKER} + : new RequestOption[] { + limit, offset, filter, flags, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) + }; + return getClient() + .getEnrichedActivities(getID(), options) + .thenApply( + response -> { + try { + return deserializeContainer(response, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedActivities(RequestOption... options) + throws StreamException { + // If no options provided, use defaults + if (options == null || options.length == 0) { + options = new RequestOption[] { + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + DefaultOptions.DEFAULT_MARKER + }; + } + + return getClient() + .getEnrichedActivities(getID(), options) + .thenApply( + response -> { + try { + return deserializeContainer(response, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type) + throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Limit limit) + throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + flags, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, String ranking) + throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + flags, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter) + throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Offset offset) + throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, EnrichmentFlags flags, String ranking) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, EnrichmentFlags flags, String ranking) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, flags, ranking); + } + + CompletableFuture> getEnrichedCustomActivities( + Class type, + Limit limit, + Offset offset, + Filter filter, + EnrichmentFlags flags, + String ranking) + throws StreamException { + final RequestOption[] options = + ranking == null + ? new RequestOption[] {limit, offset, filter, flags, DefaultOptions.DEFAULT_MARKER} + : new RequestOption[] { + limit, offset, filter, flags, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) + }; + return getClient() + .getActivities(getID(), options) + .thenApply( + response -> { + try { + return deserializeContainer(response, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/client/ImageStorageClient.java b/src/main/java/io/getstream/client/ImageStorageClient.java new file mode 100644 index 00000000..08849b91 --- /dev/null +++ b/src/main/java/io/getstream/client/ImageStorageClient.java @@ -0,0 +1,48 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Auth.buildFilesToken; + +import io.getstream.core.StreamImages; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.options.Crop; +import io.getstream.core.options.Resize; +import io.getstream.core.utils.Auth.TokenAction; +import java.io.File; +import java.net.URL; +import java8.util.concurrent.CompletableFuture; + +public final class ImageStorageClient { + private final String secret; + private final StreamImages images; + + ImageStorageClient(String secret, StreamImages images) { + this.secret = secret; + this.images = images; + } + + public CompletableFuture upload(String fileName, byte[] content) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.WRITE); + return images.upload(token, fileName, content); + } + + public CompletableFuture upload(File content) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.WRITE); + return images.upload(token, content); + } + + public CompletableFuture delete(URL url) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.DELETE); + return images.delete(token, url); + } + + public CompletableFuture process(URL url, Crop crop) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.READ); + return images.process(token, url, crop); + } + + public CompletableFuture process(URL url, Resize resize) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.READ); + return images.process(token, url, resize); + } +} diff --git a/src/main/java/io/getstream/client/ModerationClient.java b/src/main/java/io/getstream/client/ModerationClient.java new file mode 100644 index 00000000..e6beb12f --- /dev/null +++ b/src/main/java/io/getstream/client/ModerationClient.java @@ -0,0 +1,52 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Auth.buildModerationToken; +import static io.getstream.core.utils.Auth.buildReactionsToken; +import static io.getstream.core.utils.Routes.*; +import static io.getstream.core.utils.Serialization.*; + +import io.getstream.core.Moderation; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Response; +import io.getstream.core.http.Token; +import io.getstream.core.utils.Auth; +import java.util.Map; +import java8.util.concurrent.CompletableFuture; + +public class ModerationClient { + private final String secret; + private final Moderation mod; + + ModerationClient(String secret, Moderation mod) { + this.secret = secret; + this.mod = mod; + } + + public CompletableFuture flagUser( + String flaggedUserId,String reportingUser, String reason, Map custom) throws StreamException { + return flag("stream:user", flaggedUserId, reportingUser, reason, custom); + } + + public CompletableFuture flagActivity( + String entityId, String reportingUser, String reason, Map custom) + throws StreamException { + return flag("stream:feeds:v2:activity", entityId, reportingUser, reason, custom); + } + + public CompletableFuture flagReaction( + String entityId, String reportingUser, String reason, Map custom) + throws StreamException { + return flag("stream:feeds:v2:reaction", entityId, reportingUser, reason, custom); + } + + private CompletableFuture flag( + String entityType, + String entityId, + String reportingUser, + String reason, + Map custom) + throws StreamException { + final Token token = buildModerationToken(secret, Auth.TokenAction.WRITE); + return mod.flag(token, entityType, entityId, reportingUser, reason, custom); + } +} diff --git a/src/main/java/io/getstream/client/NotificationFeed.java b/src/main/java/io/getstream/client/NotificationFeed.java new file mode 100644 index 00000000..5acbc3cb --- /dev/null +++ b/src/main/java/io/getstream/client/NotificationFeed.java @@ -0,0 +1,705 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Serialization.*; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.*; +import io.getstream.core.options.*; +import io.getstream.core.utils.DefaultOptions; +import java.io.IOException; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class NotificationFeed extends Feed { + NotificationFeed(Client client, FeedID id) { + super(client, id); + } + + public CompletableFuture> getActivities() + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities(Limit limit) + throws StreamException { + return getActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities(Offset offset) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities(Filter filter) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities( + ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker); + } + + public CompletableFuture> getActivities( + Limit limit, Offset offset) throws StreamException { + return getActivities( + limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities( + Limit limit, Filter filter) throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities( + Limit limit, ActivityMarker marker) throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture> getActivities( + Filter filter, ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture> getActivities( + Offset offset, ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture> getActivities( + Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture> getActivities( + Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture> getActivities( + Limit limit, Offset offset, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), limit, offset, filter, marker) + .thenApply( + response -> { + try { + return deserialize( + response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getCustomActivities(Class type) + throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit) throws StreamException { + return getCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Offset offset) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Filter filter) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Offset offset, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, limit, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, limit, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset, Filter filter, ActivityMarker marker) + throws StreamException { + return getClient() + .getActivities(getID(), limit, offset, filter, marker) + .thenApply( + response -> { + try { + return deserialize(response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedActivities() + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedActivities( + ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + flags); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getClient() + .getEnrichedActivities(getID(), limit, offset, filter, marker, flags) + .thenApply( + response -> { + try { + return deserialize( + response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture> getEnrichedCustomActivities( + Class type, + Limit limit, + Offset offset, + Filter filter, + ActivityMarker marker, + EnrichmentFlags flags) + throws StreamException { + return getClient() + .getEnrichedActivities(getID(), limit, offset, filter, marker, flags) + .thenApply( + response -> { + try { + return deserialize(response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/client/PersonalizationClient.java b/src/main/java/io/getstream/client/PersonalizationClient.java new file mode 100644 index 00000000..536a4b5b --- /dev/null +++ b/src/main/java/io/getstream/client/PersonalizationClient.java @@ -0,0 +1,83 @@ +package io.getstream.client; + +import static io.getstream.core.utils.Auth.buildPersonalizationToken; + +import com.google.common.collect.ImmutableMap; +import io.getstream.core.StreamPersonalization; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.utils.Auth.TokenAction; +import java.util.Map; +import java8.util.concurrent.CompletableFuture; + +public final class PersonalizationClient { + private final String secret; + private final StreamPersonalization personalization; + + PersonalizationClient(String secret, StreamPersonalization personalization) { + this.secret = secret; + this.personalization = personalization; + } + + public CompletableFuture> get(String resource) throws StreamException { + return get(null, resource); + } + + public CompletableFuture> get(String resource, Map params) + throws StreamException { + return get(null, resource, params); + } + + public CompletableFuture> get(String userID, String resource) + throws StreamException { + return get(userID, resource, ImmutableMap.of()); + } + + public CompletableFuture> get( + String userID, String resource, Map params) throws StreamException { + final Token token = buildPersonalizationToken(secret, userID, TokenAction.READ); + return personalization.get(token, userID, resource, params); + } + + public CompletableFuture post(String resource, Map payload) + throws StreamException { + return post(null, resource, payload); + } + + public CompletableFuture post( + String resource, Map params, Map payload) + throws StreamException { + return post(null, resource, params, payload); + } + + public CompletableFuture post(String userID, String resource, Map payload) + throws StreamException { + return post(userID, resource, ImmutableMap.of(), payload); + } + + public CompletableFuture post( + String userID, String resource, Map params, Map payload) + throws StreamException { + final Token token = buildPersonalizationToken(secret, userID, TokenAction.WRITE); + return personalization.post(token, userID, resource, params, payload); + } + + public CompletableFuture delete(String resource) throws StreamException { + return delete(null, resource); + } + + public CompletableFuture delete(String resource, Map params) + throws StreamException { + return delete(null, resource, params); + } + + public CompletableFuture delete(String userID, String resource) throws StreamException { + return delete(userID, resource, ImmutableMap.of()); + } + + public CompletableFuture delete(String userID, String resource, Map params) + throws StreamException { + final Token token = buildPersonalizationToken(secret, userID, TokenAction.DELETE); + return personalization.delete(token, userID, resource, params); + } +} diff --git a/src/main/java/io/getstream/client/QueryAuditLogsFilters.java b/src/main/java/io/getstream/client/QueryAuditLogsFilters.java new file mode 100644 index 00000000..991ba18f --- /dev/null +++ b/src/main/java/io/getstream/client/QueryAuditLogsFilters.java @@ -0,0 +1,202 @@ +package io.getstream.client; + +import io.getstream.core.exceptions.StreamException; + +/** + * Filters for querying audit logs. + * Either entityType+entityID pair OR userID is required by the API. + * + * Common entity types in Stream include: + * - "activity" - for feed activities + * - "reaction" - for activity reactions + * - "user" - for Stream users + * - "feed" - for feed configurations + */ +public class QueryAuditLogsFilters { + private String entityType; + private String entityID; + private String userID; + + /** + * Private constructor, use builder instead. + */ + private QueryAuditLogsFilters() { + } + + /** + * Creates a new builder for QueryAuditLogsFilters. + * + * @return a new QueryAuditLogsFilters.Builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a new filter for user ID queries. + * + * @param userID The ID of the user + * @return a new QueryAuditLogsFilters with the user ID set + */ + public static QueryAuditLogsFilters forUser(String userID) { + return builder().withUserID(userID).build(); + } + + /** + * Creates a new filter for entity type and ID queries. + * + * @param entityType The type of entity (e.g., "activity", "reaction", "user", "feed") + * @param entityID The ID of the entity + * @return a new QueryAuditLogsFilters with the entity type and ID set + */ + public static QueryAuditLogsFilters forEntity(String entityType, String entityID) { + return builder().withEntityType(entityType).withEntityID(entityID).build(); + } + + /** + * Convenience method to create a filter for activity entities. + * + * @param activityID The ID of the activity + * @return a new QueryAuditLogsFilters for the activity + */ + public static QueryAuditLogsFilters forActivity(String activityID) { + return forEntity("activity", activityID); + } + + /** + * Convenience method to create a filter for reaction entities. + * + * @param reactionID The ID of the reaction + * @return a new QueryAuditLogsFilters for the reaction + */ + public static QueryAuditLogsFilters forReaction(String reactionID) { + return forEntity("reaction", reactionID); + } + + public String getEntityType() { + return entityType; + } + + public String getEntityID() { + return entityID; + } + + public String getUserID() { + return userID; + } + + /** + * Set the entity type for existing filter instance. + * + * @param entityType The type of entity (e.g., "activity", "reaction") + * @return this instance for method chaining + */ + public QueryAuditLogsFilters setEntityType(String entityType) { + this.entityType = entityType; + return this; + } + + /** + * Set the entity ID for existing filter instance. + * + * @param entityID The ID of the entity + * @return this instance for method chaining + */ + public QueryAuditLogsFilters setEntityID(String entityID) { + this.entityID = entityID; + return this; + } + + /** + * Set the user ID for existing filter instance. + * + * @param userID The ID of the user + * @return this instance for method chaining + */ + public QueryAuditLogsFilters setUserID(String userID) { + this.userID = userID; + return this; + } + + /** + * Validates that the filters contain the required fields. + * Either (entityType AND entityID) OR userID must be set. + * + * @throws StreamException if the required fields are not set + */ + public void validate() throws StreamException { + boolean hasEntityFields = entityType != null && !entityType.isEmpty() && + entityID != null && !entityID.isEmpty(); + boolean hasUserID = userID != null && !userID.isEmpty(); + + if (!hasEntityFields && !hasUserID) { + throw new StreamException("Either entityType+entityID or userID is required for audit logs queries"); + } + } + + /** + * Checks if the filter is valid according to API requirements. + * + * @return true if either (entityType AND entityID) OR userID is set + */ + public boolean isValid() { + boolean hasEntityFields = entityType != null && !entityType.isEmpty() && + entityID != null && !entityID.isEmpty(); + boolean hasUserID = userID != null && !userID.isEmpty(); + + return hasEntityFields || hasUserID; + } + + /** + * Builder class for QueryAuditLogsFilters. + */ + public static class Builder { + private final QueryAuditLogsFilters filters; + + private Builder() { + filters = new QueryAuditLogsFilters(); + } + + /** + * Set the entity type. + * + * @param entityType The type of entity (e.g., "activity", "reaction") + * @return this builder for method chaining + */ + public Builder withEntityType(String entityType) { + filters.entityType = entityType; + return this; + } + + /** + * Set the entity ID. + * + * @param entityID The ID of the entity + * @return this builder for method chaining + */ + public Builder withEntityID(String entityID) { + filters.entityID = entityID; + return this; + } + + /** + * Set the user ID. + * + * @param userID The ID of the user + * @return this builder for method chaining + */ + public Builder withUserID(String userID) { + filters.userID = userID; + return this; + } + + /** + * Builds the QueryAuditLogsFilters instance. + * + * @return a new QueryAuditLogsFilters instance + */ + public QueryAuditLogsFilters build() { + return filters; + } + } +} \ No newline at end of file diff --git a/src/main/java/io/getstream/client/QueryAuditLogsPager.java b/src/main/java/io/getstream/client/QueryAuditLogsPager.java new file mode 100644 index 00000000..8b78da59 --- /dev/null +++ b/src/main/java/io/getstream/client/QueryAuditLogsPager.java @@ -0,0 +1,44 @@ +package io.getstream.client; + +public class QueryAuditLogsPager { + private String next; + private String prev; + private int limit; + + public QueryAuditLogsPager() { + } + + public QueryAuditLogsPager(int limit) { + this.limit = limit; + } + + public QueryAuditLogsPager(String next, String prev, int limit) { + this.next = next; + this.prev = prev; + this.limit = limit; + } + + public String getNext() { + return next; + } + + public void setNext(String next) { + this.next = next; + } + + public String getPrev() { + return prev; + } + + public void setPrev(String prev) { + this.prev = prev; + } + + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } +} \ No newline at end of file diff --git a/src/main/java/io/getstream/client/QueryAuditLogsResponse.java b/src/main/java/io/getstream/client/QueryAuditLogsResponse.java new file mode 100644 index 00000000..1739ba3a --- /dev/null +++ b/src/main/java/io/getstream/client/QueryAuditLogsResponse.java @@ -0,0 +1,86 @@ +package io.getstream.client; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; +import io.getstream.core.models.AuditLog; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryAuditLogsResponse { + @JsonProperty("audit_logs") + private List auditLogs; + + private String next; + private String prev; + private String duration; + + // Default constructor + public QueryAuditLogsResponse() { + this.auditLogs = new ArrayList<>(); + } + + // Constructor with parameters + @JsonCreator + public QueryAuditLogsResponse( + @JsonProperty("audit_logs") List auditLogs, + @JsonProperty("next") String next, + @JsonProperty("prev") String prev, + @JsonProperty("duration") String duration) { + // Initialize mandatory fields with safe defaults if they're null + this.auditLogs = auditLogs != null ? auditLogs : new ArrayList<>(); + this.next = next; + this.prev = prev; + this.duration = duration != null ? duration : ""; + } + + public List getAuditLogs() { + return auditLogs; + } + + public String getNext() { + return next; + } + + public String getPrev() { + return prev; + } + + public String getDuration() { + return duration; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QueryAuditLogsResponse that = (QueryAuditLogsResponse) o; + return Objects.equals(auditLogs, that.auditLogs) && + Objects.equals(next, that.next) && + Objects.equals(prev, that.prev) && + Objects.equals(duration, that.duration); + } + + @Override + public int hashCode() { + return Objects.hash(auditLogs, next, prev, duration); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("auditLogs", this.auditLogs) + .add("next", this.next) + .add("prev", this.prev) + .add("duration", this.duration) + .toString(); + } +} \ No newline at end of file diff --git a/src/main/java/io/getstream/client/ReactionsClient.java b/src/main/java/io/getstream/client/ReactionsClient.java new file mode 100644 index 00000000..c2658163 --- /dev/null +++ b/src/main/java/io/getstream/client/ReactionsClient.java @@ -0,0 +1,259 @@ +package io.getstream.client; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Auth.buildReactionsToken; + +import com.google.common.collect.Iterables; +import io.getstream.core.LookupKind; +import io.getstream.core.StreamReactions; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Paginated; +import io.getstream.core.models.Reaction; +import io.getstream.core.models.ReactionBatch; +import io.getstream.core.options.Filter; +import io.getstream.core.options.Limit; +import io.getstream.core.utils.Auth.TokenAction; +import io.getstream.core.utils.DefaultOptions; +import java.util.List; +import java.util.Map; +import java8.util.concurrent.CompletableFuture; + +public final class ReactionsClient { + private final String secret; + private final StreamReactions reactions; + + ReactionsClient(String secret, StreamReactions reactions) { + this.secret = secret; + this.reactions = reactions; + } + + public CompletableFuture get(String id) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.get(token, id); + } + + public CompletableFuture getBatch(List ids) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.getBatchReactions(token, ids, false); + } + + public CompletableFuture getBatch(List ids, Boolean includeDeleted) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.getBatchReactions(token, ids, includeDeleted); + } + + public CompletableFuture> filter(LookupKind lookup, String id) + throws StreamException { + return filter(lookup, id, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_LIMIT, ""); + } + + public CompletableFuture> filter( + LookupKind lookup, String id, Filter filter, Limit limit, String kind, Boolean withOwnChildren, String filterUserID) + throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.filter(token, lookup, id, filter, limit, kind, withOwnChildren, filterUserID); + } + + public CompletableFuture> filter(LookupKind lookup, String id, Limit limit) + throws StreamException { + return filter(lookup, id, DefaultOptions.DEFAULT_FILTER, limit, ""); + } + + public CompletableFuture> filter(LookupKind lookup, String id, Filter filter) + throws StreamException { + return filter(lookup, id, filter, DefaultOptions.DEFAULT_LIMIT, ""); + } + + public CompletableFuture> filter(LookupKind lookup, String id, String kind) + throws StreamException { + return filter(lookup, id, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_LIMIT, kind); + } + + public CompletableFuture> filter( + LookupKind lookup, String id, Filter filter, Limit limit) throws StreamException { + return filter(lookup, id, filter, limit, ""); + } + + public CompletableFuture> filter( + LookupKind lookup, String id, Limit limit, String kind) throws StreamException { + return filter(lookup, id, DefaultOptions.DEFAULT_FILTER, limit, kind); + } + + public CompletableFuture> filter( + LookupKind lookup, String id, Filter filter, Limit limit, String kind) + throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.filter(token, lookup, id, filter, limit, kind); + } + + public CompletableFuture> filter( + LookupKind lookup, String id, Filter filter, Limit limit, String kind, Boolean withOwnChildren) + throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.filter(token, lookup, id, filter, limit, kind, withOwnChildren, ""); + } + + public CompletableFuture> paginatedFilter(LookupKind lookup, String id) + throws StreamException { + return paginatedFilter( + lookup, id, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_LIMIT, "", false); + } + + public CompletableFuture> paginatedFilter( + LookupKind lookup, String id, Limit limit) throws StreamException { + return paginatedFilter(lookup, id, DefaultOptions.DEFAULT_FILTER, limit, "", false); + } + + public CompletableFuture> paginatedFilter( + LookupKind lookup, String id, Filter filter) throws StreamException { + return paginatedFilter(lookup, id, filter, DefaultOptions.DEFAULT_LIMIT, "", false); + } + + public CompletableFuture> paginatedFilter( + LookupKind lookup, String id, String kind) throws StreamException { + return paginatedFilter( + lookup, id, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_LIMIT, kind, false); + } + + public CompletableFuture> paginatedFilter( + LookupKind lookup, String id, Filter filter, Limit limit) throws StreamException { + return paginatedFilter(lookup, id, filter, limit, "", false); + } + + public CompletableFuture> paginatedFilter( + LookupKind lookup, String id, Limit limit, String kind) throws StreamException { + return paginatedFilter(lookup, id, DefaultOptions.DEFAULT_FILTER, limit, kind, false); + } + + public CompletableFuture> paginatedFilter( + LookupKind lookup, String id, Filter filter, Limit limit, String kind, Boolean withOwnChildren) + throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.paginatedFilter(token, lookup, id, filter, limit, kind, withOwnChildren); + } + + public CompletableFuture> paginatedFilter(String next) + throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.paginatedFilter(token, next); + } + + public CompletableFuture add( + String userID, String kind, String activityID, Iterable targetFeeds) + throws StreamException { + return add(userID, kind, activityID, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture add( + String userID, String kind, String activityID, FeedID... targetFeeds) throws StreamException { + checkNotNull(kind, "Reaction kind can't be null"); + checkArgument(!kind.isEmpty(), "Reaction kind can't be empty"); + checkNotNull(activityID, "Reaction activity id can't be null"); + checkArgument(!activityID.isEmpty(), "Reaction activity id can't be empty"); + + return add(userID, Reaction.builder().activityID(activityID).kind(kind).build(), targetFeeds); + } + + public CompletableFuture add( + String userID, Reaction reaction, Iterable targetFeeds) throws StreamException { + return add(userID, reaction, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture add(String userID, Reaction reaction, FeedID... targetFeeds) + throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.WRITE); + return reactions.add(token, userID, reaction, targetFeeds); + } + + public CompletableFuture add(String userID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) + throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.WRITE); + return reactions.add(token, userID, reaction, targetFeeds, targetFeedsExtraData); + } + + public CompletableFuture addChild( + String userID, String kind, String parentID, Iterable targetFeeds) + throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild( + String userID, String kind, String parentID, FeedID... targetFeeds) throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild( + String userID, String kind, String parentID, FeedID[] targetFeeds, Map targetFeedsExtraData) throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds, targetFeedsExtraData); + } + + public CompletableFuture addChild( + String userID, String parentID, Reaction reaction, Iterable targetFeeds) + throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild( + String userID, String parentID, Reaction reaction, FeedID... targetFeeds) + throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild( + String userID, String parentID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) + throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds, targetFeedsExtraData); + } + + public CompletableFuture update(String id, Iterable targetFeeds) + throws StreamException { + return update(id, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture update(String id, FeedID... targetFeeds) throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + return update(Reaction.builder().id(id).build(), targetFeeds); + } + + public CompletableFuture update(Reaction reaction, Iterable targetFeeds) + throws StreamException { + return update(reaction, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture update(Reaction reaction, FeedID... targetFeeds) + throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.WRITE); + return reactions.update(token, reaction, targetFeeds); + } + + public CompletableFuture delete(String id) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.DELETE); + return reactions.delete(token, id, false); + } + + public CompletableFuture delete(String id, Boolean soft) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.DELETE); + return reactions.delete(token, id, soft); + } + + public CompletableFuture softDelete(String id) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.DELETE); + return reactions.delete(token, id, true); + } + + public CompletableFuture restore(String id) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.WRITE); + return reactions.restore(token, id); + } +} diff --git a/src/main/java/io/getstream/client/User.java b/src/main/java/io/getstream/client/User.java new file mode 100644 index 00000000..76b399dc --- /dev/null +++ b/src/main/java/io/getstream/client/User.java @@ -0,0 +1,117 @@ +package io.getstream.client; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.deserialize; +import static io.getstream.core.utils.Serialization.deserializeError; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Data; +import io.getstream.core.models.ProfileData; +import java.io.IOException; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class User { + private final Client client; + private final String id; + + public User(Client client, String id) { + checkNotNull(client, "Client can't be null"); + checkNotNull(id, "User ID can't be null"); + checkArgument(!id.isEmpty(), "User ID can't be empty"); + + this.client = client; + this.id = id; + } + + public String getID() { + return id; + } + + public CompletableFuture get() throws StreamException { + return client + .getUser(id) + .thenApply( + response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture delete() throws StreamException { + return client + .deleteUser(id) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture getOrCreate() throws StreamException { + return getOrCreate(new Data()); + } + + public CompletableFuture getOrCreate(Data data) throws StreamException { + return client + .getOrCreateUser(id, data) + .thenApply( + response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture create() throws StreamException { + return create(new Data()); + } + + public CompletableFuture create(Data data) throws StreamException { + return client + .createUser(id, data) + .thenApply( + response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture update(Data data) throws StreamException { + return client + .updateUser(id, data) + .thenApply( + response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture profile() throws StreamException { + return client + .userProfile(id) + .thenApply( + response -> { + try { + return deserialize(response, ProfileData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudAggregatedFeed.java b/src/main/java/io/getstream/cloud/CloudAggregatedFeed.java new file mode 100644 index 00000000..7c4f8030 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudAggregatedFeed.java @@ -0,0 +1,711 @@ +package io.getstream.cloud; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Response; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Group; +import io.getstream.core.options.*; +import io.getstream.core.utils.DefaultOptions; +import java.io.IOException; +import java.util.List; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public class CloudAggregatedFeed extends CloudFeed { + CloudAggregatedFeed(CloudClient client, FeedID id) { + super(client, id); + } + + CloudAggregatedFeed(CloudClient client, FeedID id, FeedSubscriber subscriber) { + super(client, id, subscriber); + } + + public CompletableFuture>> getActivities() + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Limit limit) + throws StreamException { + return getActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Offset offset) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Filter filter) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities( + ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker); + } + + public CompletableFuture>> getActivities( + Limit limit, Offset offset) throws StreamException { + return getActivities( + limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities( + Limit limit, Filter filter) throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities( + Limit limit, ActivityMarker marker) throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getActivities( + Filter filter, ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture>> getActivities( + Offset offset, ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getActivities( + Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture>> getActivities( + Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture>> getActivities( + Limit limit, Offset offset, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), limit, offset, filter, marker) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, Group.class, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getCustomActivities( + Class type) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit) throws StreamException { + return getCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Offset offset) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Filter filter) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Offset offset, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, limit, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture>> getCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, limit, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture>> getCustomActivities( + Class type, Limit limit, Offset offset, Filter filter, ActivityMarker marker) + throws StreamException { + return getClient() + .getActivities(getID(), limit, offset, filter, marker) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, Group.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> + getEnrichedActivities() throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Offset offset) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Filter filter) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Filter filter) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Offset offset, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture>> getEnrichedActivities( + Limit limit, Offset offset, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getClient() + .getEnrichedActivities(getID(), limit, offset, filter, marker, flags) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, Group.class, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture>> getEnrichedCustomActivities( + Class type, + Limit limit, + Offset offset, + Filter filter, + ActivityMarker marker, + EnrichmentFlags flags) + throws StreamException { + return getClient() + .getEnrichedActivities(getID(), limit, offset, filter, marker, flags) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, Group.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudAnalyticsClient.java b/src/main/java/io/getstream/cloud/CloudAnalyticsClient.java new file mode 100644 index 00000000..30392bd6 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudAnalyticsClient.java @@ -0,0 +1,32 @@ +package io.getstream.cloud; + +import com.google.common.collect.Iterables; +import io.getstream.core.StreamAnalytics; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.Engagement; +import io.getstream.core.models.Impression; +import java8.util.concurrent.CompletableFuture; + +public final class CloudAnalyticsClient { + private final Token token; + private final StreamAnalytics analytics; + + CloudAnalyticsClient(Token token, StreamAnalytics analytics) { + this.token = token; + this.analytics = analytics; + } + + public CompletableFuture trackEngagement(Iterable events) + throws StreamException { + return trackEngagement(Iterables.toArray(events, Engagement.class)); + } + + public CompletableFuture trackEngagement(Engagement... events) throws StreamException { + return analytics.trackEngagement(token, events); + } + + public CompletableFuture trackImpression(Impression event) throws StreamException { + return analytics.trackImpression(token, event); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudClient.java b/src/main/java/io/getstream/cloud/CloudClient.java new file mode 100644 index 00000000..e2417ec4 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudClient.java @@ -0,0 +1,401 @@ +package io.getstream.cloud; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import io.getstream.core.Region; +import io.getstream.core.Stream; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.faye.DefaultMessageTransformer; +import io.getstream.core.faye.Message; +import io.getstream.core.faye.client.FayeClient; +import io.getstream.core.faye.subscription.ChannelSubscription; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.OKHTTPClientAdapter; +import io.getstream.core.http.Response; +import io.getstream.core.http.Token; +import io.getstream.core.models.Activity; +import io.getstream.core.models.Data; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.OGData; +import io.getstream.core.models.RealtimeMessage; +import io.getstream.core.options.RequestOption; +import io.getstream.core.utils.Serialization; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java8.util.concurrent.CompletableFuture; + +public final class CloudClient { + private final String apiKey; + private final Token token; + private final String appID; + private final String userID; + private final Stream stream; + private final FayeClient faye; + + private CloudClient( + String key, + Token token, + String userID, + String appID, + URL baseURL, + HTTPClient httpClient, + URL fayeURL) { + this.apiKey = key; + this.token = token; + this.appID = appID; + this.userID = userID; + this.stream = new Stream(key, baseURL, httpClient); + this.faye = new FayeClient(fayeURL); + this.faye.setMessageTransformer(new FayeMessageTransformer()); + } + + public static Builder builder(String apiKey, String token, String userID) { + return new Builder(apiKey, new Token(token), userID); + } + + public static Builder builder(String apiKey, Token token, String userID) { + return new Builder(apiKey, token, userID); + } + + public static Builder builder(String apiKey, Token token, String userID, String appID) { + return new Builder(apiKey, token, userID, appID); + } + + public static final class Builder { + private static final String DEFAULT_HOST = "stream-io-api.com"; + private static final String DEFAULT_FAYE_URL = "https://faye-us-east.stream-io-api.com/faye"; + + private final String apiKey; + private final Token token; + private final String userID; + private final String appID; + private HTTPClient httpClient; + + private String scheme = "https"; + private String region = Region.US_EAST.toString(); + private String host = DEFAULT_HOST; + private int port = 443; + private String fayeURL = DEFAULT_FAYE_URL; + + public Builder(String apiKey, Token token, String userID) { + checkNotNull(apiKey, "API key can't be null"); + checkNotNull(token, "Token can't be null"); + checkNotNull(userID, "User ID can't be null"); + checkArgument(!apiKey.isEmpty(), "API key can't be empty"); + checkArgument(!userID.isEmpty(), "User ID can't be empty"); + this.apiKey = apiKey; + this.token = token; + this.userID = userID; + this.appID = null; + } + + public Builder(String apiKey, Token token, String userID, String appID) { + checkNotNull(apiKey, "API key can't be null"); + checkNotNull(token, "Token can't be null"); + checkNotNull(userID, "User ID can't be null"); + checkArgument(!apiKey.isEmpty(), "API key can't be empty"); + checkArgument(!userID.isEmpty(), "User ID can't be empty"); + this.apiKey = apiKey; + this.token = token; + this.userID = userID; + this.appID = appID; + } + + public Builder httpClient(HTTPClient httpClient) { + checkNotNull(httpClient, "HTTP client can't be null"); + this.httpClient = httpClient; + return this; + } + + public Builder scheme(String scheme) { + checkNotNull(scheme, "Scheme can't be null"); + checkArgument(!scheme.isEmpty(), "Scheme can't be empty"); + this.scheme = scheme; + return this; + } + + public Builder host(String host) { + checkNotNull(host, "Host can't be null"); + checkArgument(!host.isEmpty(), "Host can't be empty"); + this.host = host; + return this; + } + + public Builder port(int port) { + checkArgument(port > 0, "Port has to be a non-zero positive number"); + this.port = port; + return this; + } + + public Builder region(Region region) { + checkNotNull(region, "Region can't be null"); + this.region = region.toString(); + return this; + } + + public Builder region(String region) { + checkNotNull(region, "Region can't be null"); + checkArgument(!region.isEmpty(), "Region can't be empty"); + this.region = region; + return this; + } + + public Builder fayeURL(String fayeURL) { + checkNotNull(fayeURL, "FayeUrl can't be null"); + checkArgument(!fayeURL.isEmpty(), "FayeUrl can't be empty"); + this.fayeURL = fayeURL; + return this; + } + + private String buildHost() { + final StringBuilder sb = new StringBuilder(); + if (host.equals(DEFAULT_HOST)) { + sb.append(region).append("."); + } + sb.append(host); + return sb.toString(); + } + + public CloudClient build() throws MalformedURLException { + if (httpClient == null) { + httpClient = new OKHTTPClientAdapter(); + } + + return new CloudClient( + apiKey, + token, + userID, + appID, + new URL(scheme, buildHost(), port, ""), + httpClient, + new URL(DEFAULT_FAYE_URL)); + } + } + + private static class FeedSubscription { + private String token; + private String userId; + private ChannelSubscription channelSubscription; + + private FeedSubscription(String token, String userId) { + this.token = token; + this.userId = userId; + } + + private FeedSubscription(String token, String userId, ChannelSubscription subscription) { + this.token = token; + this.userId = userId; + this.channelSubscription = subscription; + } + } + + private final Map feedSubscriptions = new HashMap<>(); + + private class FayeMessageTransformer extends DefaultMessageTransformer { + @Override + public Message transformRequest(Message message) { + final String subscription = message.getSubscription(); + if (feedSubscriptions.containsKey(subscription)) { + final FeedSubscription feedSubscription = feedSubscriptions.get(subscription); + final Map ext = new HashMap<>(); + ext.put("user_id", feedSubscription.userId); + ext.put("api_key", apiKey); + ext.put("signature", feedSubscription.token); + message.setExt(ext); + } + return message; + } + } + + public T getHTTPClientImplementation() { + return stream.getHTTPClientImplementation(); + } + + public CompletableFuture openGraph(URL url) throws StreamException { + return stream.openGraph(token, url); + } + + private CompletableFuture feedSubscriber( + FeedID feedId, RealtimeMessageCallback messageCallback) { + final CompletableFuture subscriberCompletion = new CompletableFuture<>(); + try { + checkNotNull(appID, "Missing app id, which is needed in order to subscribe feed"); + final String claim = feedId.getClaim(); + final String notificationChannel = "site" + "-" + appID + "-" + "feed" + "-" + claim; + final FeedSubscription subscription = + new FeedSubscription(token.toString(), notificationChannel); + feedSubscriptions.put("/" + notificationChannel, subscription); + + final ChannelSubscription channelSubscription = + faye.subscribe( + "/" + notificationChannel, + data -> { + try { + final byte[] payload = Serialization.toJSON(data); + final RealtimeMessage message = + Serialization.fromJSON(new String(payload), RealtimeMessage.class); + messageCallback.onMessage(message); + } catch (Exception e) { + e.printStackTrace(); + } + }, + () -> feedSubscriptions.remove("/" + notificationChannel)) + .get(); + + subscription.channelSubscription = channelSubscription; + feedSubscriptions.put("/" + notificationChannel, subscription); + subscriberCompletion.complete(channelSubscription); + } catch (Exception e) { + subscriberCompletion.completeExceptionally(e); + } + return subscriberCompletion; + } + + // TODO: add personalized feed versions + public CloudFlatFeed flatFeed(String slug) { + return flatFeed(slug, userID); + } + + public CloudFlatFeed flatFeed(String slug, CloudUser user) { + return flatFeed(slug, user.getID()); + } + + public CloudFlatFeed flatFeed(String slug, String userID) { + return flatFeed(new FeedID(slug, userID)); + } + + public CloudFlatFeed flatFeed(FeedID id) { + return new CloudFlatFeed(this, id, this::feedSubscriber); + } + + public CloudAggregatedFeed aggregatedFeed(String slug) { + return aggregatedFeed(slug, userID); + } + + public CloudAggregatedFeed aggregatedFeed(String slug, CloudUser user) { + return aggregatedFeed(slug, user.getID()); + } + + public CloudAggregatedFeed aggregatedFeed(String slug, String userID) { + return aggregatedFeed(new FeedID(slug, userID)); + } + + public CloudAggregatedFeed aggregatedFeed(FeedID id) { + return new CloudAggregatedFeed(this, id, this::feedSubscriber); + } + + public CloudNotificationFeed notificationFeed(String slug) { + return notificationFeed(slug, userID); + } + + public CloudNotificationFeed notificationFeed(String slug, CloudUser user) { + return notificationFeed(slug, user.getID()); + } + + public CloudNotificationFeed notificationFeed(String slug, String userID) { + return notificationFeed(new FeedID(slug, userID)); + } + + public CloudNotificationFeed notificationFeed(FeedID id) { + return new CloudNotificationFeed(this, id, this::feedSubscriber); + } + + public CloudUser user(String userID) { + return new CloudUser(this, userID); + } + + public CloudAnalyticsClient analytics() { + return new CloudAnalyticsClient(token, stream.analytics()); + } + + public CloudCollectionsClient collections() { + return new CloudCollectionsClient(token, userID, stream.collections()); + } + + public CloudReactionsClient reactions() { + return new CloudReactionsClient(token, userID, stream.reactions()); + } + + public CloudFileStorageClient files() { + return new CloudFileStorageClient(token, stream.files()); + } + + public CloudImageStorageClient images() { + return new CloudImageStorageClient(token, stream.images()); + } + + CompletableFuture getActivities(FeedID feed, RequestOption... options) + throws StreamException { + return stream.getActivities(token, feed, options); + } + + CompletableFuture getEnrichedActivities(FeedID feed, RequestOption... options) + throws StreamException { + return stream.getEnrichedActivities(token, feed, options); + } + + CompletableFuture addActivity(FeedID feed, Activity activity) throws StreamException { + return stream.addActivity(token, feed, activity); + } + + CompletableFuture addActivities(FeedID feed, Activity... activities) + throws StreamException { + return stream.addActivities(token, feed, activities); + } + + CompletableFuture removeActivityByID(FeedID feed, String id) throws StreamException { + return stream.removeActivityByID(token, feed, id); + } + + CompletableFuture removeActivityByForeignID(FeedID feed, String foreignID) + throws StreamException { + return stream.removeActivityByForeignID(token, feed, foreignID); + } + + CompletableFuture follow(FeedID source, FeedID target, int activityCopyLimit) + throws StreamException { + return stream.follow(token, token, source, target, activityCopyLimit); + } + + CompletableFuture getFollowers(FeedID feed, RequestOption... options) + throws StreamException { + return stream.getFollowers(token, feed, options); + } + + CompletableFuture getFollowed(FeedID feed, RequestOption... options) + throws StreamException { + return stream.getFollowed(token, feed, options); + } + + CompletableFuture unfollow(FeedID source, FeedID target, RequestOption... options) + throws StreamException { + return stream.unfollow(token, source, target, options); + } + + CompletableFuture getUser(String id) throws StreamException { + return stream.getUser(token, id, false); + } + + CompletableFuture deleteUser(String id) throws StreamException { + return stream.deleteUser(token, id); + } + + CompletableFuture getOrCreateUser(String id, Data data) throws StreamException { + return stream.createUser(token, id, data, true); + } + + CompletableFuture createUser(String id, Data data) throws StreamException { + return stream.createUser(token, id, data, false); + } + + CompletableFuture updateUser(String id, Data data) throws StreamException { + return stream.updateUser(token, id, data); + } + + CompletableFuture userProfile(String id) throws StreamException { + return stream.getUser(token, id, true); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudCollectionsClient.java b/src/main/java/io/getstream/cloud/CloudCollectionsClient.java new file mode 100644 index 00000000..969220d0 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudCollectionsClient.java @@ -0,0 +1,75 @@ +package io.getstream.cloud; + +import static io.getstream.core.utils.Serialization.convert; + +import io.getstream.core.StreamCollections; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.CollectionData; +import java8.util.concurrent.CompletableFuture; + +public final class CloudCollectionsClient { + private final Token token; + private final String userID; + private final StreamCollections collections; + + CloudCollectionsClient(Token token, String userID, StreamCollections collections) { + this.token = token; + this.userID = userID; + this.collections = collections; + } + + public CompletableFuture addCustom(String collection, T item) throws StreamException { + return addCustom(userID, collection, item); + } + + public CompletableFuture addCustom(String userID, String collection, T item) + throws StreamException { + return add(userID, collection, convert(item, CollectionData.class)) + .thenApply(data -> convert(data, (Class) item.getClass())); + } + + public CompletableFuture add(String collection, CollectionData item) + throws StreamException { + return add(userID, collection, item); + } + + public CompletableFuture add( + String userID, String collection, CollectionData item) throws StreamException { + return collections.add(token, userID, collection, item); + } + + public CompletableFuture updateCustom(String collection, T item) throws StreamException { + return updateCustom(userID, collection, item); + } + + public CompletableFuture updateCustom(String userID, String collection, T item) + throws StreamException { + return update(userID, collection, convert(item, CollectionData.class)) + .thenApply(data -> convert(data, (Class) item.getClass())); + } + + public CompletableFuture update(String collection, CollectionData item) + throws StreamException { + return update(userID, collection, item); + } + + public CompletableFuture update( + String userID, String collection, CollectionData item) throws StreamException { + return collections.update(token, userID, collection, item); + } + + public CompletableFuture getCustom(Class type, String collection, String id) + throws StreamException { + return get(collection, id).thenApply(data -> convert(data, type)); + } + + public CompletableFuture get(String collection, String id) + throws StreamException { + return collections.get(token, collection, id); + } + + public CompletableFuture delete(String collection, String id) throws StreamException { + return collections.delete(token, collection, id); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudFeed.java b/src/main/java/io/getstream/cloud/CloudFeed.java new file mode 100644 index 00000000..07673c01 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudFeed.java @@ -0,0 +1,351 @@ +package io.getstream.cloud; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.*; + +import com.google.common.collect.Iterables; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.faye.subscription.ChannelSubscription; +import io.getstream.core.http.Response; +import io.getstream.core.models.Activity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.FollowRelation; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.Limit; +import io.getstream.core.options.Offset; +import io.getstream.core.options.RequestOption; +import io.getstream.core.utils.DefaultOptions; +import io.getstream.core.utils.Streams; +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.util.List; +import java8.util.J8Arrays; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public class CloudFeed { + private final CloudClient client; + private final FeedID id; + private final FeedSubscriber subscriber; + + CloudFeed(CloudClient client, FeedID id) { + checkNotNull(client, "Can't create feed w/o a client"); + checkNotNull(id, "Can't create feed w/o an ID"); + + this.client = client; + this.id = id; + this.subscriber = null; + } + + CloudFeed(CloudClient client, FeedID id, FeedSubscriber subscriber) { + checkNotNull(client, "Can't create feed w/o a client"); + checkNotNull(id, "Can't create feed w/o an ID"); + + this.client = client; + this.id = id; + this.subscriber = subscriber; + } + + protected final CloudClient getClient() { + return client; + } + + public final CompletableFuture subscribe( + RealtimeMessageCallback messageCallback) { + checkNotNull(subscriber, "A subscriber must be provided in order to start listening to a feed"); + return subscriber.subscribe(id, messageCallback); + } + + public final FeedID getID() { + return id; + } + + public final String getSlug() { + return id.getSlug(); + } + + public final String getUserID() { + return id.getUserID(); + } + + public final CompletableFuture addActivity(Activity activity) throws StreamException { + return getClient() + .addActivity(id, activity) + .thenApply( + response -> { + try { + return deserialize(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture addCustomActivity(T activity) throws StreamException { + return getClient() + .addActivity(id, Activity.builder().fromCustomActivity(activity).build()) + .thenApply( + response -> { + try { + return deserialize(response, (Class) activity.getClass()); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addActivities(Iterable activities) + throws StreamException { + return addActivities(Iterables.toArray(activities, Activity.class)); + } + + public final CompletableFuture> addCustomActivities(Iterable activities) + throws StreamException { + final Activity[] custom = + Streams.stream(activities) + .map(activity -> Activity.builder().fromCustomActivity(activity).build()) + .toArray(Activity[]::new); + return getClient() + .addActivities(id, custom) + .thenApply( + (Response response) -> { + try { + Class element = + (Class) + ((ParameterizedType) getClass().getGenericSuperclass()) + .getActualTypeArguments()[0]; + return deserializeContainer(response, "activities", element); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addActivities(Activity... activities) + throws StreamException { + return getClient() + .addActivities(id, activities) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, "activities", Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addCustomActivities(T... activities) + throws StreamException { + final Activity[] custom = + J8Arrays.stream(activities) + .map(activity -> Activity.builder().fromCustomActivity(activity).build()) + .toArray(Activity[]::new); + return getClient() + .addActivities(id, custom) + .thenApply( + (Response response) -> { + try { + Class element = (Class) activities.getClass().getComponentType(); + return deserializeContainer(response, "activities", element); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture removeActivityByID(String id) throws StreamException { + return client + .removeActivityByID(this.id, id) + .thenApply( + (Response response) -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture removeActivityByForeignID(String foreignID) + throws StreamException { + return client + .removeActivityByForeignID(id, foreignID) + .thenApply( + (Response response) -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture follow(CloudFlatFeed feed) throws StreamException { + return follow(feed, DefaultOptions.DEFAULT_ACTIVITY_COPY_LIMIT); + } + + public final CompletableFuture follow(CloudFlatFeed feed, int activityCopyLimit) + throws StreamException { + checkArgument( + activityCopyLimit <= DefaultOptions.MAX_ACTIVITY_COPY_LIMIT, + String.format( + "Activity copy limit should be less then %d", DefaultOptions.MAX_ACTIVITY_COPY_LIMIT)); + + return client + .follow(id, feed.getID(), activityCopyLimit) + .thenApply( + (Response response) -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> getFollowers(Iterable feedIDs) + throws StreamException { + return getFollowers( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers(FeedID... feedIDs) + throws StreamException { + return getFollowers(DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, feedIDs); + } + + public final CompletableFuture> getFollowers( + Limit limit, Iterable feedIDs) throws StreamException { + return getFollowers( + limit, DefaultOptions.DEFAULT_OFFSET, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers(Limit limit, FeedID... feedIDs) + throws StreamException { + return getFollowers(limit, DefaultOptions.DEFAULT_OFFSET, feedIDs); + } + + public final CompletableFuture> getFollowers( + Offset offset, Iterable feedIDs) throws StreamException { + return getFollowers( + DefaultOptions.DEFAULT_LIMIT, offset, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers( + Offset offset, FeedID... feedIDs) throws StreamException { + return getFollowers(DefaultOptions.DEFAULT_LIMIT, offset, feedIDs); + } + + public final CompletableFuture> getFollowers( + Limit limit, Offset offset, Iterable feedIDs) throws StreamException { + return getFollowers(limit, offset, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers( + Limit limit, Offset offset, FeedID... feeds) throws StreamException { + checkNotNull(feeds, "No feed ids to filter on"); + + final String[] feedIDs = J8Arrays.stream(feeds).map(id -> id.toString()).toArray(String[]::new); + final RequestOption[] options = + feedIDs.length == 0 + ? new RequestOption[] {limit, offset} + : new RequestOption[] { + limit, offset, new CustomQueryParameter("filter", String.join(",", feedIDs)) + }; + return client + .getFollowers(id, options) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, FollowRelation.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> getFollowed(Iterable feedIDs) + throws StreamException { + return getFollowed( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(FeedID... feedIDs) + throws StreamException { + return getFollowed(DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, feedIDs); + } + + public final CompletableFuture> getFollowed( + Limit limit, Iterable feedIDs) throws StreamException { + return getFollowed( + limit, DefaultOptions.DEFAULT_OFFSET, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(Limit limit, FeedID... feedIDs) + throws StreamException { + return getFollowed(limit, DefaultOptions.DEFAULT_OFFSET, feedIDs); + } + + public final CompletableFuture> getFollowed( + Offset offset, Iterable feedIDs) throws StreamException { + return getFollowed( + DefaultOptions.DEFAULT_LIMIT, offset, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(Offset offset, FeedID... feedIDs) + throws StreamException { + return getFollowed(DefaultOptions.DEFAULT_LIMIT, offset, feedIDs); + } + + public final CompletableFuture> getFollowed( + Limit limit, Offset offset, Iterable feedIDs) throws StreamException { + return getFollowed(limit, offset, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed( + Limit limit, Offset offset, FeedID... feeds) throws StreamException { + checkNotNull(feeds, "No feed ids to filter on"); + + final String[] feedIDs = J8Arrays.stream(feeds).map(id -> id.toString()).toArray(String[]::new); + final RequestOption[] options = + feedIDs.length == 0 + ? new RequestOption[] {limit, offset} + : new RequestOption[] { + limit, offset, new CustomQueryParameter("filter", String.join(",", feedIDs)) + }; + return client + .getFollowed(id, options) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, FollowRelation.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture unfollow(CloudFlatFeed feed) throws StreamException { + return unfollow(feed, io.getstream.core.KeepHistory.NO); + } + + public final CompletableFuture unfollow( + CloudFlatFeed feed, io.getstream.core.KeepHistory keepHistory) throws StreamException { + return client + .unfollow(id, feed.getID(), new io.getstream.core.options.KeepHistory(keepHistory)) + .thenApply( + (Response response) -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudFileStorageClient.java b/src/main/java/io/getstream/cloud/CloudFileStorageClient.java new file mode 100644 index 00000000..b3a82f16 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudFileStorageClient.java @@ -0,0 +1,30 @@ +package io.getstream.cloud; + +import io.getstream.core.StreamFiles; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import java.io.File; +import java.net.URL; +import java8.util.concurrent.CompletableFuture; + +public final class CloudFileStorageClient { + private final Token token; + private final StreamFiles files; + + CloudFileStorageClient(Token token, StreamFiles files) { + this.token = token; + this.files = files; + } + + public CompletableFuture upload(String fileName, byte[] content) throws StreamException { + return files.upload(token, fileName, content); + } + + public CompletableFuture upload(File content) throws StreamException { + return files.upload(token, content); + } + + public CompletableFuture delete(URL url) throws StreamException { + return files.delete(token, url); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudFlatFeed.java b/src/main/java/io/getstream/cloud/CloudFlatFeed.java new file mode 100644 index 00000000..9f7625f2 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudFlatFeed.java @@ -0,0 +1,690 @@ +package io.getstream.cloud; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Response; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.options.*; +import io.getstream.core.utils.DefaultOptions; +import java.io.IOException; +import java.util.List; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class CloudFlatFeed extends CloudFeed { + CloudFlatFeed(CloudClient client, FeedID id) { + super(client, id); + } + + CloudFlatFeed(CloudClient client, FeedID id, FeedSubscriber subscriber) { + super(client, id, subscriber); + } + + public CompletableFuture> getActivities() throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + null); + } + + public CompletableFuture> getActivities(Limit limit) throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(String ranking) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + ranking); + } + + public CompletableFuture> getActivities(Filter filter) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, null); + } + + public CompletableFuture> getActivities(Offset offset) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(Limit limit, String ranking) + throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getActivities(Limit limit, Filter filter) + throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, null); + } + + public CompletableFuture> getActivities(Limit limit, Offset offset) + throws StreamException { + return getActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(Filter filter, String ranking) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, ranking); + } + + public CompletableFuture> getActivities(Offset offset, String ranking) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getActivities(Limit limit, Filter filter, String ranking) + throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, ranking); + } + + public CompletableFuture> getActivities(Limit limit, Offset offset, String ranking) + throws StreamException { + return getActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, ranking); + } + + CompletableFuture> getActivities( + Limit limit, Offset offset, Filter filter, String ranking) throws StreamException { + final RequestOption[] options = + ranking == null + ? new RequestOption[] {limit, offset, filter, DefaultOptions.DEFAULT_MARKER} + : new RequestOption[] { + limit, offset, filter, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) + }; + return getClient() + .getActivities(getID(), options) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getCustomActivities(Class type) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + null); + } + + public CompletableFuture> getCustomActivities(Class type, Limit limit) + throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities(Class type, String ranking) + throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + ranking); + } + + public CompletableFuture> getCustomActivities(Class type, Filter filter) + throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, null); + } + + public CompletableFuture> getCustomActivities(Class type, Offset offset) + throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, String ranking) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getCustomActivities(type, limit, DefaultOptions.DEFAULT_OFFSET, filter, null); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getCustomActivities(type, limit, offset, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities( + Class type, Offset offset, String ranking) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities( + Class type, Filter filter, String ranking) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, ranking); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset, String ranking) throws StreamException { + return getCustomActivities(type, limit, offset, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Filter filter, String ranking) throws StreamException { + return getCustomActivities(type, limit, DefaultOptions.DEFAULT_OFFSET, filter, ranking); + } + + CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset, Filter filter, String ranking) + throws StreamException { + final RequestOption[] options = + ranking == null + ? new RequestOption[] {limit, offset, filter, DefaultOptions.DEFAULT_MARKER} + : new RequestOption[] { + limit, offset, filter, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) + }; + return getClient() + .getActivities(getID(), options) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedActivities() throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities(Limit limit) + throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities(EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + flags, + null); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities(String ranking) + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, String ranking) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + flags, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities(Filter filter) + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities(Limit limit, Filter filter) + throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, flags, null); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, flags, null); + } + + public CompletableFuture> getEnrichedActivities(Offset offset) + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities(Limit limit, Offset offset) + throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, String ranking) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, String ranking) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, Filter filter, EnrichmentFlags flags, String ranking) + throws StreamException { + final RequestOption[] options = + ranking == null + ? new RequestOption[] {limit, offset, filter, flags, DefaultOptions.DEFAULT_MARKER} + : new RequestOption[] { + limit, offset, filter, flags, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) + }; + return getClient() + .getEnrichedActivities(getID(), options) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedActivities(RequestOption... options) + throws StreamException { + // If no options provided, use defaults + if (options == null || options.length == 0) { + options = new RequestOption[] { + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + DefaultOptions.DEFAULT_MARKER + }; + } + + return getClient() + .getEnrichedActivities(getID(), options) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type) + throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Limit limit) + throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + flags, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, String ranking) + throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + flags, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter) + throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Offset offset) + throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, EnrichmentFlags flags, String ranking) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, + ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, EnrichmentFlags flags, String ranking) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, flags, ranking); + } + + CompletableFuture> getEnrichedCustomActivities( + Class type, + Limit limit, + Offset offset, + Filter filter, + EnrichmentFlags flags, + String ranking) + throws StreamException { + final RequestOption[] options = + ranking == null + ? new RequestOption[] {limit, offset, filter, flags, DefaultOptions.DEFAULT_MARKER} + : new RequestOption[] { + limit, offset, filter, flags, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) + }; + return getClient() + .getActivities(getID(), options) + .thenApply( + (Response response) -> { + try { + return deserializeContainer(response, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudImageStorageClient.java b/src/main/java/io/getstream/cloud/CloudImageStorageClient.java new file mode 100644 index 00000000..c9b15aa6 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudImageStorageClient.java @@ -0,0 +1,40 @@ +package io.getstream.cloud; + +import io.getstream.core.StreamImages; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.options.Crop; +import io.getstream.core.options.Resize; +import java.io.File; +import java.net.URL; +import java8.util.concurrent.CompletableFuture; + +public final class CloudImageStorageClient { + private final Token token; + private final StreamImages images; + + CloudImageStorageClient(Token token, StreamImages images) { + this.token = token; + this.images = images; + } + + public CompletableFuture upload(String fileName, byte[] content) throws StreamException { + return images.upload(token, fileName, content); + } + + public CompletableFuture upload(File content) throws StreamException { + return images.upload(token, content); + } + + public CompletableFuture delete(URL url) throws StreamException { + return images.delete(token, url); + } + + public CompletableFuture process(URL url, Crop crop) throws StreamException { + return images.process(token, url, crop); + } + + public CompletableFuture process(URL url, Resize resize) throws StreamException { + return images.process(token, url, resize); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudNotificationFeed.java b/src/main/java/io/getstream/cloud/CloudNotificationFeed.java new file mode 100644 index 00000000..f6e17a16 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudNotificationFeed.java @@ -0,0 +1,710 @@ +package io.getstream.cloud; + +import static io.getstream.core.utils.Serialization.*; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Response; +import io.getstream.core.models.*; +import io.getstream.core.options.*; +import io.getstream.core.utils.DefaultOptions; +import java.io.IOException; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class CloudNotificationFeed extends CloudFeed { + CloudNotificationFeed(CloudClient client, FeedID id) { + super(client, id); + } + + CloudNotificationFeed(CloudClient client, FeedID id, FeedSubscriber subscriber) { + super(client, id, subscriber); + } + + public CompletableFuture> getActivities() + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities(Limit limit) + throws StreamException { + return getActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities(Offset offset) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities(Filter filter) + throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities( + ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker); + } + + public CompletableFuture> getActivities( + Limit limit, Offset offset) throws StreamException { + return getActivities( + limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities( + Limit limit, Filter filter) throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getActivities( + Limit limit, ActivityMarker marker) throws StreamException { + return getActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture> getActivities( + Filter filter, ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture> getActivities( + Offset offset, ActivityMarker marker) throws StreamException { + return getActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture> getActivities( + Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture> getActivities( + Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture> getActivities( + Limit limit, Offset offset, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), limit, offset, filter, marker) + .thenApply( + (Response response) -> { + try { + return deserialize( + response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getCustomActivities(Class type) + throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit) throws StreamException { + return getCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Offset offset) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Filter filter) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Offset offset, ActivityMarker marker) throws StreamException { + return getCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, limit, DefaultOptions.DEFAULT_OFFSET, filter, marker); + } + + public CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, limit, offset, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture> getCustomActivities( + Class type, Limit limit, Offset offset, Filter filter, ActivityMarker marker) + throws StreamException { + return getClient() + .getActivities(getID(), limit, offset, filter, marker) + .thenApply( + (Response response) -> { + try { + return deserialize(response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedActivities() + throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedActivities( + ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + flags); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedActivities( + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedActivities( + Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture> getEnrichedActivities( + Offset offset, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities( + DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities(limit, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedActivities(limit, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture> getEnrichedActivities( + Limit limit, Offset offset, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getClient() + .getEnrichedActivities(getID(), limit, offset, filter, marker, flags) + .thenApply( + (Response response) -> { + try { + return deserialize( + response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + DefaultOptions.DEFAULT_MARKER, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + DefaultOptions.DEFAULT_FILTER, + marker, + flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + DefaultOptions.DEFAULT_LIMIT, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + DefaultOptions.DEFAULT_OFFSET, + filter, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities( + type, + limit, + offset, + DefaultOptions.DEFAULT_FILTER, + marker, + DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, DefaultOptions.DEFAULT_LIMIT, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Filter filter, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, DefaultOptions.DEFAULT_OFFSET, filter, marker, flags); + } + + public CompletableFuture> getEnrichedCustomActivities( + Class type, Limit limit, Offset offset, ActivityMarker marker, EnrichmentFlags flags) + throws StreamException { + return getEnrichedCustomActivities( + type, limit, offset, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture> getEnrichedCustomActivities( + Class type, + Limit limit, + Offset offset, + Filter filter, + ActivityMarker marker, + EnrichmentFlags flags) + throws StreamException { + return getClient() + .getEnrichedActivities(getID(), limit, offset, filter, marker, flags) + .thenApply( + (Response response) -> { + try { + return deserialize(response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudReactionsClient.java b/src/main/java/io/getstream/cloud/CloudReactionsClient.java new file mode 100644 index 00000000..ab04d5a4 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudReactionsClient.java @@ -0,0 +1,294 @@ +package io.getstream.cloud; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.Iterables; +import io.getstream.core.LookupKind; +import io.getstream.core.StreamReactions; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Reaction; +import io.getstream.core.options.Filter; +import io.getstream.core.options.Limit; +import io.getstream.core.utils.DefaultOptions; +import java.util.List; +import java.util.Map; +import java8.util.concurrent.CompletableFuture; + +public final class CloudReactionsClient { + private final Token token; + private final String userID; + private final StreamReactions reactions; + + CloudReactionsClient(Token token, String userID, StreamReactions reactions) { + this.token = token; + this.userID = userID; + this.reactions = reactions; + } + + public CompletableFuture get(String id) throws StreamException { + return reactions.get(token, id); + } + + public CompletableFuture> filter(LookupKind lookup, String id) + throws StreamException { + Params params = new Params(lookup, id); + return filter(params); + } + + public CompletableFuture> filter(LookupKind lookup, String id, Limit limit) + throws StreamException { + Params params = new Params(lookup, id).withLimit(limit); + return filter(params); + } + + public CompletableFuture> filter(LookupKind lookup, String id, Filter filter) + throws StreamException { + Params params = new Params(lookup, id).withFilter(filter); + return filter(params); + } + + public CompletableFuture> filter(LookupKind lookup, String id, String kind) + throws StreamException { + Params params = new Params(lookup, id).withKind(kind); + return filter(params); + } + + public CompletableFuture> filter( + LookupKind lookup, String id, Filter filter, Limit limit) throws StreamException { + Params params = new Params(lookup, id).withFilter(filter).withLimit(limit); + return filter(params); + } + + public CompletableFuture> filter( + LookupKind lookup, String id, Limit limit, String kind) throws StreamException { + Params params = new Params(lookup, id).withLimit(limit).withKind(kind); + return filter(params); + } + + public CompletableFuture> filter( + LookupKind lookup, String id, Filter filter, Limit limit, String kind) + throws StreamException { + Params params = + new Params(lookup, id).withLimit(limit).withKind(kind).withFilter(filter).withLimit(limit); + return filter(params); + } + + public CompletableFuture> filter( + LookupKind lookup, + String id, + Filter filter, + Limit limit, + String kind, + Boolean includeOwnChildren) + throws StreamException { + Params params = + new Params(lookup, id) + .withLimit(limit) + .withKind(kind) + .withFilter(filter) + .withLimit(limit) + .includeOwnChildren(includeOwnChildren); + return filter(params); + } + + private CompletableFuture> filter(Params params) throws StreamException { + return reactions.filter( + token, + params.getLookupKind(), + params.getId(), + params.getFilter(), + params.getLimit(), + params.getKind(), + params.getWithOwnChildren(), ""); + } + + public CompletableFuture add( + String kind, String activityID, Iterable targetFeeds) throws StreamException { + return add(userID, kind, activityID, targetFeeds); + } + + public CompletableFuture add(String kind, String activityID, FeedID... targetFeeds) + throws StreamException { + return add(userID, activityID, targetFeeds); + } + + public CompletableFuture add(Reaction reaction, Iterable targetFeeds) + throws StreamException { + return add(userID, reaction, targetFeeds); + } + + public CompletableFuture add(Reaction reaction, FeedID... targetFeeds) + throws StreamException { + return add(userID, reaction, targetFeeds); + } + + public CompletableFuture add( + String userID, String kind, String activityID, Iterable targetFeeds) + throws StreamException { + return add(userID, kind, activityID, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture add( + String userID, String kind, String activityID, FeedID... targetFeeds) throws StreamException { + checkNotNull(kind, "Reaction kind can't be null"); + checkArgument(!kind.isEmpty(), "Reaction kind can't be empty"); + checkNotNull(activityID, "Reaction activity id can't be null"); + checkArgument(!activityID.isEmpty(), "Reaction activity id can't be empty"); + + return add(userID, Reaction.builder().activityID(activityID).kind(kind).build(), targetFeeds); + } + + public CompletableFuture add( + String userID, Reaction reaction, Iterable targetFeeds) throws StreamException { + return add(userID, reaction, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture add(String userID, Reaction reaction, FeedID... targetFeeds) + throws StreamException { + return reactions.add(token, userID, reaction, targetFeeds); + } + + public CompletableFuture add(String userID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) + throws StreamException { + return reactions.add(token, userID, reaction, targetFeeds, targetFeedsExtraData); + } + + public CompletableFuture addChild( + String userID, String kind, String parentID, Iterable targetFeeds) + throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild( + String userID, String kind, String parentID, FeedID... targetFeeds) throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild( + String userID, String kind, String parentID, FeedID[] targetFeeds, Map targetFeedsExtraData) throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds, targetFeedsExtraData); + } + + public CompletableFuture addChild( + String userID, String parentID, Reaction reaction, Iterable targetFeeds) + throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild( + String userID, String parentID, Reaction reaction, FeedID... targetFeeds) + throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild( + String userID, String parentID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) + throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds, targetFeedsExtraData); + } + + public CompletableFuture update(String id, Iterable targetFeeds) + throws StreamException { + return update(id, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture update(String id, FeedID... targetFeeds) throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + return update(Reaction.builder().id(id).build(), targetFeeds); + } + + public CompletableFuture update(Reaction reaction, Iterable targetFeeds) + throws StreamException { + return update(reaction, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture update(Reaction reaction, FeedID... targetFeeds) + throws StreamException { + return reactions.update(token, reaction, targetFeeds); + } + + public CompletableFuture delete(String id) throws StreamException { + return reactions.delete(token, id, false); + } + + public CompletableFuture delete(String id, Boolean soft) throws StreamException { + return reactions.delete(token, id, soft); + } + + public CompletableFuture softDelete(String id) throws StreamException { + return reactions.delete(token, id, true); + } + + public CompletableFuture restore(String id) throws StreamException { + return reactions.restore(token, id); + } + + public class Params { + private LookupKind lookupKind; + private String id; + private Filter filter; + private Limit limit; + private String kind; + private Boolean withOwnChildren; + + public Params(LookupKind lookupKind, String id) { + this.id = id; + this.lookupKind = lookupKind; + } + + public Params withLimit(Limit limit) { + this.limit = limit; + return this; + } + + public Params withFilter(Filter filter) { + this.filter = filter; + return this; + } + + public Params withKind(String kind) { + this.kind = kind; + return this; + } + + public Params includeOwnChildren(Boolean includeOwnChildren) { + this.withOwnChildren = includeOwnChildren; + return this; + } + + public LookupKind getLookupKind() { + return lookupKind; + } + + public String getId() { + return id; + } + + public Filter getFilter() { + return (filter != null) ? filter : DefaultOptions.DEFAULT_FILTER; + } + + public Limit getLimit() { + return (limit != null) ? limit : DefaultOptions.DEFAULT_LIMIT; + } + + public String getKind() { + return (kind != null) ? kind : ""; + } + + public Boolean getWithOwnChildren() { + return (withOwnChildren != null) ? withOwnChildren : false; + } + } +} diff --git a/src/main/java/io/getstream/cloud/CloudUser.java b/src/main/java/io/getstream/cloud/CloudUser.java new file mode 100644 index 00000000..1f27a5b3 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudUser.java @@ -0,0 +1,117 @@ +package io.getstream.cloud; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.deserialize; +import static io.getstream.core.utils.Serialization.deserializeError; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Data; +import io.getstream.core.models.ProfileData; +import java.io.IOException; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class CloudUser { + private final CloudClient client; + private final String id; + + public CloudUser(CloudClient client, String id) { + checkNotNull(client, "Client can't be null"); + checkNotNull(id, "User ID can't be null"); + checkArgument(!id.isEmpty(), "User ID can't be empty"); + + this.client = client; + this.id = id; + } + + public String getID() { + return id; + } + + public CompletableFuture get() throws StreamException { + return client + .getUser(id) + .thenApply( + response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture delete() throws StreamException { + return client + .deleteUser(id) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture getOrCreate() throws StreamException { + return getOrCreate(new Data()); + } + + public CompletableFuture getOrCreate(Data data) throws StreamException { + return client + .getOrCreateUser(id, data) + .thenApply( + response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture create(Data data) throws StreamException { + return client + .createUser(id, data) + .thenApply( + response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture create() throws StreamException { + return create(new Data()); + } + + public CompletableFuture update(Data data) throws StreamException { + return client + .updateUser(id, data) + .thenApply( + response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture profile() throws StreamException { + return client + .userProfile(id) + .thenApply( + response -> { + try { + return deserialize(response, ProfileData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/FeedSubscriber.java b/src/main/java/io/getstream/cloud/FeedSubscriber.java new file mode 100644 index 00000000..38872777 --- /dev/null +++ b/src/main/java/io/getstream/cloud/FeedSubscriber.java @@ -0,0 +1,10 @@ +package io.getstream.cloud; + +import io.getstream.core.faye.subscription.ChannelSubscription; +import io.getstream.core.models.FeedID; +import java8.util.concurrent.CompletableFuture; + +public interface FeedSubscriber { + CompletableFuture subscribe( + FeedID feedID, RealtimeMessageCallback messageCallback); +} diff --git a/src/main/java/io/getstream/cloud/RealtimeMessageCallback.java b/src/main/java/io/getstream/cloud/RealtimeMessageCallback.java new file mode 100644 index 00000000..cce28932 --- /dev/null +++ b/src/main/java/io/getstream/cloud/RealtimeMessageCallback.java @@ -0,0 +1,7 @@ +package io.getstream.cloud; + +import io.getstream.core.models.RealtimeMessage; + +public interface RealtimeMessageCallback { + void onMessage(RealtimeMessage message); +} diff --git a/src/main/java/io/getstream/core/KeepHistory.java b/src/main/java/io/getstream/core/KeepHistory.java new file mode 100644 index 00000000..5cec59b4 --- /dev/null +++ b/src/main/java/io/getstream/core/KeepHistory.java @@ -0,0 +1,16 @@ +package io.getstream.core; + +public enum KeepHistory { + YES(true), + NO(false); + + private final boolean flag; + + KeepHistory(boolean flag) { + this.flag = flag; + } + + public boolean getFlag() { + return flag; + } +} diff --git a/src/main/java/io/getstream/core/LookupKind.java b/src/main/java/io/getstream/core/LookupKind.java new file mode 100644 index 00000000..9f4e8bdb --- /dev/null +++ b/src/main/java/io/getstream/core/LookupKind.java @@ -0,0 +1,18 @@ +package io.getstream.core; + +public enum LookupKind { + ACTIVITY("activity_id"), + ACTIVITY_WITH_DATA("activity_id"), + REACTION("reaction_id"), + USER("user_id"); + + private final String kind; + + LookupKind(String kind) { + this.kind = kind; + } + + public String getKind() { + return kind; + } +} diff --git a/src/main/java/io/getstream/core/Moderation.java b/src/main/java/io/getstream/core/Moderation.java new file mode 100644 index 00000000..a3187088 --- /dev/null +++ b/src/main/java/io/getstream/core/Moderation.java @@ -0,0 +1,55 @@ +package io.getstream.core; + +import static io.getstream.core.utils.Request.buildPost; +import static io.getstream.core.utils.Routes.*; +import static io.getstream.core.utils.Serialization.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Response; +import io.getstream.core.http.Token; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Map; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public class Moderation { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + public Moderation(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture flag( + Token token, + String entityType, + String entityId, + String reportingUser, + String Reason, + Map Custom) + throws StreamException { + try { + final byte[] payload = + toJSON( + new Object() { + public final String user_id = reportingUser; + public final String entity_type = entityType; + public final String entity_id = entityId; + public final String reason = Reason; + public final Map custom = Custom; + }); + + final URL url = buildModerationFlagURL(baseURL); + return httpClient.execute(buildPost(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new CompletionException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/Region.java b/src/main/java/io/getstream/core/Region.java new file mode 100644 index 00000000..e12884fc --- /dev/null +++ b/src/main/java/io/getstream/core/Region.java @@ -0,0 +1,25 @@ +package io.getstream.core; + +public enum Region { + US_EAST("us-east-api"), + TOKYO("tokyo-api"), + DUBLIN("dublin-api"), + SINGAPORE("singapore-api"), + OHIO("ohio-api"), + JAKARTA("jakarta-api"), + MUMBAI("mumbai-api"), + OREGON("oregon-api"), + SYDNEY("sydney-api"), + CANADA("ca-central-1"); + + private final String region; + + Region(String region) { + this.region = region; + } + + @Override + public String toString() { + return region; + } +} diff --git a/src/main/java/io/getstream/core/Stream.java b/src/main/java/io/getstream/core/Stream.java new file mode 100644 index 00000000..897817c9 --- /dev/null +++ b/src/main/java/io/getstream/core/Stream.java @@ -0,0 +1,593 @@ +package io.getstream.core; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.*; +import static io.getstream.core.utils.Serialization.*; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.OptBoolean; +import com.fasterxml.jackson.core.JsonProcessingException; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Response; +import io.getstream.core.http.Token; +import io.getstream.core.models.*; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.RequestOption; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java8.util.J8Arrays; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class Stream { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + public Stream(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public StreamBatch batch() { + return new StreamBatch(key, baseURL, httpClient); + } + + public StreamCollections collections() { + return new StreamCollections(key, baseURL, httpClient); + } + + public StreamPersonalization personalization() { + return new StreamPersonalization(key, baseURL, httpClient); + } + + public StreamAnalytics analytics() { + return new StreamAnalytics(key, baseURL, httpClient); + } + + public StreamReactions reactions() { + return new StreamReactions(key, baseURL, httpClient); + } + + public Moderation moderation() { + return new Moderation(key, baseURL, httpClient); + } + + public StreamFiles files() { + return new StreamFiles(key, baseURL, httpClient); + } + + public StreamImages images() { + return new StreamImages(key, baseURL, httpClient); + } + + public CompletableFuture> updateActivitiesByID( + Token token, ActivityUpdate[] updates) throws StreamException { + checkNotNull(updates, "No updates"); + checkArgument(updates.length > 0, "No updates"); + for (ActivityUpdate update : updates) { + checkNotNull(update.getID(), "No activity to update"); + checkNotNull(update.getSet(), "No activity properties to set"); + checkNotNull(update.getUnset(), "No activity properties to unset"); + } + + try { + final byte[] payload = + toJSON( + new Object() { + public final ActivityUpdate[] changes = updates; + }); + final URL url = buildActivityUpdateURL(baseURL); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeContainer(response, "activities", Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture updateActivityByID( + Token token, String id, Map set, String[] unset) throws StreamException { + checkNotNull(id, "No activity to update"); + checkNotNull(set, "No activity properties to set"); + checkNotNull(unset, "No activity properties to unset"); + + try { + // XXX: renaming variables so we can unambiguously name payload fields 'id', 'set', 'unset' + String activityID = id; + Map propertiesToSet = set; + String[] propertiesToUnset = unset; + final byte[] payload = + toJSON( + new Object() { + public final String id = activityID; + public final Map set = propertiesToSet; + public final String[] unset = propertiesToUnset; + }); + final URL url = buildActivityUpdateURL(baseURL); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserialize(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> updateActivitiesByForeignID( + Token token, ActivityUpdate[] updates) throws StreamException { + checkNotNull(updates, "No updates"); + checkArgument(updates.length > 0, "No updates"); + for (ActivityUpdate update : updates) { + checkNotNull(update.getForeignID(), "No activity to update"); + checkNotNull(update.getTime(), "Missing timestamp"); + checkNotNull(update.getSet(), "No activity properties to set"); + checkNotNull(update.getUnset(), "No activity properties to unset"); + } + + try { + final byte[] payload = + toJSON( + new Object() { + public final ActivityUpdate[] changes = updates; + }); + final URL url = buildActivityUpdateURL(baseURL); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeContainer(response, "activities", Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture updateActivityByForeignID( + Token token, String foreignID, Date timestamp, Map set, String[] unset) + throws StreamException { + checkNotNull(foreignID, "No activity to update"); + checkNotNull(timestamp, "Missing timestamp"); + checkNotNull(set, "No activity properties to set"); + checkNotNull(unset, "No activity properties to unset"); + + try { + // XXX: renaming variables so we can unambiguously name payload fields 'set', 'unset' + Map propertiesToSet = set; + String[] propertiesToUnset = unset; + final byte[] payload = + toJSON( + new Object() { + public final String foreign_id = foreignID; + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", + lenient = OptBoolean.FALSE, + timezone = "UTC") + public final Date time = timestamp; + + public final Map set = propertiesToSet; + public final String[] unset = propertiesToUnset; + }); + final URL url = buildActivityUpdateURL(baseURL); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserialize(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture openGraph(Token token, URL targetURL) throws StreamException { + checkNotNull(targetURL, "Missing url"); + + try { + final URL url = buildOpenGraphURL(baseURL); + return httpClient + .execute( + buildGet( + url, key, token, new CustomQueryParameter("url", targetURL.toExternalForm()))) + .thenApply( + response -> { + try { + return deserialize(response, OGData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public T getHTTPClientImplementation() { + return httpClient.getImplementation(); + } + + public CompletableFuture getActivities( + Token token, FeedID feed, RequestOption... options) throws StreamException { + checkNotNull(options, "Missing request options"); + + try { + final URL url = buildFeedURL(baseURL, feed, "/"); + return httpClient.execute(buildGet(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getEnrichedActivities( + Token token, FeedID feed, RequestOption... options) throws StreamException { + checkNotNull(options, "Missing request options"); + + try { + final URL url = buildEnrichedFeedURL(baseURL, feed, "/"); + return httpClient.execute(buildGet(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture addActivity(Token token, FeedID feed, Activity activity) + throws StreamException { + checkNotNull(activity, "No activity to add"); + + try { + final byte[] payload = toJSON(activity); + final URL url = buildFeedURL(baseURL, feed, "/"); + return httpClient.execute(buildPost(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture addActivities( + Token token, FeedID feed, Activity... activityObjects) throws StreamException { + checkNotNull(activityObjects, "No activities to add"); + + try { + final byte[] payload = + toJSON( + new Object() { + public final Activity[] activities = activityObjects; + }); + final URL url = buildFeedURL(baseURL, feed, "/"); + return httpClient.execute(buildPost(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture removeActivityByID(Token token, FeedID feed, String id) + throws StreamException { + checkNotNull(id, "No activity id to remove"); + + try { + final URL url = buildFeedURL(baseURL, feed, '/' + id + '/'); + return httpClient.execute(buildDelete(url, key, token)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture removeActivityByForeignID( + Token token, FeedID feed, String foreignID) throws StreamException { + checkNotNull(foreignID, "No activity id to remove"); + + try { + final URL url = buildFeedURL(baseURL, feed, '/' + foreignID + '/'); + return httpClient.execute( + buildDelete(url, key, token, new CustomQueryParameter("foreign_id", "1"))); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture follow( + Token token, Token targetToken, FeedID sourceFeed, FeedID targetFeed, int activityCopyLimit) + throws StreamException { + checkNotNull(targetFeed, "No feed to follow"); + checkArgument(sourceFeed != targetFeed, "Feed can't follow itself"); + checkArgument(activityCopyLimit >= 0, "Activity copy limit should be a non-negative number"); + + try { + final byte[] payload = + toJSON( + new Object() { + public String target = targetFeed.toString(); + public int activity_copy_limit = activityCopyLimit; + public String target_token = targetToken.toString(); + }); + final URL url = buildFeedURL(baseURL, sourceFeed, "/following/"); + return httpClient.execute(buildPost(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getFollowers( + Token token, FeedID feed, RequestOption... options) throws StreamException { + checkNotNull(options, "Missing request options"); + + try { + final URL url = buildFeedURL(baseURL, feed, "/followers/"); + return httpClient.execute(buildGet(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getFollowed(Token token, FeedID feed, RequestOption... options) + throws StreamException { + checkNotNull(options, "Missing request options"); + + try { + final URL url = buildFeedURL(baseURL, feed, "/following/"); + return httpClient.execute(buildGet(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture unfollow( + Token token, FeedID source, FeedID target, RequestOption... options) throws StreamException { + checkNotNull(options, "Missing request options"); + checkNotNull(target, "No target feed to unfollow"); + + try { + final URL url = buildFeedURL(baseURL, source, "/following/" + target + '/'); + return httpClient.execute(buildDelete(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getFollowStats( + Token token, + FeedID feed, + String[] followerSlugs, + String[] followingSlugs, + RequestOption... options) + throws StreamException { + try { + final URL url = followStatsPath(baseURL); + final List params = new ArrayList<>(4); + final String feedId = String.join(":", feed.getSlug(), feed.getUserID()); + params.add(new CustomQueryParameter("followers", feedId)); + params.add(new CustomQueryParameter("following", feedId)); + + if (followerSlugs != null && followerSlugs.length > 0) { + params.add(new CustomQueryParameter("followers_slugs", String.join(",", followerSlugs))); + } + if (followingSlugs != null && followingSlugs.length > 0) { + params.add(new CustomQueryParameter("following_slugs", String.join(",", followingSlugs))); + } + + return httpClient.execute( + buildGet(url, key, token, params.toArray(new CustomQueryParameter[0]))); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture updateActivityToTargets( + Token token, FeedID feed, Activity activity, FeedID[] add, FeedID[] remove, FeedID[] replace) + throws StreamException { + checkNotNull(activity, "No activity to update"); + checkNotNull(activity.getForeignID(), "Activity is required to have foreign ID attribute"); + checkNotNull(activity.getTime(), "Activity is required to have time attribute"); + checkNotNull(add, "No targets to add"); + checkNotNull(remove, "No targets to remove"); + checkNotNull(replace, "No targets to set"); + boolean modification = replace.length == 0 && (add.length > 0 || remove.length > 0); + boolean replacement = replace.length > 0 && add.length == 0 && remove.length == 0; + checkArgument( + modification || replacement, + "Can't replace and modify activity to targets at the same time"); + + final String[] addedTargets = + J8Arrays.stream(add).map(id -> id.toString()).toArray(String[]::new); + final String[] removedTargets = + J8Arrays.stream(remove).map(id -> id.toString()).toArray(String[]::new); + final String[] newTargets = + J8Arrays.stream(replace).map(id -> id.toString()).toArray(String[]::new); + + try { + final byte[] payload = + toJSON( + new Object() { + public String foreign_id = activity.getForeignID(); + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", + lenient = OptBoolean.FALSE, + timezone = "UTC") + public Date time = activity.getTime(); + + public String[] added_targets = addedTargets; + public String[] removed_targets = removedTargets; + public String[] new_targets = newTargets; + }); + final URL url = buildToTargetUpdateURL(baseURL, feed); + return httpClient.execute(buildPost(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getUser(Token token, String id, boolean withFollowCounts) + throws StreamException { + checkNotNull(id, "Missing user ID"); + checkArgument(!id.isEmpty(), "Missing user ID"); + + try { + final URL url = buildUsersURL(baseURL, id + '/'); + return httpClient.execute( + buildGet( + url, + key, + token, + new CustomQueryParameter("with_follow_counts", Boolean.toString(withFollowCounts)))); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture deleteUser(Token token, String id) throws StreamException { + checkNotNull(id, "Missing user ID"); + checkArgument(!id.isEmpty(), "Missing user ID"); + + try { + final URL url = buildUsersURL(baseURL, id + '/'); + return httpClient.execute(buildDelete(url, key, token)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture createUser( + Token token, String userID, Data userData, boolean getOrCreate) throws StreamException { + checkNotNull(userID, "Missing user ID"); + checkNotNull(userData, "Missing user data"); + checkArgument(!userID.isEmpty(), "Missing user ID"); + + try { + final byte[] payload = + toJSON( + new Object() { + public String id = userID; + public Map data = userData.getData(); + }); + final URL url = buildUsersURL(baseURL); + return httpClient.execute( + buildPost( + url, + key, + token, + payload, + new CustomQueryParameter("get_or_create", Boolean.toString(getOrCreate)))); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture updateUser(Token token, String userID, Data userData) + throws StreamException { + checkNotNull(userID, "Missing user ID"); + checkNotNull(userData, "Missing user data"); + checkArgument(!userID.isEmpty(), "Missing user ID"); + + try { + final byte[] payload = + toJSON( + new Object() { + public Map data = userData.getData(); + }); + final URL url = buildUsersURL(baseURL, userID + '/'); + return httpClient.execute(buildPut(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture deleteActivities(Token token, BatchDeleteActivitiesRequest request) throws StreamException { + try { + final URL url = deleteActivitiesURL(baseURL); + final byte[] payload = toJSON(request); + io.getstream.core.http.Request httpRequest = buildPost(url, key, token, payload); + return httpClient.execute(httpRequest).thenApply(response -> null); + } catch (Exception e) { + throw new StreamException(e); + } + } + + public CompletableFuture deleteReactions(Token token, BatchDeleteReactionsRequest request) throws StreamException { + try { + + final URL url = deleteReactionsURL(baseURL); + final byte[] payload = toJSON(request); + io.getstream.core.http.Request httpRequest = buildPost(url, key, token, payload); + + return httpClient.execute(httpRequest).thenApply(response -> null); + } catch (Exception e) { + throw new StreamException(e); + } + } + + public CompletableFuture exportUserActivities(Token token, String userId) throws StreamException { + if (userId == null || userId.isEmpty()) { + throw new IllegalArgumentException("User ID can't be null or empty"); + } + + try { + final URL url = buildExportIDsURL(baseURL, userId); + io.getstream.core.http.Request request = buildGet(url, key, token); + return httpClient + .execute(request) + .thenApply( + response -> { + try { + return deserialize(response, ExportIDsResponse.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture queryAuditLogs(Token token, RequestOption... options) throws StreamException { + try { + final URL url = buildAuditLogsURL(baseURL); + return httpClient + .execute(buildGet(url, key, token, options)) + .thenApply( + response -> { + try { + return deserialize(response, io.getstream.client.QueryAuditLogsResponse.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamAnalytics.java b/src/main/java/io/getstream/core/StreamAnalytics.java new file mode 100644 index 00000000..8e0e22dd --- /dev/null +++ b/src/main/java/io/getstream/core/StreamAnalytics.java @@ -0,0 +1,103 @@ +package io.getstream.core; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.buildPost; +import static io.getstream.core.utils.Routes.buildAnalyticsURL; +import static io.getstream.core.utils.Serialization.deserializeError; +import static io.getstream.core.utils.Serialization.toJSON; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Charsets; +import com.google.common.collect.ObjectArrays; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.models.Engagement; +import io.getstream.core.models.Impression; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class StreamAnalytics { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamAnalytics(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture trackEngagement(Token token, Engagement... events) + throws StreamException { + checkNotNull(events, "No events to track"); + checkArgument(events.length > 0, "No events to track"); + + try { + final byte[] payload = + toJSON( + new Object() { + public final Engagement[] content_list = events; + }); + final URL url = buildAnalyticsURL(baseURL, "engagement/"); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture trackImpression(Token token, Impression event) + throws StreamException { + checkNotNull(event, "No events to track"); + + try { + final byte[] payload = toJSON(event); + final URL url = buildAnalyticsURL(baseURL, "impression/"); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public URL createRedirectURL( + Token token, URL url, Impression[] impressions, Engagement[] engagements) + throws StreamException { + try { + final byte[] events = toJSON(ObjectArrays.concat(impressions, engagements, Object.class)); + return HTTPClient.requestBuilder() + .url(buildAnalyticsURL(baseURL, "redirect/")) + .addQueryParameter("api_key", key) + .addQueryParameter("url", url.toExternalForm()) + .addQueryParameter("events", new String(events, Charsets.UTF_8)) + .addQueryParameter("auth_type", "jwt") + .addQueryParameter("authorization", token.toString()) + .build() + .getURL(); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamBatch.java b/src/main/java/io/getstream/core/StreamBatch.java new file mode 100644 index 00000000..e171b572 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamBatch.java @@ -0,0 +1,288 @@ +package io.getstream.core; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.buildGet; +import static io.getstream.core.utils.Request.buildPost; +import static io.getstream.core.utils.Routes.*; +import static io.getstream.core.utils.Serialization.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Joiner; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.models.*; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.EnrichmentFlags; +import io.getstream.core.options.RequestOption; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.TimeZone; +import java8.util.J8Arrays; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class StreamBatch { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + public StreamBatch(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture addToMany(Token token, Activity activity, FeedID... feeds) + throws StreamException { + checkNotNull(activity, "Missing activity"); + checkNotNull(feeds, "No feeds to add to"); + checkArgument(feeds.length > 0, "No feeds to add to"); + + // XXX: renaming the variable so we can unambiguously name payload field 'activity' + Activity data = activity; + String[] feedIDs = J8Arrays.stream(feeds).map(feed -> feed.toString()).toArray(String[]::new); + try { + final byte[] payload = + toJSON( + new Object() { + public final Activity activity = data; + public final String[] feeds = feedIDs; + }); + final URL url = buildAddToManyURL(baseURL); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture followMany( + Token token, int activityCopyLimit, FollowRelation... follows) throws StreamException { + checkArgument(activityCopyLimit >= 0, "Activity copy limit must be non negative"); + checkNotNull(follows, "No feeds to follow"); + checkArgument(follows.length > 0, "No feeds to follow"); + + try { + final byte[] payload = toJSON(follows); + final URL url = buildFollowManyURL(baseURL); + return httpClient + .execute( + buildPost( + url, + key, + token, + payload, + new CustomQueryParameter( + "activity_copy_limit", Integer.toString(activityCopyLimit)))) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture unfollowMany(Token token, UnfollowOperation... unfollows) + throws StreamException { + checkNotNull(unfollows, "No feeds to unfollow"); + checkArgument(unfollows.length > 0, "No feeds to unfollow"); + + try { + final byte[] payload = toJSON(unfollows); + final URL url = buildUnfollowManyURL(baseURL); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> getActivitiesByID(Token token, String... activityIDs) + throws StreamException { + checkNotNull(activityIDs, "No activities to get"); + checkArgument(activityIDs.length > 0, "No activities to get"); + + try { + final URL url = buildActivitiesURL(baseURL); + return httpClient + .execute( + buildGet( + url, + key, + token, + new CustomQueryParameter("ids", Joiner.on(",").join(activityIDs)))) + .thenApply( + response -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> getEnrichedActivitiesByID( + Token token, EnrichmentFlags flags, String... activityIDs) throws StreamException { + checkNotNull(activityIDs, "No activities to get"); + checkArgument(activityIDs.length > 0, "No activities to get"); + + try { + final URL url = buildEnrichedActivitiesURL(baseURL); + return httpClient + .execute( + buildGet( + url, + key, + token, + flags, + new CustomQueryParameter("ids", Joiner.on(",").join(activityIDs)))) + .thenApply( + response -> { + try { + return deserializeContainer(response, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> getActivitiesByForeignID( + Token token, ForeignIDTimePair... activityIDTimePairs) throws StreamException { + checkNotNull(activityIDTimePairs, "No activities to get"); + checkArgument(activityIDTimePairs.length > 0, "No activities to get"); + + SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + timestampFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + timestampFormat.setLenient(false); + + String[] foreignIDs = + J8Arrays.stream(activityIDTimePairs) + .map(pair -> pair.getForeignID()) + .toArray(String[]::new); + String[] timestamps = + J8Arrays.stream(activityIDTimePairs) + .map(pair -> timestampFormat.format(pair.getTime())) + .toArray(String[]::new); + try { + final URL url = buildActivitiesURL(baseURL); + final RequestOption[] options = + new RequestOption[] { + new CustomQueryParameter("foreign_ids", Joiner.on(",").join(foreignIDs)), + new CustomQueryParameter("timestamps", Joiner.on(",").join(timestamps)) + }; + return httpClient + .execute(buildGet(url, key, token, options)) + .thenApply( + response -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> getEnrichedActivitiesByForeignID( + Token token, ForeignIDTimePair... activityIDTimePairs) throws StreamException { + checkNotNull(activityIDTimePairs, "No activities to get"); + checkArgument(activityIDTimePairs.length > 0, "No activities to get"); + + SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + timestampFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + timestampFormat.setLenient(false); + + String[] foreignIDs = + J8Arrays.stream(activityIDTimePairs) + .map(pair -> pair.getForeignID()) + .toArray(String[]::new); + String[] timestamps = + J8Arrays.stream(activityIDTimePairs) + .map(pair -> timestampFormat.format(pair.getTime())) + .toArray(String[]::new); + try { + final URL url = buildEnrichedActivitiesURL(baseURL); + final RequestOption[] options = + new RequestOption[] { + new CustomQueryParameter("foreign_ids", Joiner.on(",").join(foreignIDs)), + new CustomQueryParameter("timestamps", Joiner.on(",").join(timestamps)) + }; + return httpClient + .execute(buildGet(url, key, token, options)) + .thenApply( + response -> { + try { + return deserializeContainer(response, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture updateActivities(Token token, Activity... activities) + throws StreamException { + checkNotNull(activities, "No activities to update"); + checkArgument(activities.length > 0, "No activities to update"); + + try { + // XXX: renaming the variable so we can unambiguously name payload field 'activities' + Activity[] data = activities; + final byte[] payload = + toJSON( + new Object() { + public final Activity[] activities = data; + }); + final URL url = buildActivitiesURL(baseURL); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamCollections.java b/src/main/java/io/getstream/core/StreamCollections.java new file mode 100644 index 00000000..c00e982f --- /dev/null +++ b/src/main/java/io/getstream/core/StreamCollections.java @@ -0,0 +1,238 @@ +package io.getstream.core; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.buildBatchCollectionsURL; +import static io.getstream.core.utils.Routes.buildCollectionsURL; +import static io.getstream.core.utils.Serialization.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.models.CollectionData; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.RequestOption; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java8.util.J8Arrays; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; +import java8.util.stream.Collectors; + +public final class StreamCollections { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamCollections(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture add( + Token token, String userID, String collection, CollectionData item) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkNotNull(item, "Collection data can't be null"); + + try { + ImmutableMap.Builder builder = + new ImmutableMap.Builder().put("data", item.getData()); + if (userID != null) { + builder.put("user_id", userID); + } + if (item.getID() != null) { + builder.put("id", item.getID()); + } + final byte[] payload = toJSON(builder.build()); + final URL url = buildCollectionsURL(baseURL, collection + '/'); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserialize(response, CollectionData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture update( + Token token, String userID, String collection, CollectionData item) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkNotNull(item, "Collection data can't be null"); + + try { + ImmutableMap.Builder builder = + new ImmutableMap.Builder().put("data", item.getData()); + if (userID != null) { + builder.put("user_id", userID); + } + final byte[] payload = toJSON(builder.build()); + final URL url = buildCollectionsURL(baseURL, collection + '/' + item.getID() + '/'); + return httpClient + .execute(buildPut(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserialize(response, CollectionData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture upsert(Token token, String collection, CollectionData... items) + throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkArgument(items.length > 0, "Collection data can't be empty"); + + try { + final byte[] payload = + toJSON( + new Object() { + public final Map data = + ImmutableMap.of(collection, items); + }); + final URL url = buildBatchCollectionsURL(baseURL); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture get(Token token, String collection, String id) + throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkNotNull(id, "Collection id can't be null"); + checkArgument(!id.isEmpty(), "Collection id can't be empty"); + + try { + final URL url = buildCollectionsURL(baseURL, collection + '/' + id + '/'); + return httpClient + .execute(buildGet(url, key, token)) + .thenApply( + response -> { + try { + return deserialize(response, CollectionData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> select( + Token token, String collection, String... ids) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkArgument(ids.length > 0, "Collection ids can't be empty"); + + List foreignIDs = + J8Arrays.stream(ids) + .map(id -> String.format("%s:%s", collection, id)) + .collect(Collectors.toList()); + try { + final URL url = buildBatchCollectionsURL(baseURL); + return httpClient + .execute( + buildGet( + url, + key, + token, + new CustomQueryParameter("foreign_ids", Joiner.on(",").join(foreignIDs)))) + .thenApply( + response -> { + try { + return deserializeContainer(response, "response.data", CollectionData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete(Token token, String collection, String id) + throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkNotNull(id, "Collection id can't be null"); + checkArgument(!id.isEmpty(), "Collection id can't be empty"); + + try { + final URL url = buildCollectionsURL(baseURL, collection + '/' + id + '/'); + return httpClient + .execute(buildDelete(url, key, token)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture deleteMany(Token token, String collection, String... ids) + throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkArgument(ids.length > 0, "Collection ids can't be empty"); + + try { + final URL url = buildBatchCollectionsURL(baseURL); + final RequestOption[] options = + new RequestOption[] { + new CustomQueryParameter("collection_name", collection), + new CustomQueryParameter("ids", Joiner.on(",").join(ids)) + }; + return httpClient + .execute(buildDelete(url, key, token, options)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamFiles.java b/src/main/java/io/getstream/core/StreamFiles.java new file mode 100644 index 00000000..0da2318c --- /dev/null +++ b/src/main/java/io/getstream/core/StreamFiles.java @@ -0,0 +1,98 @@ +package io.getstream.core; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.buildDelete; +import static io.getstream.core.utils.Request.buildMultiPartPost; +import static io.getstream.core.utils.Routes.buildFilesURL; +import static io.getstream.core.utils.Serialization.deserialize; +import static io.getstream.core.utils.Serialization.deserializeError; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.options.CustomQueryParameter; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public class StreamFiles { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamFiles(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture upload(Token token, String fileName, byte[] content) + throws StreamException { + checkNotNull(content, "No data to upload"); + checkArgument(content.length > 0, "No data to upload"); + + try { + final URL url = buildFilesURL(baseURL); + return httpClient + .execute(buildMultiPartPost(url, key, token, fileName, content)) + .thenApply( + response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture upload(Token token, File content) throws StreamException { + checkNotNull(content, "No file to upload"); + checkArgument(content.exists(), "No file to upload"); + + try { + final URL url = buildFilesURL(baseURL); + return httpClient + .execute(buildMultiPartPost(url, key, token, content)) + .thenApply( + response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete(Token token, URL targetURL) throws StreamException { + checkNotNull(targetURL, "No file to delete"); + + try { + final URL url = buildFilesURL(baseURL); + return httpClient + .execute( + buildDelete( + url, key, token, new CustomQueryParameter("url", targetURL.toExternalForm()))) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamImages.java b/src/main/java/io/getstream/core/StreamImages.java new file mode 100644 index 00000000..0fbb2e88 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamImages.java @@ -0,0 +1,125 @@ +package io.getstream.core; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.buildImagesURL; +import static io.getstream.core.utils.Serialization.deserialize; +import static io.getstream.core.utils.Serialization.deserializeError; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.RequestOption; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public class StreamImages { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamImages(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture upload(Token token, String fileName, byte[] content) + throws StreamException { + checkNotNull(content, "No data to upload"); + checkArgument(content.length > 0, "No data to upload"); + + try { + final URL url = buildImagesURL(baseURL); + return httpClient + .execute(buildMultiPartPost(url, key, token, fileName, content)) + .thenApply( + response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture upload(Token token, File content) throws StreamException { + checkNotNull(content, "No file to upload"); + checkArgument(content.exists(), "No file to upload"); + + try { + final URL url = buildImagesURL(baseURL); + return httpClient + .execute(buildMultiPartPost(url, key, token, content)) + .thenApply( + response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete(Token token, URL targetURL) throws StreamException { + checkNotNull(targetURL, "No image to delete"); + + try { + final URL url = buildImagesURL(baseURL); + return httpClient + .execute( + buildDelete( + url, key, token, new CustomQueryParameter("url", targetURL.toExternalForm()))) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture process(Token token, URL targetURL, RequestOption option) + throws StreamException { + checkNotNull(targetURL, "No image to process"); + + try { + final URL url = buildImagesURL(baseURL); + return httpClient + .execute( + buildGet( + url, + key, + token, + option, + new CustomQueryParameter("url", targetURL.toExternalForm()))) + .thenApply( + response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamPersonalization.java b/src/main/java/io/getstream/core/StreamPersonalization.java new file mode 100644 index 00000000..b3f24663 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamPersonalization.java @@ -0,0 +1,129 @@ +package io.getstream.core; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.buildPersonalizationURL; +import static io.getstream.core.utils.Serialization.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.RequestOption; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Map; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; +import java8.util.stream.StreamSupport; + +public final class StreamPersonalization { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamPersonalization(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture> get( + Token token, String userID, String resource, Map params) + throws StreamException { + checkNotNull(resource, "Resource can't be empty"); + checkArgument(!resource.isEmpty(), "Resource can't be empty"); + checkNotNull(params, "Missing params"); + + try { + final URL url = buildPersonalizationURL(baseURL, resource + '/'); + final RequestOption[] options = + StreamSupport.stream(params.entrySet()) + .map(entry -> new CustomQueryParameter(entry.getKey(), entry.getValue().toString())) + .toArray(RequestOption[]::new); + return httpClient + .execute(buildGet(url, key, token, options)) + .thenApply( + response -> { + try { + return deserialize(response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture post( + Token token, + String userID, + String resource, + Map params, + Map payload) + throws StreamException { + checkNotNull(resource, "Resource can't be empty"); + checkArgument(!resource.isEmpty(), "Resource can't be empty"); + checkNotNull(params, "Missing params"); + checkNotNull(params, "Missing payload"); + + try { + final byte[] jsonPayload = + toJSON( + new Object() { + public final Map data = payload; + }); + final URL url = buildPersonalizationURL(baseURL, resource + '/'); + final RequestOption[] options = + StreamSupport.stream(params.entrySet()) + .map(entry -> new CustomQueryParameter(entry.getKey(), entry.getValue().toString())) + .toArray(RequestOption[]::new); + return httpClient + .execute(buildPost(url, key, token, jsonPayload, options)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete( + Token token, String userID, String resource, Map params) + throws StreamException { + checkNotNull(resource, "Resource can't be empty"); + checkArgument(!resource.isEmpty(), "Resource can't be empty"); + checkNotNull(params, "Missing params"); + + try { + final URL url = buildPersonalizationURL(baseURL, resource + '/'); + final RequestOption[] options = + params.entrySet().stream() + .map(entry -> new CustomQueryParameter(entry.getKey(), entry.getValue().toString())) + .toArray(RequestOption[]::new); + return httpClient + .execute(buildDelete(url, key, token, options)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamReactions.java b/src/main/java/io/getstream/core/StreamReactions.java new file mode 100644 index 00000000..1bbba031 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamReactions.java @@ -0,0 +1,387 @@ +package io.getstream.core; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.buildReactionsURL; +import static io.getstream.core.utils.Routes.buildGetReactionsBatchURL; +import static io.getstream.core.utils.Serialization.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.collect.ImmutableMap; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Request; +import io.getstream.core.http.Token; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Paginated; +import io.getstream.core.models.Reaction; +import io.getstream.core.models.ReactionBatch; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.Filter; +import io.getstream.core.options.Limit; +import io.getstream.core.options.RequestOption; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java8.util.J8Arrays; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; + +public final class StreamReactions { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamReactions(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture get(Token token, String id) throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + try { + final URL url = buildReactionsURL(baseURL, id + '/'); + return httpClient + .execute(buildGet(url, key, token)) + .thenApply( + response -> { + try { + return deserialize(response, Reaction.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getPaginated(Token token, String id) throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + try { + final URL url = buildReactionsURL(baseURL, id + '/'); + return httpClient + .execute(buildGet(url, key, token)) + .thenApply( + response -> { + try { + return deserialize(response, Paginated.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> filter( + Token token, LookupKind lookup, String id, Filter filter, Limit limit, String kind) + throws StreamException { + return filter(token, lookup, id, filter, limit, kind, null, ""); + } + + public CompletableFuture> filter( + Token token, + LookupKind lookup, + String id, + Filter filter, + Limit limit, + String kind, + Boolean withOwnChildren, + String filterUserId) + throws StreamException { + checkNotNull(lookup, "Lookup kind can't be null"); + checkNotNull(id, "Reaction ID can't be null"); + checkArgument(!id.isEmpty(), "Reaction ID can't be empty"); + checkNotNull(filter, "Filter can't be null"); + checkNotNull(kind, "Kind can't be null"); + + try { + final URL url = buildReactionsURL(baseURL, lookup.getKind() + '/' + id + '/' + kind); + RequestOption withActivityData = + new CustomQueryParameter( + "with_activity_data", Boolean.toString(lookup == LookupKind.ACTIVITY_WITH_DATA)); + RequestOption ownChildren = + new CustomQueryParameter( + "withOwnChildren", Boolean.toString(withOwnChildren != null && withOwnChildren)); + RequestOption filterByUser = + new CustomQueryParameter( + "filter_user_id", filterUserId); + + return httpClient + .execute(buildGet(url, key, token, filter, limit, withActivityData, ownChildren, filterByUser)) + .thenApply( + response -> { + try { + return deserializeContainer(response, Reaction.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> paginatedFilter( + Token token, LookupKind lookup, String id, Filter filter, Limit limit, String kind) + throws StreamException { + return paginatedFilter(token, lookup, id, filter, limit, kind, null); + } + + public CompletableFuture> paginatedFilter( + Token token, + LookupKind lookup, + String id, + Filter filter, + Limit limit, + String kind, + Boolean withOwnChildren) + throws StreamException { + checkNotNull(lookup, "Lookup kind can't be null"); + checkNotNull(id, "Reaction ID can't be null"); + checkArgument(!id.isEmpty(), "Reaction ID can't be empty"); + checkNotNull(filter, "Filter can't be null"); + checkNotNull(kind, "Kind can't be null"); + + try { + final URL url = buildReactionsURL(baseURL, lookup.getKind() + '/' + id + '/' + kind); + RequestOption withActivityData = + new CustomQueryParameter( + "with_activity_data", Boolean.toString(lookup == LookupKind.ACTIVITY_WITH_DATA)); + RequestOption ownChildren = + new CustomQueryParameter( + "withOwnChildren", Boolean.toString(withOwnChildren != null && withOwnChildren)); + + return httpClient + .execute(buildGet(url, key, token, filter, limit, withActivityData, ownChildren)) + .thenApply( + response -> { + try { + return deserialize(response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> paginatedFilter(Token token, String next) + throws StreamException { + checkNotNull(next, "next can't be null"); + checkArgument(!next.trim().isEmpty(), "next can't be empty"); + + try { + final URL url = new URL(baseURL, next); + return httpClient + .execute(buildGet(url, key, token)) + .thenApply( + response -> { + try { + return deserialize(response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture add( + Token token, String userID, Reaction reaction, FeedID... targetFeeds) throws StreamException { + return add(token, userID, reaction, targetFeeds, null); + } + + public CompletableFuture add( + Token token, String userID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) throws StreamException { + checkNotNull(reaction, "Reaction can't be null"); + checkArgument( + reaction.getActivityID() != null || reaction.getParent() != null, + "Reaction has to either have and activity ID or parent"); + checkArgument( + reaction.getActivityID() == null || reaction.getParent() == null, + "Reaction can't have both activity ID and parent"); + if (reaction.getActivityID() != null) { + checkArgument(!reaction.getActivityID().isEmpty(), "Reaction activity ID can't be empty"); + } + if (reaction.getParent() != null) { + checkArgument(!reaction.getParent().isEmpty(), "Reaction parent can't be empty"); + } + checkNotNull(reaction.getKind(), "Reaction kind can't be null"); + checkArgument(!reaction.getKind().isEmpty(), "Reaction kind can't be empty"); + + String[] targetFeedIDs = + J8Arrays.stream(targetFeeds).map(feed -> feed.toString()).toArray(String[]::new); + + try { + ImmutableMap.Builder payloadBuilder = ImmutableMap.builder(); + payloadBuilder.put("kind", reaction.getKind()); + payloadBuilder.put("target_feeds", targetFeedIDs); + if (targetFeedsExtraData != null) { + payloadBuilder.put("target_feeds_extra_data", targetFeedsExtraData); + } + if (reaction.getActivityID() != null) { + payloadBuilder.put("activity_id", reaction.getActivityID()); + } + if (userID != null || reaction.getUserID() != null) { + payloadBuilder.put("user_id", firstNonNull(userID, reaction.getUserID())); + } + if (reaction.getParent() != null) { + payloadBuilder.put("parent", reaction.getParent()); + } + if (reaction.getId() != null) { + payloadBuilder.put("id", reaction.getId()); + } + if (reaction.getExtra() != null) { + payloadBuilder.put("data", reaction.getExtra()); + } + if (reaction.getModerationTemplate() != null) { + payloadBuilder.put("moderation_template", reaction.getModerationTemplate()); + } + final byte[] payload = toJSON(payloadBuilder.build()); + final URL url = buildReactionsURL(baseURL); + return httpClient + .execute(buildPost(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserialize(response, Reaction.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture update(Token token, Reaction reaction, FeedID... targetFeeds) + throws StreamException { + checkNotNull(reaction, "Reaction can't be null"); + checkNotNull(reaction.getId(), "Reaction id can't be null"); + checkArgument(!reaction.getId().isEmpty(), "Reaction id can't be empty"); + + String[] targetFeedIDs = + J8Arrays.stream(targetFeeds).map(feed -> feed.toString()).toArray(String[]::new); + + try { + ImmutableMap.Builder payloadBuilder = ImmutableMap.builder(); + payloadBuilder.put("data", reaction.getExtra()); + payloadBuilder.put("target_feeds", targetFeedIDs); + + if (reaction.getModerationTemplate() != null) { + payloadBuilder.put("moderation_template", reaction.getModerationTemplate()); + } + + final byte[] payload = toJSON(payloadBuilder.build()); + final URL url = buildReactionsURL(baseURL, reaction.getId() + '/'); + return httpClient + .execute(buildPut(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete(Token token, String id, Boolean soft) + throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + try { + final URL url = buildReactionsURL(baseURL, id + '/'); + + final Request deleteRequest = + soft + ? buildDelete(url, key, token, new CustomQueryParameter("soft", "true")) + : buildDelete(url, key, token); + + return httpClient + .execute(deleteRequest) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture restore(Token token, String id) throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + try { + final URL url = buildReactionsURL(baseURL, id + "/restore/"); + byte[] payload = new byte[0]; + return httpClient + .execute(buildPut(url, key, token, payload)) + .thenApply( + response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getBatchReactions(Token token, List ids, Boolean includeDeleted) throws StreamException { + checkNotNull(ids, "Reaction IDs can't be null"); + checkArgument(!ids.isEmpty(), "Reaction IDs can't be empty"); + + try { + final URL url = buildGetReactionsBatchURL(baseURL); + RequestOption optionIds = + new CustomQueryParameter( + "ids", String.join(",", ids) + ); + RequestOption includeDeletedOption = + new CustomQueryParameter( + "include_deleted", includeDeleted.toString() + ); + + return httpClient + .execute(buildGet(url, key, token, optionIds, includeDeletedOption)) + .thenApply( + response -> { + try { + return deserialize(response, ReactionBatch.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/exceptions/StreamAPIException.java b/src/main/java/io/getstream/core/exceptions/StreamAPIException.java new file mode 100644 index 00000000..a1ecb66c --- /dev/null +++ b/src/main/java/io/getstream/core/exceptions/StreamAPIException.java @@ -0,0 +1,61 @@ +package io.getstream.core.exceptions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public final class StreamAPIException extends StreamException { + private final int errorCode; + private final int statusCode; + private final String errorName; + + @JsonCreator + public StreamAPIException( + @JsonProperty("detail") String message, + @JsonProperty("code") int errorCode, + @JsonProperty("status_code") int statusCode, + @JsonProperty("exception") String errorName) { + super(formatMessage(message, errorName, errorCode, statusCode)); + + this.errorCode = errorCode; + this.statusCode = statusCode; + this.errorName = errorName; + } + + private static String formatMessage( + String message, String errorName, int errorCode, int statusCode) { + StringBuilder result = new StringBuilder(); + if (errorName != null && !errorName.isEmpty()) { + result.append(errorName); + } + if (message != null && !message.isEmpty()) { + if (result.length() > 0) { + result.append(": "); + } + + result.append(message); + } + if (result.length() > 0) { + result.append(" "); + } + result.append("(code = "); + result.append(errorCode); + result.append(" status = "); + result.append(statusCode); + result.append(')'); + return result.toString(); + } + + public int getErrorCode() { + return errorCode; + } + + public int getStatusCode() { + return statusCode; + } + + public String getErrorName() { + return errorName; + } +} diff --git a/src/main/java/io/getstream/core/exceptions/StreamException.java b/src/main/java/io/getstream/core/exceptions/StreamException.java new file mode 100644 index 00000000..2833aa81 --- /dev/null +++ b/src/main/java/io/getstream/core/exceptions/StreamException.java @@ -0,0 +1,24 @@ +package io.getstream.core.exceptions; + +public class StreamException extends Exception { + public StreamException() { + super(); + } + + public StreamException(String message) { + super(message); + } + + public StreamException(String message, Throwable cause) { + super(message, cause); + } + + public StreamException(Throwable cause) { + super(cause); + } + + protected StreamException( + String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/io/getstream/core/faye/Advice.java b/src/main/java/io/getstream/core/faye/Advice.java new file mode 100644 index 00000000..5d3a5779 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/Advice.java @@ -0,0 +1,68 @@ +package io.getstream.core.faye; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.google.common.base.MoreObjects; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Advice { + private final String reconnect; + private final Integer interval; + private final Integer timeout; + + public static final String NONE = "none"; + public static final String HANDSHAKE = "handshake"; + public static final String RETRY = "retry"; + + // for deserialization + public Advice() { + reconnect = null; + interval = null; + timeout = null; + } + + public Advice(String reconnect, Integer interval, Integer timeout) { + this.reconnect = reconnect; + this.interval = interval; + this.timeout = timeout; + } + + public String getReconnect() { + return reconnect; + } + + public Integer getInterval() { + return interval; + } + + public Integer getTimeout() { + return timeout; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Advice that = (Advice) o; + return Objects.equals(reconnect, that.reconnect) + && Objects.equals(interval, that.interval) + && Objects.equals(timeout, that.timeout); + } + + @Override + public int hashCode() { + return Objects.hash(reconnect, interval, timeout); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .omitNullValues() + .add("reconnect", this.reconnect) + .add("interval", this.interval) + .add("timeout", this.timeout) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/faye/Channel.java b/src/main/java/io/getstream/core/faye/Channel.java new file mode 100644 index 00000000..988ee789 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/Channel.java @@ -0,0 +1,119 @@ +package io.getstream.core.faye; + +import com.google.common.base.MoreObjects; +import io.getstream.core.faye.emitter.EventEmitter; +import io.getstream.core.faye.emitter.EventListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class Channel { + + public static final String HANDSHAKE = "/meta/handshake"; + public static final String CONNECT = "/meta/connect"; + public static final String DISCONNECT = "/meta/disconnect"; + public static final String SUBSCRIBE = "/meta/subscribe"; + public static final String UNSUBSCRIBE = "/meta/unsubscribe"; + + private final String name; + + public Channel(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + final EventEmitter eventEmitter = new EventEmitter<>(); + + public void bind(String event, EventListener listener) { + eventEmitter.on(event, listener); + } + + public void unbind(String event, EventListener listener) { + eventEmitter.removeListener(event, listener); + } + + public void trigger(String event, Message message) { + eventEmitter.emit(event, message); + } + + public boolean hasListeners(String event) throws Exception { + return eventEmitter.hasListeners(event); + } + + public static List expand(String name) { + final List channels = new ArrayList<>(Arrays.asList("/**", name)); + final List segments = parse(name); + + if (segments == null) return null; + + List copy = new ArrayList<>(segments); + copy.add(copy.size() - 1, "*"); + channels.add(unparse(copy)); + + for (int i = 1; i < segments.size(); i++) { + copy = segments.subList(0, i); + copy.add("**"); + channels.add(unparse(copy)); + } + + return channels; + } + + public static boolean isValid(String name) { + return Grammar.CHANNEL_NAME.matcher(name).matches() + || Grammar.CHANNEL_PATTERN.matcher(name).matches(); + } + + public static List parse(String name) { + if (!isValid(name)) return null; + final String[] splits = name.split("/"); + return Arrays.asList(splits).subList(1, splits.length); + } + + public static String unparse(List segments) { + final String joinedSegments = String.join("/", segments); + return "/" + joinedSegments; + } + + public static Boolean isMeta(String name) { + final List segments = parse(name); + if (segments == null) return null; + return segments.get(0).equals("meta"); + } + + public static Boolean isService(String name) { + final List segments = parse(name); + if (segments == null) return null; + return segments.get(0).equals("service"); + } + + public static Boolean isSubscribable(String name) { + if (!isValid(name)) return null; + final Boolean isMeta = isMeta(name); + final Boolean isService = isService(name); + if (isMeta == null || isService == null) return null; + return !isMeta && !isService; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Channel that = (Channel) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).omitNullValues().add("name", this.name).toString(); + } +} diff --git a/src/main/java/io/getstream/core/faye/DefaultMessageTransformer.java b/src/main/java/io/getstream/core/faye/DefaultMessageTransformer.java new file mode 100644 index 00000000..bd462377 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/DefaultMessageTransformer.java @@ -0,0 +1,13 @@ +package io.getstream.core.faye; + +public class DefaultMessageTransformer extends MessageTransformer { + @Override + public Message transformRequest(Message message) { + return message; + } + + @Override + public Message transformResponse(Message message) { + return message; + } +} diff --git a/src/main/java/io/getstream/core/faye/FayeClientError.java b/src/main/java/io/getstream/core/faye/FayeClientError.java new file mode 100644 index 00000000..c8c643d8 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/FayeClientError.java @@ -0,0 +1,57 @@ +package io.getstream.core.faye; + +import com.google.common.base.MoreObjects; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class FayeClientError extends Throwable { + private final Integer code; + private final List params; + private final String errorMessage; + + public FayeClientError(Integer code, List params, String errorMessage) { + this.code = code; + this.params = params; + this.errorMessage = errorMessage; + } + + public static FayeClientError parse(String errorMessage) { + if (errorMessage == null) errorMessage = ""; + if (!Grammar.ERROR.matcher(errorMessage).matches()) { + return new FayeClientError(null, null, errorMessage); + } + + final List parts = Arrays.asList(errorMessage.split(":")); + final Integer code = Integer.parseInt(parts.get(0)); + final List params = Arrays.asList(parts.get(1).split(",")); + final String message = parts.get(2); + + return new FayeClientError(code, params, message); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FayeClientError that = (FayeClientError) o; + return Objects.equals(code, that.code) + && Objects.equals(params, that.params) + && Objects.equals(errorMessage, that.errorMessage); + } + + @Override + public int hashCode() { + return Objects.hash(code, params, errorMessage); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .omitNullValues() + .add("code", this.code) + .add("params", this.params) + .add("errorMessage", this.errorMessage) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/faye/Grammar.java b/src/main/java/io/getstream/core/faye/Grammar.java new file mode 100644 index 00000000..1c850ca0 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/Grammar.java @@ -0,0 +1,18 @@ +package io.getstream.core.faye; + +import java.util.regex.Pattern; + +public class Grammar { + static final Pattern CHANNEL_NAME = + Pattern.compile( + "^\\/(((([a-z]|[A-Z])|[0-9])|(\\-|\\_|\\!|\\~|\\(|\\)|\\$|\\@)))+(\\/(((([a-z]|[A-Z])|[0-9])|(\\-|\\_|\\!|\\~|\\(|\\)|\\$|\\@)))+)*$", + Pattern.MULTILINE); + static final Pattern CHANNEL_PATTERN = + Pattern.compile( + "^(\\/(((([a-z]|[A-Z])|[0-9])|(\\-|\\_|\\!|\\~|\\(|\\)|\\$|\\@)))+)*\\/\\*{1,2}$"); + static final Pattern ERROR = + Pattern.compile( + "^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\\-|\\_|\\!|\\~|\\(|\\)|\\$|\\@)| |\\/|\\*|\\.))*(,(((([a-z]|[A-Z])|[0-9])|(\\-|\\_|\\!|\\~|\\(|\\)|\\$|\\@)| |\\/|\\*|\\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\\-|\\_|\\!|\\~|\\(|\\)|\\$|\\@)| |\\/|\\*|\\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\\-|\\_|\\!|\\~|\\(|\\)|\\$|\\@)| |\\/|\\*|\\.))*)$"); + static final Pattern VERSION = + Pattern.compile("^([0-9])+(\\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\\-|\\_))*)*$"); +} diff --git a/src/main/java/io/getstream/core/faye/Message.java b/src/main/java/io/getstream/core/faye/Message.java new file mode 100644 index 00000000..085df34c --- /dev/null +++ b/src/main/java/io/getstream/core/faye/Message.java @@ -0,0 +1,168 @@ +package io.getstream.core.faye; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.google.common.base.MoreObjects; +import java.util.Map; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Message { + private String id; + private final String channel; + private String clientId; + private String connectionType; + private String version; + private String minimumVersion; + private String[] supportedConnectionTypes; + private Advice advice; + private Boolean successful; + private String subscription; + private Map data; + private Map ext; + private String error; + + // for deserialization + public Message() { + this.channel = null; + } + + public Message(String channel) { + this.channel = channel; + } + + public void setId(String id) { + this.id = id; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public void setConnectionType(String connectionType) { + this.connectionType = connectionType; + } + + public void setVersion(String version) { + this.version = version; + } + + public void setMinimumVersion(String minimumVersion) { + this.minimumVersion = minimumVersion; + } + + public void setSupportedConnectionTypes(String[] supportedConnectionTypes) { + this.supportedConnectionTypes = supportedConnectionTypes; + } + + public void setAdvice(Advice advice) { + this.advice = advice; + } + + public void setSuccessful(Boolean successful) { + this.successful = successful; + } + + public void setSubscription(String subscription) { + this.subscription = subscription; + } + + public void setData(Map data) { + this.data = data; + } + + public void setExt(Map ext) { + this.ext = ext; + } + + public void setError(String error) { + this.error = error; + } + + public String getId() { + return id; + } + + public String getChannel() { + return channel; + } + + public String getClientId() { + return clientId; + } + + public String getConnectionType() { + return connectionType; + } + + public String getVersion() { + return version; + } + + public String getMinimumVersion() { + return minimumVersion; + } + + public String[] getSupportedConnectionTypes() { + return supportedConnectionTypes; + } + + public Advice getAdvice() { + return advice; + } + + public Boolean isSuccessful() { + return successful; + } + + public String getSubscription() { + return subscription; + } + + public Map getData() { + return data; + } + + public Map getExt() { + return ext; + } + + public String getError() { + return error; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Message that = (Message) o; + return Objects.equals(id, that.id) + && Objects.equals(channel, that.channel) + && Objects.equals(clientId, that.clientId); + } + + @Override + public int hashCode() { + return Objects.hash(id, channel, clientId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .omitNullValues() + .add("id", this.id) + .add("channel", this.channel) + .add("clientId", this.clientId) + .add("connectionType", this.connectionType) + .add("version", this.version) + .add("minimumVersion", this.minimumVersion) + .add("supportedConnectionTypes", this.supportedConnectionTypes) + .add("advice", this.advice) + .add("successful", this.successful) + .add("data", this.data) + .add("ext", this.ext) + .add("error", this.error) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/faye/MessageTransformer.java b/src/main/java/io/getstream/core/faye/MessageTransformer.java new file mode 100644 index 00000000..c57c17b4 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/MessageTransformer.java @@ -0,0 +1,7 @@ +package io.getstream.core.faye; + +public abstract class MessageTransformer { + public abstract Message transformRequest(Message message); + + public abstract Message transformResponse(Message message); +} diff --git a/src/main/java/io/getstream/core/faye/client/Callback.java b/src/main/java/io/getstream/core/faye/client/Callback.java new file mode 100644 index 00000000..51d9fb06 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/client/Callback.java @@ -0,0 +1,5 @@ +package io.getstream.core.faye.client; + +interface Callback { + void call(); +} diff --git a/src/main/java/io/getstream/core/faye/client/FayeClient.java b/src/main/java/io/getstream/core/faye/client/FayeClient.java new file mode 100644 index 00000000..caf58572 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/client/FayeClient.java @@ -0,0 +1,455 @@ +package io.getstream.core.faye.client; + +import io.getstream.core.faye.Advice; +import io.getstream.core.faye.Channel; +import io.getstream.core.faye.DefaultMessageTransformer; +import io.getstream.core.faye.FayeClientError; +import io.getstream.core.faye.Message; +import io.getstream.core.faye.MessageTransformer; +import io.getstream.core.faye.subscription.ChannelDataCallback; +import io.getstream.core.faye.subscription.ChannelSubscription; +import io.getstream.core.faye.subscription.SubscriptionCancelledCallback; +import io.getstream.core.utils.Serialization; +import java.io.IOException; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CompletableFuture; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; + +public class FayeClient extends WebSocketListener { + + private static final String BAYEUX_VERSION = "1.0"; + private static final int DEFAULT_TIMEOUT = 60; // seconds + private static final int DEFAULT_INTERVAL = 0; // seconds + + private final String baseURL; + private final int timeout; + private final int interval; + + private Advice advice; + + public FayeClient(URL baseURL) { + String url = baseURL.toString(); + if (url.startsWith("https")) { + url = url.replace("https", "wss"); + } else if (url.startsWith("http")) { + url = url.replace("http", "ws"); + } + + this.baseURL = url; + this.timeout = DEFAULT_TIMEOUT; + this.interval = DEFAULT_INTERVAL; + this.advice = new Advice(Advice.RETRY, 1000 * interval, 1000 * timeout); + } + + private String clientId; + private final Map channels = new HashMap<>(); + private final Map responseCallbacks = new HashMap<>(); + + private MessageTransformer messageTransformer = new DefaultMessageTransformer(); + + public void setMessageTransformer(MessageTransformer messageTransformer) { + this.messageTransformer = messageTransformer; + } + + private FayeClientState state = FayeClientState.UNCONNECTED; + + private void setState(FayeClientState state) { + this.state = state; + if (stateChangeListener != null) stateChangeListener.onStateChanged(state); + } + + private StateChangeListener stateChangeListener; + + public void setStateChangeListener(StateChangeListener stateChangeListener) { + this.stateChangeListener = stateChangeListener; + } + + private WebSocket webSocket; + private final OkHttpClient httpClient = new OkHttpClient(); + + private Timer timer = new Timer(); + + private void initWebSocket() { + // Initiating connection with $baseUrl + if (webSocket != null) { + closeWebSocket(); + } + final Request request = new Request.Builder().url(baseURL).build(); + webSocket = httpClient.newWebSocket(request, this); + } + + private void closeWebSocket() { + // Cancelling all timer tasks + if (timer != null) { + timer.cancel(); + timer = null; + } + + // Closing connection for $baseUrl + if (webSocket != null) { + webSocket.close(1000, "Connection closed by client"); + webSocket = null; + } + } + + @Override + public void onMessage(WebSocket webSocket, String text) { + List messages = null; + try { + messages = Serialization.fromJSONList(text, Message.class); + } catch (IOException ignored) { + } + + if (messages == null) return; + + for (Message message : messages) { + receiveMessage(message); + } + } + + @Override + public void onFailure(WebSocket webSocket, Throwable t, Response response) { + // 'Error occurred', error, stacktrace); + closeWebSocket(); + initWebSocket(); + } + + private boolean manuallyClosed = false; + + @Override + public void onClosed(WebSocket webSocket, int code, String reason) { + closeWebSocket(); + + // Checking if we manually closed the connection + if (manuallyClosed) return; + initWebSocket(); + } + + private void scheduleTimerTask(Callback callback, long duration) { + if (timer == null) timer = new Timer(); + timer.schedule( + new TimerTask() { + @Override + public void run() { + callback.call(); + } + }, + duration); + } + + public void handshake() { + handshake(null); + } + + private void handshake(Callback callback) { + if (Objects.equals(advice.getReconnect(), Advice.NONE)) return; + if (state != FayeClientState.UNCONNECTED) return; + + setState(FayeClientState.CONNECTING); + + initWebSocket(); + + // Initiating handshake with $baseUrl + + final String[] connectionTypes = {"websocket"}; + final Message message = new Message(Channel.HANDSHAKE); + message.setVersion(BAYEUX_VERSION); + message.setSupportedConnectionTypes(connectionTypes); + sendMessage( + message, + response -> { + if (response.isSuccessful() != null && response.isSuccessful()) { + setState(FayeClientState.CONNECTED); + clientId = response.getClientId(); + + // Handshake successful: $clientId + final Set keys = channels.keySet(); + subscribeChannels(keys.toArray(new String[0])); + if (callback != null) callback.call(); + } else { + // Handshake unsuccessful + scheduleTimerTask(() -> handshake(callback), 1000); + setState(FayeClientState.UNCONNECTED); + } + }); + } + + private boolean connectRequestInProgress = false; + + public void connect() { + connect(null); + } + + private void connect(Callback callback) { + if (Objects.equals(advice.getReconnect(), Advice.NONE)) return; + if (state == FayeClientState.DISCONNECTED) return; + + if (state == FayeClientState.UNCONNECTED) { + handshake(() -> connect(callback)); + return; + } + + if (callback != null) callback.call(); + if (state != FayeClientState.CONNECTED) return; + + if (connectRequestInProgress) return; + connectRequestInProgress = true; + + // Initiating connection for $clientId + + final Message message = new Message(Channel.CONNECT); + message.setClientId(clientId); + message.setConnectionType("websocket"); + sendMessage(message, response -> cycleConnection()); + } + + public CompletableFuture disconnect() { + final CompletableFuture disconnectionCompleter = new CompletableFuture<>(); + + if (state != FayeClientState.CONNECTED) disconnectionCompleter.complete(null); + setState(FayeClientState.DISCONNECTED); + + // Disconnecting $clientId + + final Message message = new Message(Channel.DISCONNECT); + message.setClientId(clientId); + sendMessage( + message, + response -> { + if (response.isSuccessful() != null && response.isSuccessful()) { + manuallyClosed = true; + closeWebSocket(); + disconnectionCompleter.complete(null); + } else { + final FayeClientError error = FayeClientError.parse(response.getError()); + disconnectionCompleter.completeExceptionally(error); + } + }); + + // Clearing channel listeners for $clientId + channels.clear(); + + return disconnectionCompleter; + } + + private void subscribeChannels(String[] channels) { + for (String channel : channels) { + subscribe(channel, true); + } + } + + public CompletableFuture subscribe( + String channel, ChannelDataCallback callback) { + return subscribe(channel, callback, null, null); + } + + private CompletableFuture subscribe(String channel, Boolean force) { + return subscribe(channel, null, null, force); + } + + public CompletableFuture subscribe( + String channel, ChannelDataCallback callback, SubscriptionCancelledCallback onCancelled) { + return subscribe(channel, callback, onCancelled, null); + } + + private CompletableFuture subscribe( + String channel, + ChannelDataCallback onData, + SubscriptionCancelledCallback onCancelled, + Boolean force) { + // default value + if (force == null) force = false; + + final CompletableFuture subscriptionCompleter = new CompletableFuture<>(); + + final ChannelSubscription channelSubscription = + new ChannelSubscription(this, channel, onData, onCancelled); + final boolean hasSubscribe = channels.containsKey(channel); + + if (hasSubscribe && !force) { + subscribeChannel(channel, channelSubscription); + subscriptionCompleter.complete(channelSubscription); + } else { + Boolean finalForce = force; + connect( + () -> { + // Client $clientId attempting to subscribe to $channel + if (!finalForce) subscribeChannel(channel, channelSubscription); + final Message message = new Message(Channel.SUBSCRIBE); + message.setClientId(clientId); + message.setSubscription(channel); + sendMessage( + message, + response -> { + if (response.isSuccessful() != null && response.isSuccessful()) { + final String subscribedChannel = response.getSubscription(); + // Subscription acknowledged for $channel to $clientId + subscriptionCompleter.complete(channelSubscription); + } else { + unsubscribeChannel(channel, channelSubscription); + final FayeClientError error = FayeClientError.parse(response.getError()); + subscriptionCompleter.completeExceptionally(error); + } + }); + }); + } + + return subscriptionCompleter; + } + + public void unsubscribe(String channel, ChannelSubscription channelSubscription) { + final boolean dead = unsubscribeChannel(channel, channelSubscription); + if (!dead) return; + + connect( + () -> { + // Client $clientId attempting to unsubscribe from $channel + final Message message = new Message(Channel.UNSUBSCRIBE); + message.setClientId(clientId); + message.setSubscription(channel); + sendMessage( + message, + response -> { + if (response.isSuccessful() != null && response.isSuccessful()) { + final String unsubscribedChannel = response.getSubscription(); + // Un-subscription acknowledged for $clientId from $channel + } + }); + }); + } + + public CompletableFuture publish(String channel, Map data) { + final CompletableFuture publishCompleter = new CompletableFuture<>(); + + connect( + () -> { + // Client $clientId queuing published message to $channel: $data + final Message message = new Message(channel); + message.setData(data); + message.setClientId(clientId); + sendMessage( + message, + response -> { + if (response.isSuccessful() != null && response.isSuccessful()) { + publishCompleter.complete(null); + } else { + final FayeClientError error = FayeClientError.parse(response.getError()); + publishCompleter.completeExceptionally(error); + } + }); + }); + + return publishCompleter; + } + + private final String EVENT_MESSAGE = "message"; + + private void subscribeChannel(String name, ChannelSubscription channelSubscription) { + Channel channel; + if (channels.containsKey(name)) { + channel = channels.get(name); + } else { + channel = new Channel(name); + channels.put(name, channel); + } + channel.bind(EVENT_MESSAGE, channelSubscription::call); + } + + private boolean unsubscribeChannel(String name, ChannelSubscription channelSubscription) { + final Channel channel = channels.get(name); + if (channel == null) return false; + channel.unbind(EVENT_MESSAGE, channelSubscription::call); + try { + if (channel.hasListeners(EVENT_MESSAGE)) { + channels.remove(name); + return true; + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + private void distributeChannelMessage(Message message) { + final List expandedChannels = Channel.expand(message.getChannel()); + if (expandedChannels == null) return; + for (String c : expandedChannels) { + final Channel channel = this.channels.get(c); + if (channel != null) channel.trigger(EVENT_MESSAGE, message); + } + } + + private int messageId = 0; + + private String generateMessageId() { + messageId += 1; + if (messageId >= Math.pow(2, 32)) messageId = 0; + return Integer.toString(messageId, 36); + } + + private void sendMessage(Message message) { + sendMessage(message, null); + } + + private void sendMessage(Message message, MessageCallback onResponse) { + final String id = generateMessageId(); + message.setId(id); + message = messageTransformer.transformRequest(message); + // Sending Message: $message + if (onResponse != null) responseCallbacks.put(id, onResponse); + try { + final byte[] payload = Serialization.toJSON(message); + webSocket.send(new String(payload)); + } catch (Exception ignored) { + + } + } + + private void receiveMessage(Message message) { + final String id = message.getId(); + MessageCallback callback = null; + if (message.isSuccessful() != null) { + callback = responseCallbacks.remove(id); + } + message = messageTransformer.transformResponse(message); + // Received message: $message + if (message.getAdvice() != null) handleAdvice(message.getAdvice()); + deliverMessage(message); + if (callback != null) callback.onMessage(message); + } + + private void handleAdvice(Advice advice) { + this.advice = advice; + if (Objects.equals(advice.getReconnect(), Advice.HANDSHAKE) + && state != FayeClientState.DISCONNECTED) { + setState(FayeClientState.UNCONNECTED); + clientId = null; + cycleConnection(); + } + } + + private void deliverMessage(Message message) { + if (message.getChannel() == null || message.getData() == null) return; + // Client $clientId calling listeners for ${message.channel} with ${message.data} + distributeChannelMessage(message); + } + + private void cycleConnection() { + if (connectRequestInProgress) { + connectRequestInProgress = false; + // Closed connection for $clientId + } + scheduleTimerTask(this::connect, advice.getInterval()); + } +} diff --git a/src/main/java/io/getstream/core/faye/client/FayeClientState.java b/src/main/java/io/getstream/core/faye/client/FayeClientState.java new file mode 100644 index 00000000..05bb98be --- /dev/null +++ b/src/main/java/io/getstream/core/faye/client/FayeClientState.java @@ -0,0 +1,8 @@ +package io.getstream.core.faye.client; + +public enum FayeClientState { + UNCONNECTED, + CONNECTING, + CONNECTED, + DISCONNECTED, +} diff --git a/src/main/java/io/getstream/core/faye/client/MessageCallback.java b/src/main/java/io/getstream/core/faye/client/MessageCallback.java new file mode 100644 index 00000000..f6c8714d --- /dev/null +++ b/src/main/java/io/getstream/core/faye/client/MessageCallback.java @@ -0,0 +1,7 @@ +package io.getstream.core.faye.client; + +import io.getstream.core.faye.Message; + +interface MessageCallback { + void onMessage(Message message); +} diff --git a/src/main/java/io/getstream/core/faye/client/StateChangeListener.java b/src/main/java/io/getstream/core/faye/client/StateChangeListener.java new file mode 100644 index 00000000..127e3f3f --- /dev/null +++ b/src/main/java/io/getstream/core/faye/client/StateChangeListener.java @@ -0,0 +1,5 @@ +package io.getstream.core.faye.client; + +public interface StateChangeListener { + void onStateChanged(FayeClientState clientState); +} diff --git a/src/main/java/io/getstream/core/faye/emitter/ErrorListener.java b/src/main/java/io/getstream/core/faye/emitter/ErrorListener.java new file mode 100644 index 00000000..f7d74b1e --- /dev/null +++ b/src/main/java/io/getstream/core/faye/emitter/ErrorListener.java @@ -0,0 +1,5 @@ +package io.getstream.core.faye.emitter; + +public interface ErrorListener { + void onError(Exception error); +} diff --git a/src/main/java/io/getstream/core/faye/emitter/EventEmitter.java b/src/main/java/io/getstream/core/faye/emitter/EventEmitter.java new file mode 100644 index 00000000..3ca17328 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/emitter/EventEmitter.java @@ -0,0 +1,120 @@ +package io.getstream.core.faye.emitter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class EventEmitter { + + private final Map>> events = new HashMap<>(); + + private ErrorListener errorListener; + + public void setErrorListener(ErrorListener errorListener) { + this.errorListener = errorListener; + } + + private boolean mounted = true; + + public boolean isMounted() { + return mounted; + } + + private void assertMounted() { + assert isMounted() + : "Tried to use " + + this.getClass().getSimpleName() + + " after `dispose` was called. Consider checking `isMounted`"; + } + + public void emit(String event, T data) { + assertMounted(); + final LinkedList> listeners = events.get(event); + if (listeners == null) return; + boolean didThrow = false; + final List> removables = new ArrayList<>(); + for (ListenerEntry entry : listeners) { + try { + entry.getListener().onData(data); + Integer limit = entry.getLimit(); + if (limit != null) { + if (limit > 0) { + limit -= 1; + entry.setLimit(limit); + } + if (limit == 0) { + removables.add(entry); + } + } + } catch (Exception e) { + didThrow = true; + if (errorListener != null) { + errorListener.onError(e); + } + } + } + for (ListenerEntry entry : removables) { + listeners.remove(entry); + } + if (didThrow) throw new Error(); + } + + public void on(String event, EventListener listener) { + addListener(event, listener); + } + + public void on(String event, EventListener listener, int limit) { + addListener(event, listener, limit); + } + + public void addListener(String event, EventListener listener) { + _addListener(event, listener, null); + } + + public void addListener(String event, EventListener listener, int limit) { + _addListener(event, listener, limit); + } + + void _addListener(String event, EventListener listener, Integer limit) { + assertMounted(); + final ListenerEntry entry = new ListenerEntry(listener, limit); + LinkedList> listeners = events.get(event); + if (listeners == null) listeners = new LinkedList<>(); + listeners.add(entry); + events.put(event, listeners); + } + + public void off(String event) { + assertMounted(); + events.put(event, new LinkedList<>()); + } + + public void removeListener(String event, EventListener listener) { + assertMounted(); + final LinkedList> listeners = events.get(event); + if (listeners == null) return; + listeners.removeIf(curr -> curr.getListener() == listener); + } + + public void removeAllListeners() { + assertMounted(); + events.clear(); + } + + public boolean hasListeners(String event) throws Exception { + assertMounted(); + final LinkedList> listeners = events.get(event); + if (listeners == null) { + throw new Exception("Event not available"); + } + return !listeners.isEmpty(); + } + + public void dispose() { + assertMounted(); + events.values().forEach(LinkedList::clear); + mounted = false; + } +} diff --git a/src/main/java/io/getstream/core/faye/emitter/EventListener.java b/src/main/java/io/getstream/core/faye/emitter/EventListener.java new file mode 100644 index 00000000..c9763660 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/emitter/EventListener.java @@ -0,0 +1,5 @@ +package io.getstream.core.faye.emitter; + +public interface EventListener { + void onData(T data); +} diff --git a/src/main/java/io/getstream/core/faye/emitter/ListenerEntry.java b/src/main/java/io/getstream/core/faye/emitter/ListenerEntry.java new file mode 100644 index 00000000..90d28d6a --- /dev/null +++ b/src/main/java/io/getstream/core/faye/emitter/ListenerEntry.java @@ -0,0 +1,24 @@ +package io.getstream.core.faye.emitter; + +class ListenerEntry { + + public ListenerEntry(EventListener listener, Integer limit) { + this.listener = listener; + this.limit = limit; + } + + private Integer limit; + private final EventListener listener; + + public Integer getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public EventListener getListener() { + return listener; + } +} diff --git a/src/main/java/io/getstream/core/faye/subscription/ChannelDataCallback.java b/src/main/java/io/getstream/core/faye/subscription/ChannelDataCallback.java new file mode 100644 index 00000000..80b415d6 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/subscription/ChannelDataCallback.java @@ -0,0 +1,7 @@ +package io.getstream.core.faye.subscription; + +import java.util.Map; + +public interface ChannelDataCallback { + void onData(Map data); +} diff --git a/src/main/java/io/getstream/core/faye/subscription/ChannelSubscription.java b/src/main/java/io/getstream/core/faye/subscription/ChannelSubscription.java new file mode 100644 index 00000000..a58ec8ba --- /dev/null +++ b/src/main/java/io/getstream/core/faye/subscription/ChannelSubscription.java @@ -0,0 +1,49 @@ +package io.getstream.core.faye.subscription; + +import io.getstream.core.faye.Message; +import io.getstream.core.faye.client.FayeClient; + +public class ChannelSubscription { + private final FayeClient client; + private final String channel; + private final ChannelDataCallback channelDataCallback; + private final SubscriptionCancelledCallback onCancelledCallback; + private WithChannelDataCallback withChannel; + + private boolean cancelled = false; + + public ChannelSubscription(FayeClient client, String channel) { + this.client = client; + this.channel = channel; + this.channelDataCallback = null; + this.onCancelledCallback = null; + } + + public ChannelSubscription( + FayeClient client, + String channel, + ChannelDataCallback channelDataCallback, + SubscriptionCancelledCallback onCancelledCallback) { + this.client = client; + this.channel = channel; + this.channelDataCallback = channelDataCallback; + this.onCancelledCallback = onCancelledCallback; + } + + public ChannelSubscription setWithChannel(WithChannelDataCallback withChannel) { + this.withChannel = withChannel; + return this; + } + + public void call(Message message) { + if (channelDataCallback != null) channelDataCallback.onData(message.getData()); + if (withChannel != null) withChannel.onData(message.getChannel(), message.getData()); + } + + public void cancel() { + if (cancelled) return; + client.unsubscribe(channel, this); + if (onCancelledCallback != null) onCancelledCallback.onCancelled(); + cancelled = true; + } +} diff --git a/src/main/java/io/getstream/core/faye/subscription/SubscriptionCancelledCallback.java b/src/main/java/io/getstream/core/faye/subscription/SubscriptionCancelledCallback.java new file mode 100644 index 00000000..c8e60924 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/subscription/SubscriptionCancelledCallback.java @@ -0,0 +1,5 @@ +package io.getstream.core.faye.subscription; + +public interface SubscriptionCancelledCallback { + void onCancelled(); +} diff --git a/src/main/java/io/getstream/core/faye/subscription/WithChannelDataCallback.java b/src/main/java/io/getstream/core/faye/subscription/WithChannelDataCallback.java new file mode 100644 index 00000000..ec140c15 --- /dev/null +++ b/src/main/java/io/getstream/core/faye/subscription/WithChannelDataCallback.java @@ -0,0 +1,7 @@ +package io.getstream.core.faye.subscription; + +import java.util.Map; + +public interface WithChannelDataCallback { + void onData(String channel, Map data); +} diff --git a/src/main/java/io/getstream/core/http/HTTPClient.java b/src/main/java/io/getstream/core/http/HTTPClient.java new file mode 100644 index 00000000..b120065c --- /dev/null +++ b/src/main/java/io/getstream/core/http/HTTPClient.java @@ -0,0 +1,13 @@ +package io.getstream.core.http; + +import java8.util.concurrent.CompletableFuture; + +public abstract class HTTPClient { + public static Request.Builder requestBuilder() { + return Request.builder(); + } + + public abstract T getImplementation(); + + public abstract CompletableFuture execute(Request request); +} diff --git a/src/main/java/io/getstream/core/http/OKHTTPClientAdapter.java b/src/main/java/io/getstream/core/http/OKHTTPClientAdapter.java new file mode 100644 index 00000000..4ed770a8 --- /dev/null +++ b/src/main/java/io/getstream/core/http/OKHTTPClientAdapter.java @@ -0,0 +1,120 @@ +package io.getstream.core.http; + +import static com.google.common.base.Preconditions.checkNotNull; + +import io.getstream.core.utils.Info; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; +import java8.util.concurrent.CompletableFuture; +import okhttp3.*; + +public final class OKHTTPClientAdapter extends HTTPClient { + private static final String userAgentTemplate = "okhttp3 stream-java2 %s v%s"; + + private final OkHttpClient client; + + public OKHTTPClientAdapter() { + this.client = + new OkHttpClient.Builder().followRedirects(false).followSslRedirects(false).build(); + } + + public OKHTTPClientAdapter(OkHttpClient client) { + checkNotNull(client); + this.client = client; + } + + @Override + public T getImplementation() { + return (T) client; + } + + private okhttp3.RequestBody buildOkHttpRequestBody(io.getstream.core.http.RequestBody body) { + okhttp3.RequestBody okBody = null; + MediaType mediaType; + switch (body.getType()) { + case JSON: + mediaType = MediaType.parse(body.getType().toString()); + okBody = okhttp3.RequestBody.create(mediaType, body.getBytes()); + break; + case MULTI_PART: + String mimeType = URLConnection.guessContentTypeFromName(body.getFileName()); + mediaType = MediaType.parse(mimeType); + MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM); + if (body.getBytes() != null) { + builder.addFormDataPart( + "file", body.getFileName(), okhttp3.RequestBody.create(mediaType, body.getBytes())); + } else { + builder.addFormDataPart( + "file", body.getFileName(), okhttp3.RequestBody.create(mediaType, body.getFile())); + } + okBody = builder.build(); + break; + } + return okBody; + } + + private okhttp3.Request buildOkHttpRequest(io.getstream.core.http.Request request) { + String version = Info.getProperties().getProperty(Info.VERSION); + String userAgent = String.format(userAgentTemplate, System.getProperty("os.name"), version); + okhttp3.Request.Builder builder = + new okhttp3.Request.Builder() + .url(request.getURL()) + .addHeader("Stream-Auth-Type", "jwt") + .addHeader("Authorization", request.getToken().toString()) + .addHeader("User-Agent", userAgent) + .addHeader("X-Stream-Client", "stream-java-" + version); + + MediaType mediaType; + switch (request.getMethod()) { + case GET: + builder.get(); + break; + case DELETE: + builder.delete(); + break; + case PUT: + builder.put(buildOkHttpRequestBody(request.getBody())); + break; + case POST: + builder.post(buildOkHttpRequestBody(request.getBody())); + break; + } + return builder.build(); + } + + private io.getstream.core.http.Response buildResponse(okhttp3.Response response) { + final InputStream body = response.body() != null ? response.body().byteStream() : null; + return new io.getstream.core.http.Response(response.code(), body); + } + + @Override + public CompletableFuture execute( + io.getstream.core.http.Request request) { + final CompletableFuture result = new CompletableFuture<>(); + + client + .newCall(buildOkHttpRequest(request)) + .enqueue( + new Callback() { + @Override + public void onFailure(Call call, IOException e) { + result.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, okhttp3.Response response) { + try { + io.getstream.core.http.Response httpResponse = buildResponse(response); + result.complete(httpResponse); + } catch (Exception e) { + result.completeExceptionally(e); + } finally { + response.body().close(); + } + } + }); + + return result; + } +} diff --git a/src/main/java/io/getstream/core/http/Request.java b/src/main/java/io/getstream/core/http/Request.java new file mode 100644 index 00000000..b5b6d40d --- /dev/null +++ b/src/main/java/io/getstream/core/http/Request.java @@ -0,0 +1,158 @@ +package io.getstream.core.http; + +import com.google.common.base.MoreObjects; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Objects; + +public final class Request { + private final Token token; + private final URL url; + private final Method method; + private final RequestBody body; + + private Request(Builder builder) throws MalformedURLException { + token = builder.token; + url = builder.uri.toURL(); + method = builder.method; + body = builder.body; + } + + public Token getToken() { + return token; + } + + public URL getURL() { + return url; + } + + public Method getMethod() { + return method; + } + + public RequestBody getBody() { + return body; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return Objects.equals(token, request.token) + && Objects.equals(url, request.url) + && method == request.method + && Objects.equals(body, request.body); + } + + @Override + public int hashCode() { + return Objects.hash(token, url, method, body); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("token", this.token) + .add("url", this.url) + .add("method", this.method) + .add("body", this.body) + .toString(); + } + + public static Builder builder() { + return new Builder(); + } + + public enum Method { + GET, + POST, + PUT, + DELETE + } + + public static final class Builder { + private Token token; + private URI uri; + private StringBuilder query; + private Method method; + private RequestBody body; + + public Builder token(Token token) { + this.token = token; + return this; + } + + public Builder url(URL url) throws URISyntaxException { + uri = url.toURI(); + if (uri.getQuery() != null) { + query = new StringBuilder(uri.getQuery()); + } else { + query = new StringBuilder(); + } + return this; + } + + public Builder addQueryParameter(String key, String value) { + if (query.length() > 0) { + query.append('&'); + } + query.append(key); + query.append('='); + query.append(value); + return this; + } + + public Builder get() { + this.method = Method.GET; + this.body = null; + return this; + } + + public Builder post(byte[] body) { + this.method = Method.POST; + this.body = new RequestBody(body, RequestBody.Type.JSON); + return this; + } + + public Builder multiPartPost(String fileName, byte[] body) { + this.method = Method.POST; + this.body = new RequestBody(fileName, body, RequestBody.Type.MULTI_PART); + return this; + } + + public Builder multiPartPost(File body) { + this.method = Method.POST; + this.body = new RequestBody(body, RequestBody.Type.MULTI_PART); + return this; + } + + public Builder put(byte[] body) { + this.method = Method.PUT; + this.body = new RequestBody(body, RequestBody.Type.JSON); + return this; + } + + public Builder delete() { + this.method = Method.DELETE; + this.body = null; + return this; + } + + public Request build() throws MalformedURLException, URISyntaxException { + this.uri = + new URI( + uri.getScheme(), + uri.getUserInfo(), + uri.getHost(), + uri.getPort(), + uri.getPath(), + query.toString(), + null); + return new Request(this); + } + } +} diff --git a/src/main/java/io/getstream/core/http/RequestBody.java b/src/main/java/io/getstream/core/http/RequestBody.java new file mode 100644 index 00000000..2ea521f5 --- /dev/null +++ b/src/main/java/io/getstream/core/http/RequestBody.java @@ -0,0 +1,90 @@ +package io.getstream.core.http; + +import com.google.common.base.MoreObjects; +import java.io.File; +import java.util.Arrays; +import java.util.Objects; + +public final class RequestBody { + public enum Type { + JSON("application/json"), + MULTI_PART("multipart/form-data"); + + private final String type; + + Type(String type) { + this.type = type; + } + + @Override + public String toString() { + return type; + } + } + + private final Type type; + private final byte[] bytes; + private final File file; + private final String fileName; + + RequestBody(byte[] bytes, Type type) { + this.type = type; + this.bytes = bytes; + this.file = null; + this.fileName = null; + } + + RequestBody(String fileName, byte[] bytes, Type type) { + this.type = type; + this.bytes = bytes; + this.file = null; + this.fileName = fileName; + } + + RequestBody(File file, Type type) { + this.type = type; + this.bytes = null; + this.file = file; + this.fileName = file.getName(); + } + + public Type getType() { + return type; + } + + public byte[] getBytes() { + return bytes; + } + + public File getFile() { + return file; + } + + public String getFileName() { + return fileName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RequestBody that = (RequestBody) o; + return type == that.type && Arrays.equals(bytes, that.bytes) && Objects.equals(file, that.file); + } + + @Override + public int hashCode() { + int result = Objects.hash(type, file); + result = 31 * result + Arrays.hashCode(bytes); + return result; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(RequestBody.class) + .add("type", type) + .add("bytes", bytes) + .add("file", file) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/http/Response.java b/src/main/java/io/getstream/core/http/Response.java new file mode 100644 index 00000000..35fbe8bb --- /dev/null +++ b/src/main/java/io/getstream/core/http/Response.java @@ -0,0 +1,47 @@ +package io.getstream.core.http; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.MoreObjects; +import java.io.InputStream; +import java.util.Objects; + +public final class Response { + private final int code; + private final InputStream body; + + public Response(int code, InputStream body) { + checkArgument(code >= 100 && code <= 599, "Invalid HTTP status code"); + this.code = code; + this.body = body; + } + + public int getCode() { + return code; + } + + public InputStream getBody() { + return body; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Response response = (Response) o; + return code == response.code && Objects.equals(body, response.body); + } + + @Override + public int hashCode() { + return Objects.hash(code, body); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("code", this.code) + .add("body", this.body) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/http/Token.java b/src/main/java/io/getstream/core/http/Token.java new file mode 100644 index 00000000..8f8db8e8 --- /dev/null +++ b/src/main/java/io/getstream/core/http/Token.java @@ -0,0 +1,35 @@ +package io.getstream.core.http; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Objects; + +public final class Token { + private final String token; + + public Token(String token) { + checkNotNull(token, "Token can't be null"); + checkArgument(!token.isEmpty(), "Token can't be null"); + + this.token = token; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Token token1 = (Token) o; + return Objects.equals(token, token1.token); + } + + @Override + public int hashCode() { + return Objects.hash(token); + } + + @Override + public String toString() { + return token; + } +} diff --git a/src/main/java/io/getstream/core/models/APIError.java b/src/main/java/io/getstream/core/models/APIError.java new file mode 100644 index 00000000..c21e2871 --- /dev/null +++ b/src/main/java/io/getstream/core/models/APIError.java @@ -0,0 +1,37 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class APIError { + private String Code; + private String Message; + private String Status; + + public String toString() { + return "{Code='" + Code + "', Message=" + Message + "}"; + } + + // Default constructor + public APIError() {} + + // Constructor with parameters + @JsonCreator + public APIError( + @JsonProperty("code") String code, + @JsonProperty("message") String message, + @JsonProperty("status") String status) { + this.Code = code; + this.Message = message; + this.Status = status; + } + + // Getters + public String getCode() { + return Code; + } + + public String getMessage() { + return Message; + } +} diff --git a/src/main/java/io/getstream/core/models/Activity.java b/src/main/java/io/getstream/core/models/Activity.java new file mode 100644 index 00000000..1a41127b --- /dev/null +++ b/src/main/java/io/getstream/core/models/Activity.java @@ -0,0 +1,299 @@ +package io.getstream.core.models; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import io.getstream.core.models.serialization.DateDeserializer; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@JsonInclude(Include.NON_NULL) +@JsonDeserialize(builder = Activity.Builder.class) +public class Activity { + + private final String id; + private final String actor; + private final String verb; + private final String object; + private final String foreignID; + private final String target; + // TODO: support Java 8 Date/Time types? + private final Date time; + private final String origin; + private final List to; + private final Double score; + private final Map extra; + private final String moderationTemplate; + private final ModerationResponse moderationResponse; + + private Activity(Builder builder) { + id = builder.id; + actor = builder.actor; + verb = builder.verb; + object = builder.object; + foreignID = builder.foreignID; + target = builder.target; + time = builder.time; + origin = builder.origin; + to = builder.to; + score = builder.score; + extra = builder.extra; + moderationTemplate = builder.moderationTemplate; + moderationResponse = builder.moderationResponse; + } + + public String getID() { + return id; + } + + public String getActor() { + return actor; + } + + public String getVerb() { + return verb; + } + + public String getObject() { + return object; + } + + @JsonProperty("foreign_id") + public String getForeignID() { + return foreignID; + } + + public String getTarget() { + return target; + } + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", + lenient = OptBoolean.FALSE, + timezone = "UTC") + public Date getTime() { + return time; + } + + public String getOrigin() { + return origin; + } + + public List getTo() { + return to; + } + + public Double getScore() { + return score; + } + + @JsonAnyGetter + public Map getExtra() { + return extra; + } + + @JsonProperty("moderation") + public ModerationResponse getModerationResponse() { + return moderationResponse; + } + + @JsonProperty("moderation_template") + public String getModerationTemplate() { + return moderationTemplate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Activity activity = (Activity) o; + return Objects.equals(id, activity.id) + && Objects.equals(actor, activity.actor) + && Objects.equals(verb, activity.verb) + && Objects.equals(object, activity.object) + && Objects.equals(foreignID, activity.foreignID) + && Objects.equals(target, activity.target) + && Objects.equals(time, activity.time) + && Objects.equals(origin, activity.origin) + && Objects.equals(to, activity.to) + && Objects.equals(score, activity.score) + && Objects.equals(extra, activity.extra); + } + + @Override + public int hashCode() { + return Objects.hash(id, actor, verb, object, foreignID, target, time, origin, to, score, extra); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", this.id) + .add("actor", this.actor) + .add("verb", this.verb) + .add("object", this.object) + .add("foreignID", this.foreignID) + .add("target", this.target) + .add("time", this.time) + .add("origin", this.origin) + .add("to", this.to) + .add("score", this.score) + .add("extra", this.extra) + .toString(); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonPOJOBuilder(withPrefix = "") + public static final class Builder { + private String id; + private String actor; + private String verb; + private String object; + private String foreignID; + private String target; + private Date time; + private String origin; + private List to; + private Double score; + private Map extra; + private String moderationTemplate; + private ModerationResponse moderationResponse; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder moderationTemplate(String moderationTemplate) { + this.moderationTemplate = moderationTemplate; + return this; + } + + public Builder actor(String actor) { + this.actor = actor; + return this; + } + + public Builder verb(String verb) { + this.verb = verb; + return this; + } + + public Builder object(String object) { + this.object = object; + return this; + } + + @JsonProperty("foreign_id") + public Builder foreignID(String foreignID) { + this.foreignID = foreignID; + return this; + } + + @JsonProperty("moderation") + public Builder setModerationResponse(ModerationResponse mod) { + this.moderationResponse = mod; + return this; + } + + public Builder target(String target) { + this.target = target; + return this; + } + + @JsonDeserialize(using = DateDeserializer.class) + public Builder time(Date time) { + this.time = time; + return this; + } + + public Builder origin(String origin) { + this.origin = origin; + return this; + } + + @JsonProperty("to") + public Builder to(List to) { + this.to = to; + return this; + } + + @JsonIgnore + public Builder to(Iterable to) { + this.to = Lists.newArrayList(to); + return this; + } + + @JsonIgnore + public Builder to(FeedID... to) { + this.to = Lists.newArrayList(to); + return this; + } + + public Builder score(double score) { + this.score = score; + return this; + } + + @JsonAnySetter + public Builder extraField(String key, Object value) { + if (extra == null) { + extra = Maps.newHashMap(); + } + extra.put(key, value); + return this; + } + + @JsonIgnore + public Builder extra(Map extra) { + if (!extra.isEmpty()) { + this.extra = extra; + } + return this; + } + + @JsonIgnore + public Builder fromActivity(Activity activity) { + this.id = activity.id; + this.actor = activity.actor; + this.verb = activity.verb; + this.object = activity.object; + this.foreignID = activity.foreignID; + this.target = activity.target; + this.time = activity.time; + this.origin = activity.origin; + this.to = activity.to; + this.score = activity.score; + this.extra = activity.extra; + this.moderationTemplate = activity.moderationTemplate; + this.moderationResponse = activity.moderationResponse; + return this; + } + + @JsonIgnore + public Builder fromCustomActivity(T custom) { + return fromActivity(convert(custom, Activity.class)); + } + + public Activity build() { + checkNotNull(actor, "Activity 'actor' field required"); + checkNotNull(verb, "Activity 'verb' field required"); + checkNotNull(object, "Activity 'object' field required"); + + return new Activity(this); + } + } +} diff --git a/src/main/java/io/getstream/core/models/ActivityUpdate.java b/src/main/java/io/getstream/core/models/ActivityUpdate.java new file mode 100644 index 00000000..a4f90348 --- /dev/null +++ b/src/main/java/io/getstream/core/models/ActivityUpdate.java @@ -0,0 +1,117 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.OptBoolean; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import java.util.Date; +import java.util.List; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ActivityUpdate { + private final String id; + private final String foreignID; + private final Date time; + private final Map set; + private final List unset; + + ActivityUpdate(Builder builder) { + if (builder.id != null) { + id = builder.id; + foreignID = null; + time = null; + } else { + id = null; + foreignID = builder.foreignID; + time = builder.time; + } + set = builder.set; + unset = builder.unset; + } + + public String getID() { + return id; + } + + @JsonProperty("foreign_id") + public String getForeignID() { + return foreignID; + } + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", + lenient = OptBoolean.FALSE, + timezone = "UTC") + public Date getTime() { + return time; + } + + public Map getSet() { + return set; + } + + public List getUnset() { + return unset; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String id; + private String foreignID; + private Date time; + private Map set; + private List unset; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder foreignID(String foreignID) { + this.foreignID = foreignID; + return this; + } + + public Builder time(Date time) { + this.time = time; + return this; + } + + public Builder foreignIDTimePair(ForeignIDTimePair pair) { + foreignID = pair.getForeignID(); + time = pair.getTime(); + return this; + } + + public Builder set(Map set) { + this.set = ImmutableMap.copyOf(set); + return this; + } + + public Builder set(Iterable> set) { + this.set = ImmutableMap.copyOf(set); + return this; + } + + public Builder unset(Iterable unset) { + this.unset = Lists.newArrayList(unset); + return this; + } + + public Builder unset(String... unset) { + this.unset = Lists.newArrayList(unset); + return this; + } + + public ActivityUpdate build() { + return new ActivityUpdate(this); + } + } +} diff --git a/src/main/java/io/getstream/core/models/AuditLog.java b/src/main/java/io/getstream/core/models/AuditLog.java new file mode 100644 index 00000000..82527981 --- /dev/null +++ b/src/main/java/io/getstream/core/models/AuditLog.java @@ -0,0 +1,47 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Date; +import java.util.Map; + +public class AuditLog { + @JsonProperty("entity_type") + private String entityType; + + @JsonProperty("entity_id") + private String entityID; + + private String action; + + @JsonProperty("user_id") + private String userID; + + private Map custom; + + @JsonProperty("created_at") + private Date createdAt; + + public String getEntityType() { + return entityType; + } + + public String getEntityID() { + return entityID; + } + + public String getAction() { + return action; + } + + public String getUserID() { + return userID; + } + + public Map getCustom() { + return custom; + } + + public Date getCreatedAt() { + return createdAt; + } +} \ No newline at end of file diff --git a/src/main/java/io/getstream/core/models/BatchDeleteActivitiesRequest.java b/src/main/java/io/getstream/core/models/BatchDeleteActivitiesRequest.java new file mode 100644 index 00000000..b0e41757 --- /dev/null +++ b/src/main/java/io/getstream/core/models/BatchDeleteActivitiesRequest.java @@ -0,0 +1,39 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BatchDeleteActivitiesRequest { + + private final List activities; + + public BatchDeleteActivitiesRequest(List activities) { + this.activities = activities; + } + + public List getActivities() { + return activities; + } + + public static class ActivityToDelete { + private final String id; + private final List removeFromFeeds; + + public ActivityToDelete( + @JsonProperty("id") String id, + @JsonProperty("remove_from_feeds") List removeFromFeeds) { + this.id = id; + this.removeFromFeeds = removeFromFeeds; + } + + public String getId() { + return id; + } + + public List getRemoveFromFeeds() { + return removeFromFeeds; + } + } +} \ No newline at end of file diff --git a/src/main/java/io/getstream/core/models/BatchDeleteReactionsRequest.java b/src/main/java/io/getstream/core/models/BatchDeleteReactionsRequest.java new file mode 100644 index 00000000..d0292f4d --- /dev/null +++ b/src/main/java/io/getstream/core/models/BatchDeleteReactionsRequest.java @@ -0,0 +1,31 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BatchDeleteReactionsRequest { + + private final List ids; + private final Boolean SoftDelete; + + + public BatchDeleteReactionsRequest(@JsonProperty("ids") List ids) { + this.ids = ids; + SoftDelete = null; + } + public BatchDeleteReactionsRequest(@JsonProperty("ids") List ids, @JsonProperty("soft_delete") Boolean softDelete) { + this.ids = ids; + SoftDelete = softDelete; + } + + public List getIds() { + return ids; + } + + @JsonProperty("soft_delete") + public Boolean getSoftDelete() { + return SoftDelete; + } +} \ No newline at end of file diff --git a/src/main/java/io/getstream/core/models/CollectionData.java b/src/main/java/io/getstream/core/models/CollectionData.java new file mode 100644 index 00000000..da57458b --- /dev/null +++ b/src/main/java/io/getstream/core/models/CollectionData.java @@ -0,0 +1,94 @@ +package io.getstream.core.models; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Objects; + +public final class CollectionData { + private final String id; + private final String collection; + private final Map data; + + @JsonCreator + public CollectionData( + @JsonProperty("collection") String collection, + @JsonProperty("id") String id, + @JsonProperty("data") Map data) { + this.collection = collection; + this.data = firstNonNull(data, Maps.newHashMap()); + this.id = checkNotNull(id, "ID required"); + } + + public CollectionData() { + this(null, "", null); + } + + public CollectionData(String id) { + this(null, id, null); + } + + public static CollectionData buildFrom(T data) { + return convert(data, CollectionData.class); + } + + public String getID() { + return id; + } + + @JsonIgnore + public String getCollection() { + return collection; + } + + @JsonAnyGetter + public Map getData() { + return data; + } + + @JsonAnySetter + public CollectionData set(String key, T value) { + checkNotNull(key, "Key can't be null"); + + data.put(key, value); + return this; + } + + public CollectionData from(T data) { + checkNotNull(data, "Can't extract data from null"); + + Map map = convert(data, new TypeReference>() {}); + for (Map.Entry entry : map.entrySet()) { + set(entry.getKey(), entry.getValue()); + } + return this; + } + + public T get(String key) { + return (T) data.get(checkNotNull(key, "Key can't be null")); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CollectionData collectionData = (CollectionData) o; + return Objects.equals(id, collectionData.id) && Objects.equals(data, collectionData.data); + } + + @Override + public int hashCode() { + return Objects.hash(id, data); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("id", this.id).add("data", this.data).toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Content.java b/src/main/java/io/getstream/core/models/Content.java new file mode 100644 index 00000000..c88249d8 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Content.java @@ -0,0 +1,84 @@ +package io.getstream.core.models; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Objects; + +public final class Content { + private final String foreignID; + private final Map data = Maps.newHashMap(); + + @JsonCreator + public Content(@JsonProperty("foreign_id") String foreignID) { + this.foreignID = checkNotNull(foreignID, "ID required"); + } + + public static Content buildFrom(T data) { + return convert(data, Content.class); + } + + @JsonProperty("foreign_id") + public String getForeignID() { + return foreignID; + } + + @JsonAnyGetter + public Map getData() { + return data; + } + + @JsonAnySetter + public Content set(String key, T value) { + checkArgument(!"foreignID".equals(key), "Key can't be named 'foreignID'"); + checkNotNull(key, "Key can't be null"); + + data.put(key, value); + return this; + } + + public Content from(T data) { + checkNotNull(data, "Can't extract data from null"); + + Map map = convert(data, new TypeReference>() {}); + for (Map.Entry entry : map.entrySet()) { + set(entry.getKey(), entry.getValue()); + } + return this; + } + + public T get(String key) { + return (T) data.get(checkNotNull(key, "Key can't be null")); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Content collectionData = (Content) o; + return Objects.equals(foreignID, collectionData.foreignID) + && Objects.equals(data, collectionData.data); + } + + @Override + public int hashCode() { + return Objects.hash(foreignID, data); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", this.foreignID) + .add("data", this.data) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Data.java b/src/main/java/io/getstream/core/models/Data.java new file mode 100644 index 00000000..65fb5e28 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Data.java @@ -0,0 +1,85 @@ +package io.getstream.core.models; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Maps; +import io.getstream.core.models.serialization.DataDeserializer; +import java.util.Map; +import java.util.Objects; + +@JsonDeserialize(using = DataDeserializer.class) +public final class Data { + private final String id; + private final Map data = Maps.newHashMap(); + + public Data(String id) { + this.id = checkNotNull(id, "ID required"); + } + + public Data() { + this(""); + } + + public static Data buildFrom(T data) { + return convert(data, Data.class); + } + + public String getID() { + return id; + } + + @JsonAnyGetter + public Map getData() { + return data; + } + + public Data set(String key, T value) { + checkNotNull(key, "Key can't be null"); + + data.put(key, value); + return this; + } + + public Data from(T data) { + return from(convert(data, new TypeReference>() {})); + } + + public Data from(Map map) { + checkNotNull(data, "Can't extract data from null"); + if (map == null || map.isEmpty()) { + return this; + } + + for (Map.Entry entry : map.entrySet()) { + set(entry.getKey(), entry.getValue()); + } + return this; + } + + public T get(String key) { + return (T) data.get(checkNotNull(key, "Key can't be null")); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Data data = (Data) o; + return Objects.equals(id, data.id) && Objects.equals(data, data.data); + } + + @Override + public int hashCode() { + return Objects.hash(id, data); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("id", this.id).add("data", this.data).toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Engagement.java b/src/main/java/io/getstream/core/models/Engagement.java new file mode 100644 index 00000000..37fa8df7 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Engagement.java @@ -0,0 +1,210 @@ +package io.getstream.core.models; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.MoreObjects; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +@JsonInclude(Include.NON_NULL) +@JsonDeserialize(builder = Engagement.Builder.class) +public class Engagement { + private final String feedID; + private final UserData userData; + private final String label; + private final Content content; + private final Integer boost; + private final Integer position; + private final String location; + private final List features; + private final Date trackedAt; + + private Engagement(Builder builder) { + label = builder.label; + content = builder.content; + boost = builder.boost; + position = builder.position; + feedID = builder.feedID; + location = builder.location; + userData = builder.userData; + features = builder.features; + trackedAt = builder.trackedAt; + } + + public static Builder builder() { + return new Builder(); + } + + public String getLabel() { + return label; + } + + public Content getContent() { + return content; + } + + public int getBoost() { + return boost; + } + + public int getPosition() { + return position; + } + + @JsonProperty("feed_id") + public String getFeedID() { + return feedID; + } + + public String getLocation() { + return location; + } + + @JsonProperty("user_data") + public UserData getUserData() { + return userData; + } + + public List getFeatures() { + return features; + } + + @JsonProperty("tracked_at") + public Date getTrackedAt() { + return trackedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Engagement that = (Engagement) o; + return Objects.equals(label, that.label) + && Objects.equals(content, that.content) + && Objects.equals(boost, that.boost) + && Objects.equals(position, that.position) + && Objects.equals(feedID, that.feedID) + && Objects.equals(location, that.location) + && Objects.equals(userData, that.userData) + && Objects.equals(features, that.features) + && Objects.equals(trackedAt, that.trackedAt); + } + + @Override + public int hashCode() { + return Objects.hash( + label, content, boost, position, feedID, location, userData, features, trackedAt); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("label", this.label) + .add("content", this.content) + .add("boost", this.boost) + .add("position", this.position) + .add("feedID", this.feedID) + .add("location", this.location) + .add("userData", this.userData) + .add("features", this.features) + .add("trackedAt", this.trackedAt) + .toString(); + } + + @JsonPOJOBuilder(withPrefix = "") + public static final class Builder { + private String label; + private Content content; + private Integer boost; + private Integer position; + private String feedID; + private String location; + private UserData userData; + private List features; + private Date trackedAt; + + public Builder label(String label) { + this.label = label; + return this; + } + + public Builder content(Content content) { + this.content = content; + return this; + } + + public Builder boost(int boost) { + this.boost = boost; + return this; + } + + public Builder position(int position) { + this.position = position; + return this; + } + + @JsonProperty("feed_id") + public Builder feedID(String feedID) { + this.feedID = feedID; + return this; + } + + public Builder location(String location) { + this.location = location; + return this; + } + + @JsonProperty("user_data") + public Builder userData(UserData userData) { + this.userData = userData; + return this; + } + + public Builder features(List features) { + this.features = features; + return this; + } + + @JsonProperty("tracked_at") + public Builder trackedAt(Date trackedAt) { + this.trackedAt = trackedAt; + return this; + } + + @JsonIgnore + public Builder fromEngagement(Engagement engagement) { + label = engagement.label; + content = engagement.content; + boost = engagement.boost; + position = engagement.position; + feedID = engagement.feedID; + location = engagement.location; + userData = engagement.userData; + features = engagement.features; + trackedAt = engagement.trackedAt; + return this; + } + + @JsonIgnore + public Builder fromCustomEngagement(T custom) { + return fromEngagement(convert(custom, Engagement.class)); + } + + public Engagement build() { + checkNotNull(feedID, "Engagement 'feedID' field required"); + checkNotNull(userData, "Engagement 'userData' field required"); + checkNotNull(label, "Engagement 'label' field required"); + checkNotNull(content, "Engagement 'content' field required"); + + return new Engagement(this); + } + } +} diff --git a/src/main/java/io/getstream/core/models/EnrichedActivity.java b/src/main/java/io/getstream/core/models/EnrichedActivity.java new file mode 100644 index 00000000..8d05f52d --- /dev/null +++ b/src/main/java/io/getstream/core/models/EnrichedActivity.java @@ -0,0 +1,378 @@ +package io.getstream.core.models; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import io.getstream.core.models.serialization.DateDeserializer; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +// TODO: check which fields could actually be enriched +@JsonInclude(Include.NON_NULL) +@JsonDeserialize(builder = EnrichedActivity.Builder.class) +public class EnrichedActivity { + private final String id; + private final Data actor; + private final String verb; + private final Data object; + private final String foreignID; + private final Data target; + // TODO: support Java 8 Date/Time types? + private final Date time; + private final Data origin; + private final List to; + private final Double score; + private final Map reactionCounts; + private final Map> ownReactions; + private final Map> latestReactions; + private final Map extra; + private final String moderationTemplate; + private final ModerationResponse moderationResponse; + + private EnrichedActivity(Builder builder) { + id = builder.id; + actor = builder.actor; + verb = builder.verb; + object = builder.object; + foreignID = builder.foreignID; + target = builder.target; + time = builder.time; + origin = builder.origin; + to = builder.to; + score = builder.score; + reactionCounts = builder.reactionCounts; + ownReactions = builder.ownReactions; + latestReactions = builder.latestReactions; + extra = builder.extra; + moderationTemplate = builder.moderationTemplate; + moderationResponse = builder.moderationResponse; + } + + public String getID() { + return id; + } + + public Data getActor() { + return actor; + } + + public String getVerb() { + return verb; + } + + public Data getObject() { + return object; + } + + @JsonProperty("foreign_id") + public String getForeignID() { + return foreignID; + } + + public Data getTarget() { + return target; + } + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", + lenient = OptBoolean.FALSE, + timezone = "UTC") + public Date getTime() { + return time; + } + + public Data getOrigin() { + return origin; + } + + public List getTo() { + return to; + } + + public Double getScore() { + return score; + } + + @JsonIgnore + public Map getReactionCounts() { + return reactionCounts; + } + + @JsonIgnore + public Map> getOwnReactions() { + return ownReactions; + } + + @JsonIgnore + public Map> getLatestReactions() { + return latestReactions; + } + + @JsonProperty("moderation") + public ModerationResponse getModerationResponse() { + return moderationResponse; + } + + @JsonProperty("moderation_template") + public String getModerationTemplate() { + return moderationTemplate; + } + + @JsonAnyGetter + public Map getExtra() { + return extra; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EnrichedActivity activity = (EnrichedActivity) o; + return Objects.equals(id, activity.id) + && Objects.equals(actor, activity.actor) + && Objects.equals(verb, activity.verb) + && Objects.equals(object, activity.object) + && Objects.equals(foreignID, activity.foreignID) + && Objects.equals(target, activity.target) + && Objects.equals(time, activity.time) + && Objects.equals(origin, activity.origin) + && Objects.equals(to, activity.to) + && Objects.equals(score, activity.score) + && Objects.equals(extra, activity.extra); + } + + @Override + public int hashCode() { + return Objects.hash(id, actor, verb, object, foreignID, target, time, origin, to, score, extra); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", this.id) + .add("actor", this.actor) + .add("verb", this.verb) + .add("object", this.object) + .add("foreignID", this.foreignID) + .add("target", this.target) + .add("time", this.time) + .add("origin", this.origin) + .add("to", this.to) + .add("score", this.score) + .add("ownReactions", this.ownReactions) + .add("latestReactions", this.latestReactions) + .add("reactionCounts", this.reactionCounts) + .add("moderationTemplate", this.moderationTemplate) + .add("moderationResponse", this.moderationResponse) + .add("extra", this.extra) + .toString(); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonPOJOBuilder(withPrefix = "") + public static final class Builder { + private String id; + private Data actor; + private String verb; + private Data object; + private String foreignID; + private Data target; + private Date time; + private Data origin; + private List to; + private Double score; + private Map reactionCounts; + private Map> ownReactions; + private Map> latestReactions; + private Map extra; + private String moderationTemplate; + private ModerationResponse moderationResponse; + + public Builder id(String id) { + this.id = id; + return this; + } + + @JsonIgnore + public Builder actor(String actor) { + this.actor = new Data(actor); + return this; + } + + @JsonProperty("actor") + public Builder actor(Data actor) { + this.actor = actor; + return this; + } + + public Builder verb(String verb) { + this.verb = verb; + return this; + } + + @JsonIgnore + public Builder object(String object) { + this.object = new Data(object); + return this; + } + + @JsonProperty("object") + public Builder object(Data object) { + this.object = object; + return this; + } + + @JsonProperty("foreign_id") + public Builder foreignID(String foreignID) { + this.foreignID = foreignID; + return this; + } + + @JsonIgnore + public Builder target(String target) { + this.target = new Data(target); + return this; + } + + @JsonProperty("target") + public Builder target(Data target) { + this.target = target; + return this; + } + + @JsonDeserialize(using = DateDeserializer.class) + public Builder time(Date time) { + this.time = time; + return this; + } + + @JsonIgnore + public Builder origin(String origin) { + this.origin = new Data(origin); + return this; + } + + @JsonProperty("origin") + public Builder origin(Data origin) { + this.origin = origin; + return this; + } + + @JsonProperty("to") + public Builder to(List to) { + this.to = to; + return this; + } + + @JsonIgnore + public Builder to(Iterable to) { + this.to = Lists.newArrayList(to); + return this; + } + + @JsonIgnore + public Builder to(FeedID... to) { + this.to = Lists.newArrayList(to); + return this; + } + + public Builder score(double score) { + this.score = score; + return this; + } + + @JsonProperty("own_reactions") + public Builder ownReactions(Map> ownReactions) { + this.ownReactions = ownReactions; + return this; + } + + @JsonProperty("latest_reactions") + public Builder latestReactions(Map> latestReactions) { + this.latestReactions = latestReactions; + return this; + } + + @JsonProperty("reaction_counts") + public Builder reactionCounts(Map reactionCounts) { + this.reactionCounts = reactionCounts; + return this; + } + + @JsonProperty("moderation_template") + public Builder moderationTemplate(String moderationTemplate) { + this.moderationTemplate = moderationTemplate; + return this; + } + + @JsonProperty("moderation") + public Builder moderationResponse(ModerationResponse moderationResponse) { + this.moderationResponse = moderationResponse; + return this; + } + + @JsonAnySetter + public Builder extraField(String key, Object value) { + if (extra == null) { + extra = Maps.newHashMap(); + } + extra.put(key, value); + return this; + } + + @JsonIgnore + public Builder extra(Map extra) { + if (!extra.isEmpty()) { + this.extra = extra; + } + return this; + } + + @JsonIgnore + public Builder fromEnrichedActivity(EnrichedActivity activity) { + this.id = activity.id; + this.actor = activity.actor; + this.verb = activity.verb; + this.object = activity.object; + this.foreignID = activity.foreignID; + this.target = activity.target; + this.time = activity.time; + this.origin = activity.origin; + this.to = activity.to; + this.score = activity.score; + this.ownReactions = activity.ownReactions; + this.latestReactions = activity.latestReactions; + this.reactionCounts = activity.reactionCounts; + this.moderationTemplate = activity.moderationTemplate; + this.moderationResponse = activity.moderationResponse; + this.extra = activity.extra; + return this; + } + + @JsonIgnore + public Builder fromCustomEnrichedActivity(T custom) { + return fromEnrichedActivity(convert(custom, EnrichedActivity.class)); + } + + public EnrichedActivity build() { + checkNotNull(actor, "EnrichedActivity 'actor' field required"); + checkNotNull(verb, "EnrichedActivity 'verb' field required"); + checkNotNull(object, "EnrichedActivity 'object' field required"); + + return new EnrichedActivity(this); + } + } +} diff --git a/src/main/java/io/getstream/core/models/ExportIDsResponse.java b/src/main/java/io/getstream/core/models/ExportIDsResponse.java new file mode 100644 index 00000000..190f6ddb --- /dev/null +++ b/src/main/java/io/getstream/core/models/ExportIDsResponse.java @@ -0,0 +1,35 @@ +package io.getstream.core.models; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ExportIDsResponse { + @JsonProperty("export") + private ExportIDsResult export; + + @JsonProperty("duration") + private String duration; + + // No-argument constructor + public ExportIDsResponse() { + } + + // Constructor with parameters + public ExportIDsResponse(String duration) { + this.duration = duration; + } + + public ExportIDsResult getExport() { + return export; + } + + public void setExport(ExportIDsResult export) { + this.export = export; + } + + public String getDuration() { + return duration; + } + + public void setDuration(String duration) { + this.duration = duration; + } +} \ No newline at end of file diff --git a/src/main/java/io/getstream/core/models/ExportIDsResult.java b/src/main/java/io/getstream/core/models/ExportIDsResult.java new file mode 100644 index 00000000..043c673b --- /dev/null +++ b/src/main/java/io/getstream/core/models/ExportIDsResult.java @@ -0,0 +1,62 @@ +package io.getstream.core.models; + +import java.util.List; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ExportIDsResult { + @JsonProperty("user_id") + private String userId; + + @JsonProperty("activity_count") + private int activityCount; + + @JsonProperty("activity_ids") + private List activityIds; + + @JsonProperty("reaction_count") + private int reactionCount; + + @JsonProperty("reaction_ids") + private List reactionIds; + + // Getters and Setters + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public int getActivityCount() { + return activityCount; + } + + public void setActivityCount(int activityCount) { + this.activityCount = activityCount; + } + + public List getActivityIds() { + return activityIds; + } + + public void setActivityIds(List activityIds) { + this.activityIds = activityIds; + } + + public int getReactionCount() { + return reactionCount; + } + + public void setReactionCount(int reactionCount) { + this.reactionCount = reactionCount; + } + + public List getReactionIds() { + return reactionIds; + } + + public void setReactionIds(List reactionIds) { + this.reactionIds = reactionIds; + } +} \ No newline at end of file diff --git a/src/main/java/io/getstream/core/models/Feature.java b/src/main/java/io/getstream/core/models/Feature.java new file mode 100644 index 00000000..c41a5fb9 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Feature.java @@ -0,0 +1,46 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; +import java.util.Objects; + +public final class Feature { + private final String group; + private final String value; + + @JsonCreator + public Feature(@JsonProperty("group") String group, @JsonProperty("value") String value) { + this.group = group; + this.value = value; + } + + public String getGroup() { + return group; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Feature feature = (Feature) o; + return Objects.equals(group, feature.group) && Objects.equals(value, feature.value); + } + + @Override + public int hashCode() { + return Objects.hash(group, value); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("group", this.group) + .add("value", this.value) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/FeedID.java b/src/main/java/io/getstream/core/models/FeedID.java new file mode 100644 index 00000000..0f3c55b2 --- /dev/null +++ b/src/main/java/io/getstream/core/models/FeedID.java @@ -0,0 +1,66 @@ +package io.getstream.core.models; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import java.util.Objects; + +@JsonSerialize(using = ToStringSerializer.class) +public final class FeedID { + private final String slug; + private final String userID; + + public FeedID(String slug, String userID) { + checkNotNull(slug, "Feed slug can't be null"); + checkArgument(!slug.isEmpty(), "Feed slug can't be empty"); + checkArgument(!slug.contains(":"), "Invalid slug"); + checkNotNull(userID, "Feed user ID can't be null"); + checkArgument(!userID.contains(":"), "Invalid user ID"); + checkArgument(!userID.isEmpty(), "User ID can't be empty"); + + this.slug = slug; + this.userID = userID; + } + + public FeedID(String id) { + checkNotNull(id, "Feed ID can't be null"); + checkArgument(id.contains(":"), "Invalid feed ID"); + + String[] parts = id.split(":"); + checkArgument(parts.length == 2, "Invalid feed ID"); + this.slug = parts[0]; + this.userID = parts[1]; + } + + public String getSlug() { + return slug; + } + + public String getUserID() { + return userID; + } + + public String getClaim() { + return slug + userID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FeedID feedID = (FeedID) o; + return Objects.equals(slug, feedID.slug) && Objects.equals(userID, feedID.userID); + } + + @Override + public int hashCode() { + return Objects.hash(slug, userID); + } + + @Override + public String toString() { + return slug + ':' + userID; + } +} diff --git a/src/main/java/io/getstream/core/models/FollowRelation.java b/src/main/java/io/getstream/core/models/FollowRelation.java new file mode 100644 index 00000000..8646722d --- /dev/null +++ b/src/main/java/io/getstream/core/models/FollowRelation.java @@ -0,0 +1,78 @@ +package io.getstream.core.models; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +public final class FollowRelation { + private final String source; + private final String target; + private final Integer activityCopyLimit; + + @JsonCreator + public FollowRelation( + @JsonProperty("feed_id") String source, + @JsonProperty("target_id") String target, + @JsonProperty("activity_copy_limit") Integer activityCopyLimit) { + checkNotNull(source, "FollowRelation 'source' field required"); + checkNotNull(target, "FollowRelation 'target' field required"); + if (activityCopyLimit != null) { + checkArgument(activityCopyLimit >= 0, "Activity copy limit must be non negative"); + } + + this.source = source; + this.target = target; + this.activityCopyLimit = activityCopyLimit; + } + + public FollowRelation( + @JsonProperty("feed_id") String source, + @JsonProperty("target_id") String target) { + this(source, target, null); + } + + public String getSource() { + return this.source; + } + + public String getTarget() { + return this.target; + } + + @JsonProperty("activity_copy_limit") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getActivityCopyLimit() { + return this.activityCopyLimit; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FollowRelation that = (FollowRelation) o; + return Objects.equals(source, that.source) + && Objects.equals(target, that.target) + && Objects.equals(activityCopyLimit, that.activityCopyLimit); + } + + @Override + public int hashCode() { + return Objects.hash(source, target, activityCopyLimit); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("source", this.source) + .add("target", this.target) + .add("activityCopyLimit", this.activityCopyLimit) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/FollowStats.java b/src/main/java/io/getstream/core/models/FollowStats.java new file mode 100644 index 00000000..0da0d170 --- /dev/null +++ b/src/main/java/io/getstream/core/models/FollowStats.java @@ -0,0 +1,37 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public final class FollowStats { + @JsonProperty(value = "followers") + private FollowStat followers; + + @JsonProperty(value = "following") + private FollowStat following; + + public FollowStat getFollowers() { + return followers; + } + + public FollowStat getFollowing() { + return following; + } + + public class FollowStat { + @JsonProperty(value = "count") + private int count; + + @JsonProperty(value = "feed") + private String feed; + + public int getCount() { + return count; + } + + public String getFeed() { + return feed; + } + } +} diff --git a/src/main/java/io/getstream/core/models/ForeignIDTimePair.java b/src/main/java/io/getstream/core/models/ForeignIDTimePair.java new file mode 100644 index 00000000..f1ebfe78 --- /dev/null +++ b/src/main/java/io/getstream/core/models/ForeignIDTimePair.java @@ -0,0 +1,44 @@ +package io.getstream.core.models; + +import com.google.common.base.MoreObjects; +import java.util.Date; +import java.util.Objects; + +public final class ForeignIDTimePair { + private final String foreignID; + private final Date time; + + public ForeignIDTimePair(String foreignID, Date time) { + this.foreignID = foreignID; + this.time = time; + } + + public String getForeignID() { + return foreignID; + } + + public Date getTime() { + return time; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ForeignIDTimePair that = (ForeignIDTimePair) o; + return Objects.equals(foreignID, that.foreignID) && Objects.equals(time, that.time); + } + + @Override + public int hashCode() { + return Objects.hash(foreignID, time); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("foreignID", this.foreignID) + .add("time", this.time) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Group.java b/src/main/java/io/getstream/core/models/Group.java new file mode 100644 index 00000000..afd3c7e4 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Group.java @@ -0,0 +1,99 @@ +package io.getstream.core.models; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.MoreObjects; +import io.getstream.core.models.serialization.DateDeserializer; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Group { + private final String id; + private final String group; + private final List activities; + private final int actorCount; + private final Date createdAt; + private final Date updatedAt; + + @JsonCreator + public Group( + @JsonProperty("id") String id, + @JsonProperty("group") String group, + @JsonProperty("activities") List activities, + @JsonProperty("actor_count") int actorCount, + @JsonProperty("created_at") @JsonDeserialize(using = DateDeserializer.class) Date createdAt, + @JsonProperty("updated_at") @JsonDeserialize(using = DateDeserializer.class) Date updatedAt) { + checkNotNull(id, "Group 'id' field required"); + checkNotNull(group, "Group 'group' field required"); + checkNotNull(activities, "Group 'activities' field required"); + + this.id = id; + this.group = group; + this.activities = activities; + this.actorCount = actorCount; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public String getID() { + return id; + } + + public String getGroup() { + return group; + } + + public String getGroupID() { + return id + '.' + group; + } + + public List getActivities() { + return activities; + } + + public int getActorCount() { + return actorCount; + } + + public Date getCreatedAt() { + return createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Group that = (Group) o; + return actorCount == that.actorCount + && Objects.equals(id, that.id) + && Objects.equals(group, that.group) + && Objects.equals(activities, that.activities) + && Objects.equals(createdAt, that.createdAt) + && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, group, activities, actorCount, createdAt, updatedAt); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("activities", this.activities) + .add("actorCount", this.actorCount) + .add("createdAt", this.createdAt) + .add("updatedAt", this.updatedAt) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Impression.java b/src/main/java/io/getstream/core/models/Impression.java new file mode 100644 index 00000000..b01c6d28 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Impression.java @@ -0,0 +1,192 @@ +package io.getstream.core.models; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +@JsonInclude(Include.NON_NULL) +@JsonDeserialize(builder = Impression.Builder.class) +public class Impression { + private final String feedID; + private final UserData userData; + private final List contentList; + private final String position; + private final String location; + private final List features; + private final Date trackedAt; + + private Impression(Builder builder) { + position = builder.position; + feedID = builder.feedID; + location = builder.location; + userData = builder.userData; + contentList = builder.contentList; + features = builder.features; + trackedAt = builder.trackedAt; + } + + public static Builder builder() { + return new Builder(); + } + + public String getPosition() { + return position; + } + + @JsonProperty("feed_id") + public String getFeedID() { + return feedID; + } + + public String getLocation() { + return location; + } + + @JsonProperty("user_data") + public UserData getUserData() { + return userData; + } + + @JsonProperty("content_list") + public List getContentList() { + return contentList; + } + + public List getFeatures() { + return features; + } + + @JsonProperty("tracked_at") + public Date getTrackedAt() { + return trackedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Impression that = (Impression) o; + return Objects.equals(position, that.position) + && Objects.equals(feedID, that.feedID) + && Objects.equals(location, that.location) + && Objects.equals(userData, that.userData) + && Objects.equals(contentList, that.contentList) + && Objects.equals(features, that.features) + && Objects.equals(trackedAt, that.trackedAt); + } + + @Override + public int hashCode() { + return Objects.hash(position, feedID, location, userData, contentList, features, trackedAt); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("position", this.position) + .add("feedID", this.feedID) + .add("location", this.location) + .add("userData", this.userData) + .add("contentList", this.contentList) + .add("features", this.features) + .add("trackedAt", this.trackedAt) + .toString(); + } + + @JsonPOJOBuilder(withPrefix = "") + public static final class Builder { + private String position; + private String feedID; + private String location; + private UserData userData; + private List contentList; + private List features; + private Date trackedAt; + + public Builder position(String position) { + this.position = position; + return this; + } + + @JsonProperty("feed_id") + public Builder feedID(String feedID) { + this.feedID = feedID; + return this; + } + + public Builder location(String location) { + this.location = location; + return this; + } + + @JsonProperty("user_data") + public Builder userData(UserData userData) { + this.userData = userData; + return this; + } + + @JsonProperty("content_list") + public Builder contentList(List contentList) { + this.contentList = contentList; + return this; + } + + @JsonIgnore + public Builder contentList(Iterable contentList) { + this.contentList = Lists.newArrayList(contentList); + return this; + } + + @JsonIgnore + public Builder contentList(Content... contentList) { + this.contentList = Lists.newArrayList(contentList); + return this; + } + + public Builder features(List features) { + this.features = features; + return this; + } + + @JsonProperty("tracked_at") + public Builder trackedAt(Date trackedAt) { + this.trackedAt = trackedAt; + return this; + } + + @JsonIgnore + public Builder fromImpression(Impression impression) { + position = impression.position; + feedID = impression.feedID; + location = impression.location; + userData = impression.userData; + contentList = impression.contentList; + features = impression.features; + trackedAt = impression.trackedAt; + return this; + } + + @JsonIgnore + public Builder fromCustomImpression(T custom) { + return fromImpression(convert(custom, Impression.class)); + } + + public Impression build() { + checkNotNull(feedID, "Impression 'feedID' field required"); + checkNotNull(userData, "Impression 'userData' field required"); + + return new Impression(this); + } + } +} diff --git a/src/main/java/io/getstream/core/models/ModerationResponse.java b/src/main/java/io/getstream/core/models/ModerationResponse.java new file mode 100644 index 00000000..f30cf8c2 --- /dev/null +++ b/src/main/java/io/getstream/core/models/ModerationResponse.java @@ -0,0 +1,53 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ModerationResponse { + private String Status; + private String RecommendedAction; + private APIError APIError; + private String OriginFeed; + private String LatestModeratorAction; + + // Default constructor + public ModerationResponse() {} + + // Constructor with parameters + @JsonCreator + public ModerationResponse( + @JsonProperty("status") String status, + @JsonProperty("recommended_action") String recommendedAction, + @JsonProperty("api_error") APIError apiError, + @JsonProperty("origin_feed") String originFeed, + @JsonProperty("latest_moderator_action") String latestModeratorAction) { + this.Status = status; + this.RecommendedAction = recommendedAction; + this.APIError = apiError; + this.OriginFeed = originFeed; + this.LatestModeratorAction = latestModeratorAction; + } + + // Getters + public String getStatus() { + return Status; + } + + public String getRecommendedAction() { + return RecommendedAction; + } + + public APIError getAPIError() { + return APIError; + } + + public String getOriginFeed() { + return OriginFeed; + } + + public String getLatestModeratorAction() { + return LatestModeratorAction; + } +} diff --git a/src/main/java/io/getstream/core/models/NotificationGroup.java b/src/main/java/io/getstream/core/models/NotificationGroup.java new file mode 100644 index 00000000..27dd2aae --- /dev/null +++ b/src/main/java/io/getstream/core/models/NotificationGroup.java @@ -0,0 +1,67 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.MoreObjects; +import io.getstream.core.models.serialization.DateDeserializer; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class NotificationGroup extends Group { + private final boolean seen; + private final boolean read; + + @JsonCreator + public NotificationGroup( + @JsonProperty("id") String id, + @JsonProperty("group") String group, + @JsonProperty("activities") List activities, + @JsonProperty("actor_count") int actorCount, + @JsonProperty("created_at") @JsonDeserialize(using = DateDeserializer.class) Date createdAt, + @JsonProperty("updated_at") @JsonDeserialize(using = DateDeserializer.class) Date updatedAt, + @JsonProperty("is_seen") boolean isSeen, + @JsonProperty("is_read") boolean isRead) { + super(id, group, activities, actorCount, createdAt, updatedAt); + + this.seen = isSeen; + this.read = isRead; + } + + public boolean isSeen() { + return seen; + } + + public boolean isRead() { + return read; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + NotificationGroup that = (NotificationGroup) o; + return seen == that.seen && read == that.read; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), seen, read); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("activities", getActivities()) + .add("actorCount", getActorCount()) + .add("createdAt", getCreatedAt()) + .add("updatedAt", getUpdatedAt()) + .add("isSeen", seen) + .add("isRead", read) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/OGData.java b/src/main/java/io/getstream/core/models/OGData.java new file mode 100644 index 00000000..46f88be4 --- /dev/null +++ b/src/main/java/io/getstream/core/models/OGData.java @@ -0,0 +1,238 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class OGData { + public static class Image { + private final String image; + private final String url; + private final String secureUrl; + private final String width; + private final String height; + private final String type; + private final String alt; + + @JsonCreator + public Image( + @JsonProperty("image") String image, + @JsonProperty("url") String url, + @JsonProperty("secure_url") String secureUrl, + @JsonProperty("width") String width, + @JsonProperty("height") String height, + @JsonProperty("type") String type, + @JsonProperty("alt") String alt) { + this.image = image; + this.url = url; + this.secureUrl = secureUrl; + this.width = width; + this.height = height; + this.type = type; + this.alt = alt; + } + + public String getImage() { + return image; + } + + public String getURL() { + return url; + } + + public String getSecureUrl() { + return secureUrl; + } + + public String getWidth() { + return width; + } + + public String getHeight() { + return height; + } + + public String getType() { + return type; + } + + public String getAlt() { + return alt; + } + } + + public static class Video { + private final String video; + private final String alt; + private final String url; + private final String secureURL; + private final String type; + private final String width; + private final String height; + + @JsonCreator + public Video( + @JsonProperty("video") String video, + @JsonProperty("alt") String alt, + @JsonProperty("url") String url, + @JsonProperty("secure_url") String secureURL, + @JsonProperty("type") String type, + @JsonProperty("width") String width, + @JsonProperty("height") String height) { + this.video = video; + this.alt = alt; + this.url = url; + this.secureURL = secureURL; + this.type = type; + this.width = width; + this.height = height; + } + + public String getSecureURL() { + return secureURL; + } + + public String getURL() { + return url; + } + + public String getWidth() { + return width; + } + + public String getHeight() { + return height; + } + + public String getType() { + return type; + } + + public String getAlt() { + return alt; + } + + public String getVideo() { + return video; + } + } + + public static class Audio { + private final String url; + private final String secureURL; + private final String type; + private final String audio; + + @JsonCreator + public Audio( + @JsonProperty("url") String url, + @JsonProperty("secure_url") String secureURL, + @JsonProperty("type") String type, + @JsonProperty("audio") String audio) { + this.type = type; + this.audio = audio; + this.url = url; + this.secureURL = secureURL; + } + + public String getSecureURL() { + return secureURL; + } + + public String getURL() { + return url; + } + + public String getType() { + return type; + } + + public String getAudio() { + return audio; + } + } + + private final String title; + private final String type; + private final String description; + private final String determiner; + private final String locale; + private final String siteName; + private final List images; + private final List