Skip to content
This repository was archived by the owner on Nov 5, 2025. It is now read-only.
Merged

Dev #94

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f0afe72
feat: add how to contribute
Sigmanificient Nov 2, 2025
6ce58c3
Merge pull request #82 from Sigmapitech/feat/issue-79/(re)actions
Sigmanificient Nov 2, 2025
7e5fa54
docs(root): Add the readme.md of the project
Fenriir42 Sep 16, 2025
02c7963
feat(readme): add installation and start contributing sections
Lilianbazantay Nov 2, 2025
4e27def
fix(readme): fix layout of contributing/service
Lilianbazantay Nov 2, 2025
1dcdb80
feat(README): add link to API documentation
Lilianbazantay Nov 2, 2025
d2893a2
feat(readme): add step-by-step apk creation
Lilianbazantay Nov 2, 2025
5d017c6
feat(readme): add environnement element to technologies
Lilianbazantay Nov 2, 2025
1040cbe
fix(readme): remove unwanted data
Lilianbazantay Nov 2, 2025
fddbcb2
feat(readme): add config.toml config
Lilianbazantay Nov 3, 2025
b6df3b3
refactor(readme): remove contributing section of readme
Lilianbazantay Nov 3, 2025
3021faa
refactor(readme): move exemples into separate files
Lilianbazantay Nov 4, 2025
33328b8
refactor(readme): update config exemple
Lilianbazantay Nov 4, 2025
5ce129e
build(mobile): add dockerfile that build the apk
Ciznia Nov 2, 2025
ea836fb
build(nix): readd a nix builder for the front
Ciznia Nov 2, 2025
976a96b
fix(docker): fix mobile/back docker
Ciznia Nov 4, 2025
2f98796
fix(docker): fix the dockerfile for the front and serve the apk at th…
Ciznia Nov 4, 2025
32072f3
fix(examples): fix orthographe mistakes
Lilianbazantay Nov 4, 2025
d872811
refactor(readme): remove useless steps
Lilianbazantay Nov 4, 2025
3920bf4
feat(readme): add database and routes explanations
Lilianbazantay Nov 4, 2025
0a89fc8
fix(front): setup api url properly
Sigmanificient Nov 4, 2025
7b1b022
Merge pull request #91 from Sigmapitech/extra/issue-6/add-the-differe…
Ciznia Nov 5, 2025
98e8982
Merge pull request #88 from Sigmapitech/feat/issue-86/apk_generation
Fenriir42 Nov 5, 2025
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
83 changes: 83 additions & 0 deletions HOW_TO_CONTRIBUTE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# How to Contribute a New OAuth Service

## 1. Create a Config Model

In your service module, create a `Config` Pydantic model that defines the OAuth configuration:

```python
from pydantic import BaseModel

class Config(BaseModel):
service: str = "google"
client_id: str
client_secret: str
auth_base: str = "..."
token_url: str = "..."
api_base: str = "..."
api_resource: str = "..."
profile_endpoint: str = "..."
redirect_uri: str = "..."
scope: str = "..."
pkce: bool = True
```

* Set `service` to a unique identifier for the platform.
* Specify the authorization URL (`auth_base`), token URL (`token_url`), API endpoints, and scopes.
* `redirect_uri` should point to your API route for handling the callback.

## 2. Instantiate the OAuth Provider

Use the shared `OAuthProvider` class:

```python
from fastapi import APIRouter
import pathlib
from .oauth_base import OAuthProvider

router = APIRouter(prefix="/[my_service]", tags=["[my_service]"])

provider = OAuthProvider(
package=__package__,
config_model=Config,
icon=(pathlib.Path(__file__).parent / "icon.svg").read_text()
)
```

* The `icon` will be displayed in the frontend service cards.

## 3. Add API Routes

Define FastAPI routes to handle connecting, authentication, token refresh, and user info:

```python
@router.get("/connect")
async def google_connect(token: str, platform: str):
return await provider.connect(token, platform)

@router.get("/auth")
async def google_auth(code: str, state: str, db=Depends(get_session)):
return await provider.auth(code, state, db)

@router.get("/refresh")
async def google_refresh(user=Depends(get_current_user), db=Depends(get_session)):
return await provider.refresh(user, db)

@router.get("/me")
async def google_me(user=Depends(get_current_user), db=Depends(get_session)):
return await provider.me(user, db)
```

* `/connect` initiates the OAuth connection.
* `/auth` handles the callback from the OAuth provider.
* `/refresh` refreshes the access token.
* `/me` retrieves the current user’s profile info from the service.

## 4. Add Service Icon

Place an SVG icon named `icon.svg` in the service module folder. This will be displayed in the frontend cards for connecting services.

## 5. Test

1. Login in to the area
2. Go to `/services`
3. You should see you newly added service and be able to connect to it
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Action-Reaction

## Create an Automation Platform (similar to IFTTT / Zapier)

## 📌 Overview

Action-Reaction is an automation platform designed to connect services together.
Users can define **AREAs** (*Action + REAction*) that automatically execute when certain events occur.

The system is composed of three main parts:

- **Application Server**: Business logic & REST API.
- **Web Client**: Browser-based UI, communicates with the server.
- **Mobile Client**: Android app, communicates with the server.

---

## ✨ Features

- User registration & authentication (password-based + OAuth2).
- Service subscription (Google, Outlook, Dropbox, etc.).
- Action components (event triggers).
- REAction components (automated tasks).
- AREAs: link Actions to REActions.
- Hooks: monitor & trigger automation.

---

## 🏗 Architecture

- **Server**: Runs business logic, exposes REST API (`http://localhost:8080`).
- **Web Client**: User interface (`http://localhost:8081`).
- **Mobile Client**: Android application, distributed via APK.
- **Docker Compose**: Orchestration of all components.

---

## 🚀 Getting Started

### Prerequisites

- [Docker](https://docs.docker.com/get-docker/)
- [Docker Compose](https://docs.docker.com/compose/)

### Installation

- **Step 1**: Go to back and a create a \`config.toml\` file. Fill it based on the data in exemples/exemple_config:
The `jwt_secret` is an ascii string.
Create an `uri` (or copy for exemples/exemple_config) and fill it with `"sqlite+aiosqlite:///app.db"`.
Each routes is defined following that structure: [routes.{service}]. The list of services can be found in back/app/routes.
For each routes fill the `client_id` and `client_secret` with your own.
Note for caldav/gmail/youtube, use the same `client_id`/`client_secret`. The `client_id` must finish with `.apps.googleusercontent.com`.


- **Step 2**: Go to front and create a \`gradle.properties\` file. Fill it with the informations in exemples/exemple_gradle. Fill `RELEASE_STORE_PASSWORD` and `RELEASE_KEY_PASSWORD` with your own password. The two must have an identical one.

- **Step 3**: In your terminal run `keytool -genkey -v -keystore apk_key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias alias;`.
It will ask for a keystore password, put the one you chose for the second step. It will follow by asking more information; those information don't need to be necesarilly true.
Enter 'y' to confirm the datas you entered.

- **Step 4**: Run `docker compose up --build`


### Services

- Server -> `http://localhost:8080/about.json`
- Web Client -> `http://localhost:8081/`
- Mobile Client APK -> YES

---

## 📜 API Example: `about.json`

WIP

---

## 📅 Project Timeline

- **21/09/2025**: Tech stack selection, PoC, task distribution.
- **06/10/2025**: Core architecture & base functionality.
- **02/11/2025**: Full feature set, UI, Docker deployment.

---

## 📖 Documentation

- **API**: http://localhost:8080/docs
3 changes: 2 additions & 1 deletion back/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ FROM scratch
# Copy /nix/store and the built result
COPY --from=builder /tmp/nix-store-closure /nix/store
COPY --from=builder /tmp/build/result /app
COPY --from=builder /tmp/build/back/config.toml /app/config.toml

# Expose server port and run
ENV PORT=8080
WORKDIR /app
EXPOSE 8080
# Pass "dev" to enable the CORS branch in app.main
CMD ["/app/bin/area", "dev"]
CMD ["/app/bin/area", "run"]
2 changes: 1 addition & 1 deletion back/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ async def lifespan(_: FastAPI):


def main():
uvicorn.run(app, host="127.0.0.1", port=8080)
uvicorn.run(app, host="0.0.0.0", port=8080)
47 changes: 27 additions & 20 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
services:
client_mobile:
build:
context: front
dockerfile: ./android/Dockerfile
volumes:
- shared-artifacts:/shared
# Optional: wait for a short while on first run to produce the APK
healthcheck:
test: ["CMD", "test", "-f", "/shared/client.apk"]
interval: 10s
timeout: 5s
retries: 30

server:
build:
context: .
dockerfile: back/Dockerfile
networks:
- backToFront
ports:
- "8080:8080"
working_dir: /app
volumes:
- ./back/config.toml:/app/config.toml:ro

# client_mobile:
# build:
# context: .
# dockerfile: front/android/Dockerfile
# volumes:
# - shared-artifacts:/shared
# # Optional: wait for a short while on first run to produce the APK
# healthcheck:
# test: ["CMD", "test", "-f", "/shared/client.apk"]
# interval: 10s
# timeout: 5s
# retries: 30

client_web:
build:
context: .
dockerfile: front/Dockerfile
context: front
dockerfile: ./Dockerfile
depends_on:

server:
condition: service_started
client_mobile:
condition: service_completed_successfully
networks:
- backToFront
ports:
- "8081:8081"
- "8081:80"
volumes:
- ./shared-artifacts:/app/share/www
- shared-artifacts:/shared/:ro

volumes:
shared-artifacts:

networks:
backToFront:
54 changes: 54 additions & 0 deletions examples/example_config
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[security]
jwt_secret = ""

[db]
uri="sqlite+aiosqlite:///app.db"


[routes.discord]
client_id = ""
client_secret = ""

[routes.spotify]
# scope = "user-read-email user-read-currently-playing user-follow-read"
client_id = ""
client_secret = ""

[routes.gmail]
# Provide your Google OAuth 2.0 Web client credentials
# Ensure the redirect URI http://127.0.0.1:8080/gmail/auth is added in Google Cloud Console
client_id = ""
client_secret = ""
# Optional: override scope or redirect URI here if needed
# scope = "https://www.googleapis.com/auth/gmail.readonly openid email profile"
# redirect_uri = "http://127.0.0.1:8080/gmail/auth"

[routes.caldav]
# Uses Google OAuth credentials (you can reuse the same client as Gmail/YouTube)
# Add redirect URI: http://127.0.0.1:8080/caldav/auth
client_id = ""
client_secret = ""
# Optional overrides
# scope = "https://www.googleapis.com/auth/calendar.readonly openid email profile"
# redirect_uri = "http://127.0.0.1:8080/caldav/auth"

[routes.youtube]
# Create OAuth credentials at https://console.cloud.google.com/apis/credentials
# Add http://127.0.0.1:8080/youtube/auth as an authorized redirect URI
client_id = "-."
client_secret = ""
# Optional overrides
# scope = "https://www.googleapis.com/auth/youtube.readonly openid email profile"
# redirect_uri = "http://127.0.0.1:8080/youtube/auth"

[routes.reddit]
# Create a Reddit app at https://www.reddit.com/prefs/apps
# For local dev, use type: installed app (or web app) and add redirect URI:
# http://127.0.0.1:8080/reddit/auth
# Note: Installed apps have no secret; keep client_secret empty. Web apps have a secret.
client_id = ""
client_secret = ""
# Optional overrides:
# scope = "identity read"
# redirect_uri = "http://127.0.0.1:8080/reddit/auth"
# resource_headers.User-Agent should be descriptive; override via code if needed
4 changes: 4 additions & 0 deletions front/android/gradle.properties → examples/example_gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ org.gradle.jvmargs=-Xmx1536m
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
RELEASE_STORE_FILE=/build/apk_key.jks
RELEASE_STORE_PASSWORD=xxxxxx
RELEASE_KEY_ALIAS=alias
RELEASE_KEY_PASSWORD=xxxxxx
1 change: 1 addition & 0 deletions front/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ node_modules
dist
dist-ssr
*.local
*.jks
48 changes: 27 additions & 21 deletions front/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
# Nix builder
FROM nixos/nix:latest AS builder
FROM node:22-bookworm-slim AS build

# Copy our source and setup our working dir.
COPY .. /tmp/build
WORKDIR /tmp/build
ENV DEBIAN_FRONTEND=noninteractive

# Build our Nix environment (front-web helper)
RUN nix \
--extra-experimental-features "nix-command flakes" \
--option filter-syscalls false \
build .#front
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Copy the Nix store closure into a directory.
RUN mkdir /tmp/nix-store-closure
RUN cp -R $(nix-store -qR result/) /tmp/nix-store-closure
RUN corepack enable && corepack prepare pnpm@latest --activate

# Final image is based on scratch.
FROM scratch
RUN npm install -g serve

# Copy /nix/store and the built result
COPY --from=builder /tmp/nix-store-closure /nix/store
COPY --from=builder /tmp/build/result /app
WORKDIR /app

# Expose client_web port and run
EXPOSE 8081
CMD ["/app/bin/web"]
COPY package.json pnpm-lock.yaml ./

ENV CI=true
RUN pnpm install --frozen-lockfile

COPY . .

RUN pnpm build:web

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Expose to 80 and remap it in compose since nginx defaults to 80 without parameters possible
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
1 change: 1 addition & 0 deletions front/android/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ out/
.gradle/
build/
local.properties
gradle.properties

*.log

Expand Down
Loading