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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Makefile
.gradle
.idea
build
.kotlin
.gitignore
LICENSE
Expand Down
6 changes: 0 additions & 6 deletions .env.example

This file was deleted.

17 changes: 17 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Database
DB_USER=remind-app-user
DB_PASSWORD=super-secret-wire-pwd
DB_URL=jdbc:postgresql://db:5432/remind-app
DB_NAME=remind-app

# Init
SDK_APP_ID=myAppUuid
SDK_APP_TOKEN=myApiToken
API_HOST_URL=https://staging-nginz-https.zinfra.io
CRYPTO_PASSWORD=myDummyPasswordmyDummyPassword01

# SDK
WIRE_SDK_USER_ID=myUserUuid
[email protected]
WIRE_SDK_PASSWORD=mySecretPassword01!
WIRE_SDK_ENVIRONMENT=staging.zinfra.io
10 changes: 3 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ nb-configuration.xml
# Plugin directory
/.quarkus/cli/plugins/

**/demo.properties

# Ignore SQLite database
*.db

# Ignore Core-crypto directory
cryptography
# Ignore Storage directory content but keep the directory
storage/*
!storage/.gitkeep
10 changes: 6 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ COPY . .

RUN ./gradlew clean build --no-daemon

FROM registry.access.redhat.com/ubi9/openjdk-21
FROM eclipse-temurin:21-jre

WORKDIR /deployments

ENV LANGUAGE='en_US:en'

Expand All @@ -19,9 +21,9 @@ COPY --chown=185 --from=build /setup/build/quarkus-app/quarkus-run.jar /deployme
COPY --chown=185 --from=build /setup/build/quarkus-app/app/ /deployments/app/
COPY --chown=185 --from=build /setup/build/quarkus-app/quarkus/ /deployments/quarkus/

RUN mkdir -p /deployments/storage && chown -R 185:0 /deployments/storage

EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"

ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
ENTRYPOINT ["java", "-jar", "-Dquarkus.http.host=0.0.0.0", "-Djava.util.logging.manager=org.jboss.logmanager.LogManager", "/deployments/quarkus-run.jar"]
37 changes: 14 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ This is an app that can create reminders for conversation, and send a message wh
- Delete reminders

> [!IMPORTANT]
> As of now, the bot only supports a maximum of 5 active reminders per group.
> As of now, the app only supports a maximum of 5 active reminders per group.

## Getting started
## Commands

### The basics of `/remind` command:

Expand All @@ -39,13 +39,13 @@ This is an app that can create reminders for conversation, and send a message wh
| one time reminder | `/remind to` | `"Fill in your invoices by end of day"` | `"tomorrow at 17:30"` |
| one time reminder | `/remind to` | `"Fill in your invoices by end of day"` | `"next Tue at 17:30"` |
| one time reminder | `/remind to` | `"Reply to HR email"` | `"in 10 minutes"` |
| one time reminder | `/remind to` | `"Travel back in time to not develop the bot"` | `"11/11/2150"` |
| one time reminder | `/remind to` | `"Travel back in time to not develop the app"` | `"11/11/2150"` |
| recurrent reminder | `/remind to` | `"Join the daily stand-up"` | `"every day at 10:00"` |
| recurrent reminder | `/remind to` | `"Empty the unread emails"` | `"every Friday at 17:00"` |
| recurrent reminder | `/remind to` | `"Empty the unread emails"` | `"every Mon, TUE, friday at 17:00"` |

> [!TIP]
> You can set reminders for yourself. To do so, you can use the commands in a private conversation, a 1:1 with the bot.
> You can set reminders for yourself. To do so, you can use the commands in a private conversation, a 1:1 with the app.

### Other helpful commands:

Expand All @@ -54,33 +54,24 @@ This is an app that can create reminders for conversation, and send a message wh
- `/remind delete <reminder-identifier>` (deletes the target reminder, the identifier can be obtained from the list
command)

## Technical details
## Development setup

### Bot Architecture
You first need to set all the env variables required by the app itself and the SDK inside.
Check the `.env.sample` and create a new `.env` file while setting the properties.

We are using a DDD-like architecture, but without the burden of defining a full DDD model (involving domains "experts"
and so on). The idea is to have a clear separation of concerns between the different layers of the application.
So each layer does the following:
And also the env variables required by `Wire Applications JVM SDK`, check related README
[SDK README](https://github.com/wireapp/wire-apps-jvm-sdk/blob/main/README.md)

- **Application**: Exposes the REST API and handles the HTTP requests, it's what the clients see.
- **Domain**: Contains the business logic, domain core entities, this layer is "clean" in other words, doesn't have any
dependency on other layers or frameworks. To access the logic, we provide UseCases, so we can group them (kind of
services + aggregates in DDD)
- **Infrastructure**: Contains the implementation of the domain repositories, and other external dependencies, like
databases, queues technologies, framework configurations, etc.

<img src="https://imgpile.com/images/C7Q2Gj.png" width="800"/>

**Note**: To enforce layer dependency, can be split into different gradle modules, but for now, we are keeping it
simple.

### Bot Framework
## Running the application in dev mode

This project uses Quarkus, the Supersonic Subatomic Java Framework.

If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .

## Running the application in dev mode
If you don't want to mess with Quarkus, use the provided `Dockerfile` or `docker-compose.yml`

Otherwise you can just start the `db` service in docker to have Postgres running and run the app yourself.

You can run your application in dev mode that enables live coding using:
```shell script
Expand Down Expand Up @@ -119,7 +110,7 @@ Or, if you don't have GraalVM installed, you can run the native executable build
./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true
```

You can then execute your native executable with: `./build/reminders-bots-1.0.0-SNAPSHOT-runner`
You can then execute your native executable inside the `build` directory

If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling.

Expand Down
8 changes: 5 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ val quarkusPlatformVersion: String by project
*/
configurations.all {
resolutionStrategy {
force("com.google.protobuf:protobuf-java:4.32.0")
force("com.google.protobuf:protobuf-kotlin:4.32.0")
force("com.google.protobuf:protobuf-java:4.33.0")
force("com.google.protobuf:protobuf-kotlin:4.33.0")
}
}

Expand All @@ -45,6 +45,8 @@ dependencies {
implementation("io.quarkus:quarkus-flyway")
implementation("io.quarkus:quarkus-quartz")
implementation("io.quarkus:quarkus-jdbc-postgresql")
implementation("io.quarkus:quarkus-smallrye-health")
implementation("io.quarkus:quarkus-logging-json")

// Kotlin support
implementation("io.quarkus:quarkus-kotlin")
Expand All @@ -55,7 +57,7 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("com.rubiconproject.oss:jchronic:0.2.8")
implementation("io.arrow-kt:arrow-core:2.1.2")
implementation("com.wire:wire-apps-jvm-sdk:0.0.16")
implementation("com.wire:wire-apps-jvm-sdk:0.0.18")

// Test dependencies
testImplementation("io.quarkus:quarkus-junit5")
Expand Down
11 changes: 8 additions & 3 deletions config/detekt/baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<?xml version="1.0" ?>
<?xml version='1.0' encoding='UTF-8'?>
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<CurrentIssues></CurrentIssues>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>UseCheckOrError:MlsSdkClient.kt$MlsSdkClient$throw IllegalStateException("API_HOST_URL environment variable is required")</ID>
<ID>UseCheckOrError:MlsSdkClient.kt$MlsSdkClient$throw IllegalStateException("CRYPTO_PASSWORD environment variable is required")</ID>
<ID>UseCheckOrError:MlsSdkClient.kt$MlsSdkClient$throw IllegalStateException("SDK_APP_ID environment variable is required")</ID>
<ID>UseCheckOrError:MlsSdkClient.kt$MlsSdkClient$throw IllegalStateException("SDK_APP_TOKEN environment variable is required")</ID>
</CurrentIssues>
</SmellBaseline>
20 changes: 12 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
version: '3'

services:
reminders_bot:
image: wirebots/reminders:latest
reminders_app:
platform: linux/amd64 # Allows using core-crypto on macOs
build:
context: ./
dockerfile: Dockerfile
environment:
QUARKUS_DATASOURCE_URL: jdbc:postgresql://db/quarkus
QUARKUS_DATASOURCE_URL: jdbc:postgresql://db/${DB_NAME}
env_file: .env
volumes:
- reminders_app-storage:/deployments/storage
depends_on:
- db

db:
image: postgres:13
environment:
- POSTGRES_USER=quarkus
- POSTGRES_PASSWORD=quarkus
- POSTGRES_DB=quarkus
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_NAME}
ports:
- 5432:5432
volumes:
- reminders_bot-db:/var/lib/postgresql/data/
- reminders_app-db:/var/lib/postgresql/data/

volumes:
reminders_bot-db:
reminders_app-storage:
reminders_app-db:
9 changes: 9 additions & 0 deletions helm/remindapp/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v2
name: remindapp
description: Wire Remind-App - A Helm chart for deploying the Wire Remind app
type: application
version: 0.1.0
appVersion: "0.1.0"
home: https://github.com/wireapp/remind-app
maintainers:
- name: Wire Integrations Team
85 changes: 85 additions & 0 deletions helm/remindapp/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
Wire remind-app has been deployed successfully!

The Wire Remind App is now running and will respond to these commands in Wire:
- /remind to "what" "when" - Create a new reminder (one-time or recurring)
- /remind help - Show help information with command examples
- /remind list - List all active reminders in the conversation
- /remind delete <reminderId> - Delete a specific reminder by ID

AVAILABLE ENDPOINTS:
- GET /q/health/started - Server running

DEPLOYMENT INFORMATION:
{{- if .Values.persistence.enabled }}
- Persistent storage: {{ .Values.persistence.size }} ({{ .Values.persistence.storageClass | default "default" }} storage class)
{{- end }}
- Health checks: Startup, liveness, and readiness probes configured
- Resource limits: {{ .Values.resources.limits.cpu }} CPU, {{ .Values.resources.limits.memory }} memory
- Service: {{ .Values.service.type }} on port {{ .Values.service.port }} -> {{ .Values.service.targetPort }}

MONITORING & DEBUGGING:

1. Check application status:
kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/name={{ include "remindapp.name" . }}

2. View application logs:
kubectl logs -f deployment/{{ include "remindapp.fullname" . }} -n {{ .Release.Namespace }}

3. Access health endpoint:
{{- if contains "ClusterIP" .Values.service.type }}
kubectl port-forward -n {{ .Release.Namespace }} svc/{{ include "remindapp.fullname" . }} 8080:{{ .Values.service.port }}
# Then visit: http://localhost:8080/q/health/started
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get -n {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "remindapp.fullname" . }})
export NODE_IP=$(kubectl get nodes -n {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
# Visit: http://$NODE_IP:$NODE_PORT/q/health/started
{{- else if contains "LoadBalancer" .Values.service.type }}
export SERVICE_IP=$(kubectl get svc -n {{ .Release.Namespace }} {{ include "remindapp.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
# Visit: http://$SERVICE_IP:{{ .Values.service.port }}/q/health/started
{{- end }}

4. Check configuration:
kubectl describe configmap/{{ include "remindapp.fullname" . }} -n {{ .Release.Namespace }}
{{- if .Values.secrets.secretName }}
kubectl describe secret/{{ .Values.secrets.secretName }} -n {{ .Release.Namespace }}
{{- end }}

CONFIGURATION:
{{- range .Values.env }}
{{- if eq .name "WIRE_SDK_USER_ID" }}
- Wire SDK User Id: {{ .value | default "Not configured" }}
{{- end }}
{{- if eq .name "WIRE_SDK_EMAIL" }}
- Wire SDK User Email: {{ .value | default "Not configured" }}
{{- end }}
{{- if eq .name "WIRE_SDK_ENVIRONMENT" }}
- Wire SDK Environment: {{ .value | default "Not configured" }}
{{- end }}
{{- if eq .name "DB_USER" }}
- Database user: {{ .value | default "Not configured" }}
{{- end }}
{{- if eq .name "DB_URL" }}
- Database Url: {{ .value | default "Not configured" }}
{{- end }}
{{- if eq .name "SDK_APP_ID" }}
- Wire SDK App Id: {{ .value | default "Not configured" }}
{{- end }}
{{- if eq .name "API_HOST_URL" }}
- Wire API Host Url: {{ .value | default "Not configured" }}
{{- end }}
{{- end }}
{{- if .Values.secrets.secretName }}
- Secrets mounted at: {{ .Values.secrets.mountPath }}
{{- end }}

The app is ready to create and manage reminders in Wire conversations!

USAGE TIPS:
- Maximum 5 active reminders per conversation
- One-time reminder examples:
/remind to "Submit report" "tomorrow at 17:30"
/remind to "Reply to email" "in 10 minutes"
- Recurring reminder examples:
/remind to "Daily standup" "every day at 10:00"
/remind to "Weekly review" "every Monday at 17:00"
/remind to "Team meeting" "every Mon, Wed, Fri at 14:00"
62 changes: 62 additions & 0 deletions helm/remindapp/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "remindapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "remindapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "remindapp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "remindapp.labels" -}}
helm.sh/chart: {{ include "remindapp.chart" . }}
{{ include "remindapp.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "remindapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "remindapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "remindapp.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "remindapp.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
6 changes: 6 additions & 0 deletions helm/remindapp/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "remindapp.fullname" . }}-config
labels:
{{- include "remindapp.labels" . | nindent 4 }}
Loading