Compare commits

...

42 Commits
master ... ext

Author SHA1 Message Date
4NK Dev
650b934e0c chore: doc updates [skip ci] 2025-10-02 15:32:00 +00:00
4NK Dev
08d64fc0d2 ci: docker_tag=dev-test - Centralisation des fichiers CODE_OF_CONDUCT, CODEOWNERS, CONTRIBUTING, LICENSE 2025-10-01 21:05:44 +00:00
4NK Dev
d66bfc4009 ci: docker_tag=dev-test - Mise à jour documentation et standardisation 2025-10-01 20:57:45 +00:00
4NK Dev
450c3bcf05 auto_clea
All checks were successful
build-and-push-ext / build_push (push) Successful in 7s
2025-09-25 18:33:38 +00:00
4NK Dev
7b6f77cb89 auto_clea 2025-09-25 18:05:11 +00:00
4NK Dev
06879e4fed auto_clea 2025-09-25 17:34:41 +00:00
4NK Dev
4dad51241c auto_clea 2025-09-25 15:25:11 +00:00
4NK Dev
bc9bd7b92a auto_clea 2025-09-25 15:03:43 +00:00
4NK Dev
8fd81ea3aa auto_clea 2025-09-25 12:42:16 +00:00
4NK Dev
5543f20820 ci: docker_tag=ext chore(logging): redirect stdout to /var/log/sdk_storage/stdout.log 2025-09-25 10:59:25 +00:00
4NK Dev
50975a98e6 ci: docker_tag=ext chore: sync ignores, logging and backups 2025-09-25 08:58:24 +00:00
4NK Dev
682485abe4 ci: docker_tag=ext\n\nTrigger CI build for Docker images
All checks were successful
build-and-push-ext / build_push (push) Successful in 23s
2025-09-22 11:10:07 +00:00
4NK Dev
33b27c1d3e update submodule 2025-09-22 05:45:43 +00:00
4NK Dev
09232a2fe9 ci: docker_tag=ext - Trigger CI build for Docker images
All checks were successful
build-and-push-ext / build_push (push) Successful in 7s
2025-09-21 19:57:53 +00:00
4NK Dev
d15c995b31 ci: docker_tag=ext - Migrate to Debian base with minimal packages
All checks were successful
build-and-push-ext / build_push (push) Successful in 33s
2025-09-21 18:24:40 +00:00
4NK Dev
78efe3a208 ci: docker_tag=ext - Optimize Dockerfile to reduce image size (remove dev tools)
All checks were successful
build-and-push-ext / build_push (push) Successful in 23s
2025-09-21 18:09:11 +00:00
4NK Dev
719b8874df ci: docker_tag=ext
Some checks failed
build-and-push-ext / build_push (push) Failing after 16m4s
2025-09-21 17:05:59 +00:00
4NK Dev
eb5c0f364f ci: docker_tag=ext 2025-09-21 16:57:51 +00:00
4NK Dev
1fdb949347 ci: docker_tag=ext - Update Dockerfiles with comprehensive dependencies and tools
Some checks are pending
build-and-push-ext / build_push (push) Waiting to run
2025-09-21 13:45:51 +00:00
4NK Dev
7e1e4358a6 ci: docker_tag=ext - Mise à jour Dockerfile avec outils requis et .cursorignore 2025-09-21 13:27:01 +00:00
4NK Dev
ae7ad037f7 ci: docker_tag=ext - Update project for dev4 branch consistency
All checks were successful
build-and-push-ext / build_push (push) Successful in 6s
2025-09-21 08:23:43 +00:00
4NK Dev
a4fa790021 ci: Ajout du workflow Gitea pour le tag 'ext'
All checks were successful
build-and-push-ext / build_push (push) Successful in 3m49s
- Création de .gitea/workflows/build-ext.yml
- Workflow déclenché sur push du tag 'ext'
- Build et push de l'image sdk_storage:ext
- Configuration SSH et Docker registry
2025-09-20 22:22:18 +00:00
4NK Dev
a2c19e7e9e Externalize configuration and improve Docker compatibility
- Add STORAGE_DIR and PORT environment variables
- Update tests to use 0.0.0.0 instead of 127.0.0.1 for Docker compatibility
- Update documentation and changelog
2025-09-20 21:24:17 +00:00
4NK Dev
f32fa316dc feat: externaliser la configuration et remplacer 127.0.0.1 par 0.0.0.0
- Ajout des variables d'environnement STORAGE_DIR et PORT
- Remplacement de 127.0.0.1 par 0.0.0.0 dans les tests
- Ajout de la documentation CONFIGURATION.md
- Mise à jour du CHANGELOG.md vers v0.2.3
- Amélioration de la compatibilité Docker
2025-09-20 20:49:52 +00:00
4NK Dev
0b834eb4cb docs: Documentation des améliorations récentes
- Installation des outils système dans le Dockerfile
- Optimisation de l'image Docker
- Configuration des services
- État actuel et prochaines étapes
2025-09-20 14:23:21 +00:00
4NK Dev
b904214499 feat: Ajout des outils (curl, git, wget, jq, telnet, wscat, tee, npm) dans l'image Docker 2025-09-20 12:11:11 +00:00
4NK Dev
97fbb5a2a5 fix faucet empty commitment 2025-09-20 08:52:15 +00:00
4NK Dev
ecf48cec7e ci: docker_tag=dev-test - Ajout documentation analyse
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 1m27s
2025-09-20 08:13:59 +00:00
omaroughriss
120a3dc8d0 Add CICD to storage
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 1m28s
2025-09-08 19:06:03 +02:00
44472bef1e Rework api to take and return binary directly without hex serialization
Some checks failed
CI / rust (push) Failing after 30s
2025-09-04 12:55:55 +02:00
f1021295f7 Add logging 2025-09-04 12:54:55 +02:00
Your Name
1c42b86f90 chore(release): bump to v0.2.2
Some checks failed
CI / rust (push) Failing after 29s
Docker Image / docker (push) Failing after 19s
Release / build-release (ubuntu-latest) (push) Failing after 1m2s
Release / build-release (windows-latest) (push) Has been cancelled
2025-08-26 14:24:04 +02:00
Your Name
7aebbca98d feat(health): add /health endpoint + tests + docs
Some checks failed
CI / rust (push) Failing after 30s
Docker Image / docker (push) Failing after 19s
2025-08-26 11:16:33 +02:00
Your Name
8f456f0cd5 chore-gitignore-update
Some checks failed
CI / rust (push) Failing after 30s
Docker Image / docker (push) Failing after 40s
2025-08-26 11:13:36 +02:00
Your Name
87e89317f5 chore(ci): CODEOWNERS + doc variables registre Docker
Some checks failed
CI / rust (push) Failing after 29s
Docker Image / docker (push) Failing after 18s
2025-08-26 11:05:40 +02:00
Your Name
7cc675c129 docs: ajout guide de release
Some checks failed
CI / rust (push) Failing after 30s
Docker Image / docker (push) Failing after 18s
2025-08-26 11:03:06 +02:00
Your Name
62a972594d test(api): couverture complète endpoints + docs contrats
Some checks failed
Docker Image / docker (push) Failing after 18s
CI / rust (push) Failing after 30s
Release / build-release (ubuntu-latest) (push) Failing after 57s
Release / build-release (windows-latest) (push) Has been cancelled
2025-08-26 10:34:11 +02:00
Your Name
0950da48d6 chore(release): update CHANGELOG for v0.2.0
Some checks failed
Release / build-release (ubuntu-latest) (push) Failing after 1m8s
CI / rust (push) Failing after 29s
Docker Image / docker (push) Failing after 18s
Release / build-release (windows-latest) (push) Has been cancelled
2025-08-26 10:26:01 +02:00
Your Name
790ffb4032 docs: api json spec
Some checks failed
CI / rust (push) Failing after 31s
Docker Image / docker (push) Failing after 22s
2025-08-26 10:23:15 +02:00
Your Name
53c6301be2 docs: ajout des pages alignées avec le template 4NK
Some checks failed
CI / rust (push) Failing after 30s
Docker Image / docker (push) Failing after 19s
2025-08-26 10:19:55 +02:00
Your Name
6907e4baf1 feat(docker): Dockerfile, .dockerignore, CI docker, docs
Some checks failed
CI / rust (push) Failing after 30s
Docker Image / docker (push) Failing after 1m24s
2025-08-26 10:17:35 +02:00
Your Name
50e0b97a7f Alignement template 4NK: lib.rs, docs/, tests/, OSS files, .gitea, .cursor, release CI, .4nk-sync.yml
Some checks failed
CI / rust (push) Failing after 1m5s
2025-08-26 10:13:06 +02:00
21 changed files with 1586 additions and 299 deletions

17
.4nk-sync.yml Normal file
View File

@ -0,0 +1,17 @@
template:
repo: nicolas.cantu/4NK_project_template
host: git.4nkweb.com
sync:
include:
- .gitea/
- .cursor/
- docs/
- AGENTS.md
- CONTRIBUTING.md
- CODE_OF_CONDUCT.md
- SECURITY.md
- CHANGELOG.md
exclude:
- target/
- storage/
- .git/

1
.ci-build Normal file
View File

@ -0,0 +1 @@
# CI Build Trigger

2
.ci-trigger Normal file
View File

@ -0,0 +1,2 @@
# CI Trigger
# CI Trigger Sun Sep 21 19:57:53 UTC 2025

95
.cursorignore Normal file
View File

@ -0,0 +1,95 @@
# 4NK Environment - Git Ignore
# ============================
confs/
# Dossiers de sauvegarde des scripts
**/backup/
**/*backup*
**/.cargo/
# Fichiers temporaires
**/*.tmp*
**/*.temp*
**/*.log*
**/*.pid*
# Fichiers de configuration locale
**/*.env*
**/*.conf*
**/*.yaml*
**/*.yml*
**/*.ini*
**/*.json*
**/*.toml*
**/*.lock*
# Données et logs
**/*.logs*
**/*.data
*.db
*.sqlite
# Certificats et clés
**/*.key
**/*.pem
**/*.crt
**/*.p12
**/*.pfx
ssl/
certs/
# Docker
**/*.docker*
# Cache et build
**/node_modules/
**/dist/
**/build/
**/target/
**/.next/
**/.turbo/
**/coverage/
**/.pytest_cache/
**/.cache/
**/.pnpm-store/
**/.venv/
**/vendor/
**/*.*.o
**/*.so
**/*.dylib
# IDE et éditeurs
**/*.vscode/
**/*.idea/
**/*.swp
**/*.swo
**/*~
# OS
**/*.DS_Store
**/*Thumbs.db
**/*tmp*
# Git
**/*.git/
**/*.orig*
# Backup des projets existants
**/*backup*
**/backups/
**/*backups*
**/*wallet*
**/*keys*
**/*node_modules*
**/*cursor*
**/*pid*
**/*next*
# Dossiers de logs communs
log/
logs/
**/log/
**/logs/

166
.cursorrules Normal file
View File

@ -0,0 +1,166 @@
# Règles globales Cursor pour les projets
## Principes généraux
- Lire impérativement le fichier `.cursorrules` au démarrage de chaque session.
- Lire tous les fichiers du dossier `docs/`, le code et les paramètres avant de commencer.
- Poser des questions et proposer des améliorations si nécessaire.
- Ajouter les leçons apprises à ce fichier `.cursorrules`.
- Écrire des documents complets et exhaustifs.
- Respecter strictement les règles de lint du Markdown.
- Préférer toujours un shell **bash** à PowerShell.
- Fermer et relancer le terminal avant chaque utilisation.
- Si le terminal est interrompu, analyser la commande précédente (interruption probablement volontaire).
- Exécuter automatiquement les étapes de résolution de problème.
- Expliquer les commandes complexes avant de les lancer.
- Compiler régulièrement et corriger toutes les erreurs avant de passer à létape suivante.
- Tester, documenter, compiler, aligner tag git, changelog et version avant déploiement et push.
- Utiliser `docx2txt` pour lire les fichiers `.docx`.
- Ajouter automatiquement les dépendances et rechercher systématiquement les dernières versions.
- Faire des commandes simples et claires en plusieurs étapes.
- Vérifie toujours tes hypothèses avant de commencer.
- N'oublie jamais qu'après la correction d'un problème, il faut corriger toutes les erreurs qui peuvent en découler.
## Organisation des fichiers et répertoires
- Scripts regroupés dans `scripts/`
- Configurations regroupées dans `confs/`
- Journaux regroupés dans `logs/`
- Répertoires obligatoires :
- `docs/` : documentation de toute fonctionnalité ajoutée, modifiée, supprimée ou découverte.
- `tests/` : tests liés à toute fonctionnalité ajoutée, modifiée, supprimée ou découverte.
- Remplacer les résumés (`RESUME`) par des mises à jour dans `docs/`.
## Configuration critique des services
- Mempool du réseau signet :
`https://mempool2.4nkweb.com/fr/docs/api/rest`
## Développement et sécurité
- Ne jamais committer de clés privées ou secrets.
- Utiliser des variables denvironnement pour les données sensibles.
- Définir correctement les dépendances Docker avec healthchecks.
- Utiliser les URLs de service Docker Compose (`http://service_name:port`).
- Documenter toutes les modifications importantes dans `docs/`.
- Externaliser au maximum les variables denvironnement.
- Toujours utiliser une clé SSH pour cloner les dépôts.
- Monter en version les dépôts au début du travail.
- Pousser les tags docker `ext` via la CI sur `git.4nkweb.com`.
- Corriger systématiquement les problèmes, même mineurs, sans contournement.
## Scripts (règles critiques)
- Vérifier lexistence dun script dans `scripts/` avant toute action.
- Utiliser les scripts existants plutôt que des commandes directes.
- Ne jamais créer plusieurs versions ou noms de scripts.
- Améliorer lexistant au lieu de créer des variantes (`startup-v2.sh`, etc.).
## Images Docker (règles critiques)
- Ajouter systématiquement `apt update && apt upgrade` dans les Dockerfiles.
- Installer en arrière-plan dans les images Docker :
`curl, git, sed, awk, nc, wget, jq, telnet, tee, wscat, ping, npm (dernière version)`
- Appliquer à tous les Dockerfiles et `docker-compose.yml`.
- N'utilise pas les version test ou dev ou ext-dev mais toujours les version ext, relance leur compilation si nécessaire
## Fichiers de configuration (règles critiques)
- Vérifier lécriture effective après chaque modification.
- Fichiers concernés : `nginx.conf`, `bitcoin.conf`, `package.json`, `Cargo.toml`.
- Utiliser `cat`, `jq` ou vérificateurs de syntaxe.
## Connexion au réseau Bitcoin signet
Commande unique et obligatoire :
```bash
docker exec bitcoin-signet bitcoin-cli -signet -rpccookiefile=/home/bitcoin/.bitcoin/signet/.cookie getblockchaininfo
````
## Connexion au relay/faucet bootstrap
* Test via WSS : `wss://dev3.4nkweb.com/ws/`
* Envoi Faucet, réponse attendue avec `NewTx` (tx hex et tweak\_data).
## Debug
* Automatiser dans le code toute solution validée.
* Pérenniser les retours dexpérience dans code et paramètres.
* Compléter les tests pour éviter les régressions.
* Toujours tout corriger sans contournement, sans desactivation, sans simplification
## Nginx
* Tous les fichiers dans `conf/ngnix` doivent être mappés avec ceux du serveur.
## Minage (règles critiques)
* Toujours valider les adresses utilisées (adresses TSP non reconnues).
* Utiliser uniquement des adresses Bitcoin valides (bech32m).
* Vérifier que le minage génère des blocs avec transactions, pas uniquement coinbase.
* Surveiller les logs du minage pour détecter les erreurs dadresse.
* Vérifier la propagation via le mempool externe.
## Mempool externe
* Utiliser `https://mempool2.4nkweb.com` pour vérifier les transactions.
* Vérifier la synchronisation entre réseau local et externe.
## Données et modèles
* Utiliser les fichiers CSV comme base des modèles de données.
* Être attentif aux en-têtes multi-lignes.
* Confirmer la structure comprise et demander définition de toutes les colonnes.
* Corriger automatiquement incohérences de type.
## Implémentation et architecture
* Code splitting avec `React.lazy` et `Suspense`.
* Centraliser létat avec Redux ou Context API.
* Créer une couche dabstraction pour les services de données.
* Aller systématiquement au bout dune implémentation.
## Préparation open source
Chaque projet doit être prêt pour un dépôt sur `git.4nkweb.com` :
* Inclure : `LICENSE` (MIT, Apache 2.0 ou GPL), `CONTRIBUTING.md`, `CHANGELOG.md`, `CODE_OF_CONDUCT.md`.
* Aligner documentation et tests avec `4NK_node`.
## Versioning et documentation
* Mettre à jour documentation et tests systématiquement.
* Gérer versioning avec changelog.
* Demander validation avant tag.
* Documenter les hypothèses testées dans un REX technique.
* Tester avant tout commit.
* Tester les buildsavant tout tag.
## Bonnes pratiques de confidentialité et sécurité
### Docker
- Ne jamais stocker de secrets (clés, tokens, mots de passe) dans les Dockerfiles ou docker-compose.yml.
- Utiliser des fichiers `.env` sécurisés (non commités avec copie en .env.example) pour toutes les variables sensibles.
- Ne pas exécuter de conteneurs avec lutilisateur root, privilégier un utilisateur dédié.
- Limiter les capacités des conteneurs (option `--cap-drop`) pour réduire la surface dattaque.
- Scanner régulièrement les images Docker avec un outil de sécurité (ex : Trivy, Clair).
- Mettre à jour en continu les images de base afin déliminer les vulnérabilités.
- Ne jamais exposer de ports inutiles.
- Restreindre les volumes montés au strict nécessaire.
- Utiliser des réseaux Docker internes pour la communication inter-containers.
- Vérifier et tenir à jour les .dockerignore.
### Git
- Ne jamais committer de secrets, clés ou identifiants (même temporairement).
- Configurer des hooks Git (pre-commit) pour détecter automatiquement les secrets et les failles.
- Vérifier lhistorique (`git log`, `git filter-repo`) pour sassurer quaucune information sensible na été poussée.
- Signer les commits avec GPG pour garantir lauthenticité.
- Utiliser systématiquement SSH pour les connexions à distance.
- Restreindre les accès aux dépôts (principes du moindre privilège).
- Documenter les changements sensibles dans `CHANGELOG.md`.
- Ne jamais pousser directement sur `main` ou `master`, toujours passer par des branches de feature ou PR.
- Vérifier et tenir à jour les .gitignore.
- Vérifier et tenir à jour les .gitkeep.
- Vérifier et tenir à jour les .gitattributes.
### Cursor
- Toujours ouvrir une session en commençant par relire le fichier `.cursorrules`.
- Vérifier que Cursor ne propose pas de commit contenant des secrets ou fichiers sensibles.
- Ne pas exécuter dans Cursor de commandes non comprises ou copiées sans vérification.
- Préférer lutilisation de scripts audités dans `scripts/` plutôt que des commandes directes dans Cursor.
- Fermer et relancer Cursor régulièrement pour éviter des contextes persistants non désirés.
- Ne jamais partager le contenu du terminal ou des fichiers sensibles via Cursor en dehors du périmètre du projet.
- Vérifier et tenir à jour les .cursorrules.
- Vérifier et tenir à jour les .cursorignore.

95
.dockerignore Normal file
View File

@ -0,0 +1,95 @@
# 4NK Environment - Git Ignore
# ============================
confs/
# Dossiers de sauvegarde des scripts
**/backup/
**/*backup*
**/.cargo/
# Fichiers temporaires
**/*.tmp*
**/*.temp*
**/*.log*
**/*.pid*
# Fichiers de configuration locale
**/*.env*
**/*.conf*
**/*.yaml*
**/*.yml*
**/*.ini*
**/*.json*
**/*.toml*
**/*.lock*
# Données et logs
**/*.logs*
**/*.data
*.db
*.sqlite
# Certificats et clés
**/*.key
**/*.pem
**/*.crt
**/*.p12
**/*.pfx
ssl/
certs/
# Docker
**/*.docker*
# Cache et build
**/node_modules/
**/dist/
**/build/
**/target/
**/.next/
**/.turbo/
**/coverage/
**/.pytest_cache/
**/.cache/
**/.pnpm-store/
**/.venv/
**/vendor/
**/*.*.o
**/*.so
**/*.dylib
# IDE et éditeurs
**/*.vscode/
**/*.idea/
**/*.swp
**/*.swo
**/*~
# OS
**/*.DS_Store
**/*Thumbs.db
**/*tmp*
# Git
**/*.git/
**/*.orig*
# Backup des projets existants
**/*backup*
**/backups/
**/*backups*
**/*wallet*
**/*keys*
**/*node_modules*
**/*cursor*
**/*pid*
**/*next*
# Dossiers de logs communs
log/
logs/
**/log/
**/logs/

View File

@ -0,0 +1,60 @@
name: build-and-push-ext
on:
push:
tags:
- ext
jobs:
build_push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Prepare SSH agent (optional)
shell: bash
run: |
set -euo pipefail
eval "$(ssh-agent -s)"
if [ -n "${{ secrets.SSH_PRIVATE_KEY || '' }}" ]; then
echo "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' | ssh-add - >/dev/null 2>&1 || true
fi
mkdir -p ~/.ssh
ssh-keyscan git.4nkweb.com >> ~/.ssh/known_hosts 2>/dev/null || true
echo "SSH agent ready: $SSH_AUTH_SOCK"
# Rendre l'agent dispo aux steps suivants
echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV"
echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV"
- name: Docker login (git.4nkweb.com)
shell: bash
env:
REG_USER: ${{ secrets.USER }}
REG_TOKEN: ${{ secrets.TOKEN }}
run: |
set -euo pipefail
echo "$REG_TOKEN" | docker login git.4nkweb.com -u "$REG_USER" --password-stdin
- name: Build image (target ext)
shell: bash
env:
DOCKER_BUILDKIT: "1"
run: |
set -euo pipefail
if [ -n "${SSH_AUTH_SOCK:-}" ]; then
docker build --ssh default \
-t git.4nkweb.com/4nk/sdk_storage:ext \
-f Dockerfile .
else
echo "SSH_AUTH_SOCK non défini: l'agent SSH n'est pas disponible. Assurez-vous de définir secrets.SSH_PRIVATE_KEY."
exit 1
fi
- name: Push image
shell: bash
run: |
set -euo pipefail
docker push git.4nkweb.com/4nk/sdk_storage:ext

97
.gitignore vendored
View File

@ -1,2 +1,95 @@
/target
/storage
# 4NK Environment - Git Ignore
# ============================
confs/
# Dossiers de sauvegarde des scripts
**/backup/
**/*backup*
**/.cargo/
# Fichiers temporaires
**/*.tmp*
**/*.temp*
**/*.log*
**/*.pid*
# Fichiers de configuration locale
**/*.env*
**/*.conf*
**/*.yaml*
**/*.yml*
**/*.ini*
**/*.json*
**/*.toml*
**/*.lock*
# Données et logs
**/*.logs*
**/*.data
*.db
*.sqlite
# Certificats et clés
**/*.key
**/*.pem
**/*.crt
**/*.p12
**/*.pfx
ssl/
certs/
# Docker
**/*.docker*
# Cache et build
**/node_modules/
**/dist/
**/build/
**/target/
**/.next/
**/.turbo/
**/coverage/
**/.pytest_cache/
**/.cache/
**/.pnpm-store/
**/.venv/
**/vendor/
**/*.*.o
**/*.so
**/*.dylib
# IDE et éditeurs
**/*.vscode/
**/*.idea/
**/*.swp
**/*.swo
**/*~
# OS
**/*.DS_Store
**/*Thumbs.db
**/*tmp*
# Git
**/*.git/
**/*.orig*
# Backup des projets existants
**/*backup*
**/backups/
**/*backups*
**/*wallet*
**/*keys*
**/*node_modules*
**/*cursor*
**/*pid*
**/*next*
# Dossiers de logs communs
log/
logs/
**/log/
**/logs/

24
CHANGELOG.md Normal file
View File

@ -0,0 +1,24 @@
# Changelog
## 0.2.3
- **Configuration externalisée** : Ajout des variables d'environnement `STORAGE_DIR` et `PORT`
- **Tests améliorés** : Remplacement de `127.0.0.1` par `0.0.0.0` dans les tests pour compatibilité Docker
- **Documentation** : Ajout de `docs/CONFIGURATION.md` avec guide des variables d'environnement
- **Flexibilité** : Configuration plus flexible pour les déploiements Docker et conteneurs
## 0.2.0
- Ajout Dockerfile multi-stage et `.dockerignore`
- CI: workflows build/test, release et build/push Docker
- Documentation étendue dans `docs/` (architecture, guides, API JSON)
- Tests renforcés: conflit de clé et suppression des expirés
## 0.2.2
- Endpoint `/health` ajouté et testé
- Spec JSON/API et docs monitoring mises à jour
- CI Docker: tagging multi-tags (`latest` et `vX.Y.Z` sur tag)
- Version et déploiements alignés
## 0.1.0
- Refactor vers `src/lib.rs` et service `StorageService`
- Ajout `docs/` (README) et `tests/` (test intégration service)
- API HTTP Tide conservée; nettoyage TTL périodique 60s

3
CODEOWNERS copy Normal file
View File

@ -0,0 +1,3 @@
# Code owners par défaut du dépôt
# Ajustez ces lignes si vous utilisez des équipes/alias différents.
* @nicolas.cantu

9
CODE_OF_CONDUCT copy.md Normal file
View File

@ -0,0 +1,9 @@
# Code de Conduite
Nous nous engageons à offrir une communauté ouverte, accueillante et respectueuse.
- Pas de harcèlement.
- Respect des avis techniques et des personnes.
- Suivre les consignes des mainteneurs.
Signalez tout problème via les issues du dépôt.

8
CONTRIBUTING copy.md Normal file
View File

@ -0,0 +1,8 @@
# Contribuer à sdk_storage
Merci de proposer des issues et des Pull Requests.
- Discutez via une issue avant une modification majeure.
- Travaillez sur une branche `feature/...`.
- Ajoutez systématiquement des tests (`tests/`) et mettez à jour la documentation (`docs/`).
- Assurez-vous que `cargo fmt`, `cargo clippy` et `cargo test` passent localement.

615
Cargo.lock generated
View File

@ -2,6 +2,21 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aead"
version = "0.3.2"
@ -56,6 +71,15 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -236,6 +260,18 @@ dependencies = [
"pin-project-lite 0.2.15",
]
[[package]]
name = "async-native-tls"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33"
dependencies = [
"async-std",
"native-tls",
"thiserror",
"url",
]
[[package]]
name = "async-process"
version = "2.3.0"
@ -365,6 +401,21 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [
"addr2line",
"cfg-if 1.0.0",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
]
[[package]]
name = "base-x"
version = "0.2.11"
@ -507,6 +558,17 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "config"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
dependencies = [
"lazy_static",
"nom",
"serde",
]
[[package]]
name = "const_fn"
version = "0.4.10"
@ -536,6 +598,16 @@ dependencies = [
"version_check",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -557,6 +629,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
@ -592,6 +673,33 @@ dependencies = [
"cipher",
]
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if 1.0.0",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "deadpool"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d126179d86aee4556e54f5f3c6bf6d9884e7cc52cef82f77ee6f90a7747616d"
dependencies = [
"async-trait",
"config",
"crossbeam-queue",
"num_cpus",
"serde",
"tokio",
]
[[package]]
name = "digest"
version = "0.9.0"
@ -618,6 +726,19 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "erased-serde"
version = "0.4.5"
@ -696,6 +817,21 @@ dependencies = [
"web-sys",
]
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -705,6 +841,21 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
@ -712,6 +863,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
@ -720,6 +872,17 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
@ -765,6 +928,12 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
@ -777,9 +946,13 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite 0.2.15",
"pin-utils",
"slab",
@ -817,6 +990,18 @@ dependencies = [
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if 1.0.0",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
]
[[package]]
name = "ghash"
version = "0.3.1"
@ -827,6 +1012,12 @@ dependencies = [
"polyval",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "gloo-timers"
version = "0.3.0"
@ -839,6 +1030,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hermit-abi"
version = "0.3.9"
@ -851,6 +1048,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "hex"
version = "0.4.3"
@ -893,8 +1096,14 @@ version = "6.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1947510dc91e2bf586ea5ffb412caad7673264e14bb39fb9078da114a94ce1a5"
dependencies = [
"async-h1",
"async-native-tls",
"async-std",
"async-trait",
"cfg-if 1.0.0",
"dashmap",
"deadpool",
"futures",
"http-types",
"log",
]
@ -927,6 +1136,12 @@ version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
[[package]]
name = "humantime"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
[[package]]
name = "iana-time-zone"
version = "0.1.61"
@ -1115,6 +1330,28 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "io-uring"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [
"bitflags 2.6.0",
"cfg-if 1.0.0",
"libc",
]
[[package]]
name = "is-terminal"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi 0.5.2",
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "itoa"
version = "1.0.11"
@ -1140,10 +1377,29 @@ dependencies = [
]
[[package]]
name = "libc"
version = "0.2.164"
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lexical-core"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"bitflags 1.3.2",
"cfg-if 1.0.0",
"ryu",
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "linux-raw-sys"
@ -1163,6 +1419,16 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
[[package]]
name = "lock_api"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.22"
@ -1179,6 +1445,70 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.59.0",
]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "5.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b"
dependencies = [
"lexical-core",
"memchr",
"version_check",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@ -1188,6 +1518,25 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi 0.5.2",
"libc",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.20.2"
@ -1200,12 +1549,69 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags 2.6.0",
"cfg-if 1.0.0",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot_core"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -1261,6 +1667,12 @@ dependencies = [
"futures-io",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "polling"
version = "2.8.0"
@ -1336,6 +1748,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.7.3"
@ -1407,12 +1825,56 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "redox_syscall"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "regex"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "route-recognizer"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56770675ebc04927ded3e60633437841581c285dc6236109ea25fbf3beb7b59e"
[[package]]
name = "rustc-demangle"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]]
name = "rustc_version"
version = "0.2.3"
@ -1455,17 +1917,58 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sdk_storage"
version = "0.1.0"
version = "0.2.2"
dependencies = [
"async-std",
"env_logger",
"hex",
"serde",
"serde_json",
"surf",
"tempfile",
"tide",
]
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.6.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "0.9.0"
@ -1628,6 +2131,12 @@ dependencies = [
"version_check",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stdweb"
version = "0.4.20"
@ -1683,6 +2192,28 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "surf"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718b1ae6b50351982dedff021db0def601677f2120938b070eadb10ba4038dd7"
dependencies = [
"async-native-tls",
"async-std",
"async-trait",
"cfg-if 1.0.0",
"futures-util",
"getrandom 0.2.15",
"http-client",
"http-types",
"log",
"mime_guess",
"once_cell",
"pin-project-lite 0.2.15",
"serde",
"serde_json",
]
[[package]]
name = "sval"
version = "2.13.2"
@ -1794,6 +2325,29 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "tempfile"
version = "3.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
dependencies = [
"cfg-if 1.0.0",
"fastrand 2.2.0",
"getrandom 0.3.3",
"once_cell",
"rustix 0.38.41",
"windows-sys 0.59.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@ -1885,6 +2439,20 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tokio"
version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [
"backtrace",
"io-uring",
"libc",
"mio",
"pin-project-lite 0.2.15",
"slab",
]
[[package]]
name = "tracing"
version = "0.1.40"
@ -1913,6 +2481,12 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicase"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-ident"
version = "1.0.13"
@ -1989,6 +2563,12 @@ dependencies = [
"sval_serde",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
@ -2013,6 +2593,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
@ -2108,6 +2697,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@ -2271,6 +2869,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "write16"
version = "1.0.0"

View File

@ -1,11 +1,16 @@
[package]
name = "sdk_storage"
version = "0.1.0"
version = "0.2.2"
edition = "2021"
[dependencies]
tide = "0.16.0"
async-std = { version = "1.8.0", features = ["attributes"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
hex = "0.4.3"
tide = "0.16"
async-std = { version = "1", features = ["attributes"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
hex = "0.4"
env_logger = "0.10"
[dev-dependencies]
tempfile = "3"
surf = { version = "2", default-features = false, features = ["h1-client"] }

24
Dockerfile Normal file
View File

@ -0,0 +1,24 @@
# syntax=docker/dockerfile:1
FROM rust:1 as builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN cargo build --release
FROM debian:bookworm-slim
RUN useradd -m -u 1000 appuser && \
apt-get update && apt-get upgrade -y && \
apt-get install -y --fix-missing \
ca-certificates curl jq git \
net-tools iputils-ping dnsutils \
netcat-openbsd telnet procps && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /app
COPY --from=builder /app/target/release/sdk_storage /usr/local/bin/sdk_storage
RUN mkdir -p /app/storage && chown -R appuser:appuser /app
RUN mkdir -p /var/log/sdk_storage && chown -R appuser:appuser /var/log/sdk_storage
USER appuser
EXPOSE 8081
ENV RUST_LOG=info
ENTRYPOINT ["/bin/sh","-c","exec /usr/local/bin/sdk_storage --permanent >> /var/log/sdk_storage/stdout.log 2>&1"]

19
LICENSE copy Normal file
View File

@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

43
README.md Normal file
View File

@ -0,0 +1,43 @@
# sdk_storage
Voir la documentation détaillée dans `docs/`.
## Démarrage rapide
- Construire: `cargo build`
- Lancer: `cargo run -- --permanent` (clé sans TTL = permanente)
- Tester: `cargo test`
## API
- POST `/store` { key(hex64), value(hex), ttl? (s) }
- GET `/retrieve/:key`
## Contribution
Voir `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`.
## Licence
Voir `LICENSE` (MIT).
### Documentation centralisée
- Voir `/home/debian/4NK_env/docs/sdk_storage/`
## Intégration dans lenvironnement 4NK
Ce service fournit un stockage temporaire utilisé par les applications 4NK. Les hôtes et URL externes sont injectés via variables denvironnement et fichiers de configuration, sans référence à des machines spécifiques. Voir la documentation centrale pour lorchestration:
- `docs/DEEP_ARCHITECTURE_ANALYSIS.md`
- `docs/TECHNICAL_REFERENCE.md`
- `docs/DEPLOYMENT_GUIDE.md`
## 📋 Fichiers centralisés
Les fichiers suivants sont centralisés dans le dépôt principal `4NK_env` :
- `CODE_OF_CONDUCT.md` - Code de conduite
- `CODEOWNERS` - Propriétaires du code
- `CONTRIBUTING.md` - Guide de contribution
- `LICENSE` - Licence du projet
Voir : [`4NK_env/CODE_OF_CONDUCT.md`](../../CODE_OF_CONDUCT.md), [`4NK_env/CODEOWNERS`](../../CODEOWNERS), [`4NK_env/CONTRIBUTING.md`](../../CONTRIBUTING.md), [`4NK_env/LICENSE`](../../LICENSE)

5
SECURITY.md Normal file
View File

@ -0,0 +1,5 @@
# Politique de Sécurité
- Ne divulguez pas publiquement les vulnérabilités.
- Ouvrez une issue privée si possible ou contactez les mainteneurs.
- Merci d'inclure des étapes de reproduction et l'impact.

263
src/lib.rs Normal file
View File

@ -0,0 +1,263 @@
use async_std::fs::{create_dir_all, read_dir, read_to_string, remove_file, File};
use async_std::io::WriteExt;
use async_std::path::Path;
use async_std::stream::StreamExt;
use serde::{Deserialize, Serialize};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tide::{log, Request, Response, StatusCode};
#[derive(Clone, Debug)]
pub struct StorageService {
storage_dir: String,
}
impl StorageService {
pub fn new<S: Into<String>>(storage_dir: S) -> Self {
Self {
storage_dir: storage_dir.into(),
}
}
fn get_file_path(&self, key: &str) -> String {
let dir_name = format!("{}/{}", self.storage_dir, &key[..2]);
let file_path = format!("{}/{}", dir_name, &key[2..]);
file_path
}
pub async fn store_data(
&self,
key: &str,
value: &[u8],
expires_at: Option<SystemTime>,
) -> Result<(), tide::Error> {
let file_name = self.get_file_path(key);
let file_path = Path::new(&file_name);
if file_path.exists().await {
return Err(tide::Error::from_str(StatusCode::Conflict, "Key already exists"));
}
create_dir_all(file_path.parent().ok_or(tide::Error::from_str(
StatusCode::InternalServerError,
"File path doesn't have parent",
))?)
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
let metadata_path = format!("{}.meta", file_name);
let mut file = File::create(&file_path)
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
file.write_all(value)
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
let metadata = Metadata {
expires_at: expires_at.map(system_time_to_unix),
};
let metadata_json = serde_json::to_string(&metadata)
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
let mut meta_file = File::create(&metadata_path)
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
meta_file
.write_all(metadata_json.as_bytes())
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
Ok(())
}
pub async fn retrieve_data(&self, key: &str) -> Result<Vec<u8>, String> {
let file_path = format!("{}/{}/{}", self.storage_dir, &key[..2], &key[2..]);
let mut file = File::open(&file_path)
.await
.map_err(|_| "Key not found.".to_string())?;
let mut buffer = Vec::new();
async_std::io::ReadExt::read_to_end(&mut file, &mut buffer)
.await
.map_err(|e| e.to_string())?;
Ok(buffer)
}
pub async fn cleanup_expired_files_once(&self) -> Result<(), String> {
let mut entries = read_dir(&self.storage_dir)
.await
.map_err(|e| format!("Failed to read storage dir: {}", e))?;
let now = system_time_to_unix(SystemTime::now());
while let Some(entry) = entries.next().await {
let e = entry.map_err(|e| format!("entry returned error: {}", e))?;
let path = e.path();
if path.is_dir().await {
if let Ok(mut sub_entries) = read_dir(&path).await {
while let Some(sub_entry) = sub_entries.next().await {
if let Ok(sub_entry) = sub_entry {
let file_path = sub_entry.path();
if file_path.extension() == Some("meta".as_ref()) {
self.handle_file_cleanup(now, &file_path).await?;
}
}
}
}
}
}
Ok(())
}
async fn handle_file_cleanup(&self, now: u64, meta_path: &Path) -> Result<(), String> {
let meta_content = read_to_string(meta_path)
.await
.map_err(|e| format!("Failed to read metadata: {}", e.to_string()))?;
let metadata: Metadata = serde_json::from_str(&meta_content)
.map_err(|e| format!("Failed to parse metadata: {}", e.to_string()))?;
if metadata.expires_at.is_some() && metadata.expires_at.unwrap() < now {
let data_file_path = meta_path.with_extension("");
remove_file(&data_file_path)
.await
.map_err(|e| format!("Failed to remove data file: {}", e.to_string()))?;
remove_file(meta_path)
.await
.map_err(|e| format!("Failed to remove metadata file: {}", e.to_string()))?;
}
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Metadata {
pub expires_at: Option<u64>,
}
pub fn system_time_to_unix(system_time: SystemTime) -> u64 {
system_time
.duration_since(UNIX_EPOCH)
.expect("SystemTime before UNIX_EPOCH!")
.as_secs()
}
pub fn unix_to_system_time(unix_timestamp: u64) -> SystemTime {
UNIX_EPOCH + Duration::from_secs(unix_timestamp)
}
#[derive(Deserialize, Debug)]
pub struct StoreRequest {
pub key: String,
pub value: String,
pub ttl: Option<u64>,
}
#[derive(Serialize)]
pub struct ApiResponse { pub message: String }
#[derive(Serialize)]
pub struct RetrieveResponse { pub key: String, pub value: String }
pub async fn handle_health(_req: Request<StorageService>) -> tide::Result<Response> {
Ok(Response::builder(StatusCode::Ok)
.body(serde_json::to_value(&ApiResponse { message: "ok".into() })?)
.build())
}
pub async fn handle_store(mut req: Request<StorageService>, no_ttl_permanent: bool) -> tide::Result<Response> {
// Extract key from URL parameter
let key: String = req.param("key")?.to_string();
// Validate key format
if key.len() != 64 || !key.chars().all(|c| c.is_ascii_hexdigit()) {
return Ok(Response::builder(StatusCode::BadRequest)
.body("Invalid key: must be a 32 bytes hex string.".to_string())
.build());
}
// Get TTL from query parameter (optional)
let ttl: Option<u64> = req.url().query_pairs()
.find(|(key, _)| key == "ttl")
.and_then(|(_, value)| value.parse().ok());
log::info!("ttl: {:?}", ttl);
let live_for: Option<Duration> = if let Some(ttl) = ttl {
if ttl < 60 {
return Ok(Response::builder(StatusCode::BadRequest)
.body(format!("Invalid ttl: must be at least {} seconds.", 60))
.build());
} else if ttl > 31_536_000 {
return Ok(Response::builder(StatusCode::BadRequest)
.body(format!("Invalid ttl: must be at most {} seconds.", 31_536_000))
.build());
}
Some(Duration::from_secs(ttl))
} else if no_ttl_permanent {
None
} else {
Some(Duration::from_secs(86_400))
};
let expires_at: Option<SystemTime> = match live_for {
Some(lf) => Some(
SystemTime::now()
.checked_add(lf)
.ok_or(tide::Error::from_str(StatusCode::BadRequest, "Invalid ttl"))?
),
None => None,
};
// Read binary data directly from request body
let value_bytes = match req.body_bytes().await {
Ok(bytes) => bytes,
Err(e) => {
return Ok(Response::builder(StatusCode::BadRequest)
.body(format!("Failed to read request body: {}", e))
.build());
}
};
log::info!("received {} bytes", value_bytes.len());
let svc = req.state();
match svc.store_data(&key, &value_bytes, expires_at).await {
Ok(()) => Ok(Response::builder(StatusCode::Ok)
.body(serde_json::to_value(&ApiResponse {
message: "Data stored successfully.".to_string(),
})?)
.build()),
Err(e) => Ok(Response::builder(e.status())
.body(serde_json::to_value(&ApiResponse {
message: e.to_string(),
})?)
.build()),
}
}
pub async fn handle_retrieve(req: Request<StorageService>) -> tide::Result<Response> {
let key: String = req.param("key")?.to_string();
if key.len() != 64 || !key.chars().all(|c| c.is_ascii_hexdigit()) {
return Ok(Response::builder(StatusCode::BadRequest)
.body("Invalid key: must be a 32 bytes hex string.".to_string())
.build());
}
let svc = req.state();
match svc.retrieve_data(&key).await {
Ok(value) => {
Ok(Response::builder(StatusCode::Ok)
.header("Content-Type", "application/octet-stream")
.body(value)
.build())
}
Err(e) => Ok(Response::builder(StatusCode::NotFound).body(e).build()),
}
}
pub fn create_app(no_ttl_permanent: bool, storage_dir: impl Into<String>) -> tide::Server<StorageService> {
let svc = StorageService::new(storage_dir);
let mut app = tide::with_state(svc);
app.at("/health").get(handle_health);
app.at("/store/:key").post(move |req| handle_store(req, no_ttl_permanent));
app.at("/retrieve/:key").get(handle_retrieve);
app
}

View File

@ -1,308 +1,55 @@
use async_std::fs::{create_dir_all, read_dir, read_to_string, remove_file, File};
use serde::{Deserialize, Serialize};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::env;
use async_std::io::WriteExt;
use async_std::path::Path;
use async_std::stream::StreamExt;
use async_std::task;
use tide::{Request, Response, StatusCode};
use async_std::fs::create_dir_all;
use sdk_storage::{StorageService, create_app};
use tide::log;
const STORAGE_DIR: &str = "./storage";
const PORT: u16 = 8081;
const MIN_TTL: u64 = 60; // 1 minute
const DEFAULT_TTL: u64 = 86400; // 1 day
const MAX_TTL: u64 = 31_536_000; // 1 year, to be discussed
// Configuration via variables d'environnement avec valeurs par défaut
const DEFAULT_STORAGE_DIR: &str = "./storage";
const DEFAULT_PORT: u16 = 8080;
const DEFAULT_TTL: u64 = 86400;
/// Scans storage and removes expired files
async fn cleanup_expired_files() {
loop {
// Traverse storage directory
let mut entries = match read_dir(STORAGE_DIR).await {
Ok(entry) => entry,
Err(e) => {
eprintln!("Failed to read storage dir: {}", e);
task::sleep(Duration::from_secs(60)).await;
continue;
}
};
let now = system_time_to_unix(SystemTime::now());
while let Some(entry) = entries.next().await {
let e = match entry {
Ok(e) => e,
Err(e) => {
eprintln!("entry returned error: {}", e);
continue;
}
};
let path = e.path();
if path.is_dir().await {
if let Ok(mut sub_entries) = read_dir(&path).await {
while let Some(sub_entry) = sub_entries.next().await {
if let Ok(sub_entry) = sub_entry {
let file_path = sub_entry.path();
if file_path.extension() == Some("meta".as_ref()) {
if let Err(err) = handle_file_cleanup(now, &file_path).await {
eprintln!("Error cleaning file {:?}: {}", file_path, err);
}
}
}
}
}
}
}
// Sleep for 1 minute before next cleanup
task::sleep(Duration::from_secs(60)).await;
}
}
#[derive(Debug, Deserialize, Serialize)]
struct Metadata {
expires_at: Option<u64>,
}
/// Converts a `SystemTime` to a UNIX timestamp (seconds since UNIX epoch).
fn system_time_to_unix(system_time: SystemTime) -> u64 {
system_time
.duration_since(UNIX_EPOCH)
.expect("SystemTime before UNIX_EPOCH!")
.as_secs()
}
/// Converts a UNIX timestamp (seconds since UNIX epoch) back to `SystemTime`.
fn unix_to_system_time(unix_timestamp: u64) -> SystemTime {
UNIX_EPOCH + Duration::from_secs(unix_timestamp)
}
#[derive(Deserialize)]
struct StoreRequest {
key: String,
value: String,
ttl: Option<u64>,
}
#[derive(Serialize)]
struct ApiResponse {
message: String,
}
#[derive(Serialize)]
struct RetrieveResponse {
key: String,
value: String,
}
async fn get_file_path(key: &str) -> String {
let dir_name = format!("{}/{}", STORAGE_DIR, &key[..2]);
let file_path = format!("{}/{}", dir_name, &key[2..]);
file_path
}
/// Store data on the filesystem
async fn store_data(key: &str, value: &[u8], expires_at: Option<SystemTime>) -> Result<(), tide::Error> {
let file_name = get_file_path(key).await;
let file_path = Path::new(&file_name);
// Check if key exists
if file_path.exists().await {
return Err(tide::Error::from_str(
StatusCode::Conflict,
"Key already exists",
));
}
create_dir_all(file_path.parent().ok_or(tide::Error::from_str(
StatusCode::InternalServerError,
"File path doesn't have parent",
))?)
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
let metadata_path = format!("{}.meta", file_name);
let mut file = File::create(&file_path)
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
file.write_all(value)
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
let metadata = Metadata {
expires_at: expires_at.map(|e| system_time_to_unix(e)),
};
let metadata_json = serde_json::to_string(&metadata)
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
let mut meta_file = File::create(&metadata_path)
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
meta_file
.write_all(metadata_json.as_bytes())
.await
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
Ok(())
}
async fn retrieve_data(key: &str) -> Result<Vec<u8>, String> {
let file_path = format!("{}/{}/{}", STORAGE_DIR, &key[..2], &key[2..]);
let mut file = File::open(&file_path)
.await
.map_err(|_| "Key not found.".to_string())?;
let mut buffer = Vec::new();
async_std::io::ReadExt::read_to_end(&mut file, &mut buffer)
.await
.map_err(|e| e.to_string())?;
Ok(buffer)
}
/// Handler for the /store endpoint
async fn handle_store(mut req: Request<()>, no_ttl_permanent: bool) -> tide::Result<Response> {
// Parse the JSON body
let data: StoreRequest = match req.body_json().await {
Ok(data) => data,
Err(e) => {
return Ok(Response::builder(StatusCode::BadRequest)
.body(format!("Invalid request: {}", e))
.build());
}
};
// Validate the key
if data.key.len() != 64 || !data.key.chars().all(|c| c.is_ascii_hexdigit()) {
return Ok(Response::builder(StatusCode::BadRequest)
.body("Invalid key: must be a 32 bytes hex string.".to_string())
.build());
}
// Validate the ttl
let live_for: Option<Duration> = if let Some(ttl) = data.ttl {
if ttl < MIN_TTL {
return Ok(Response::builder(StatusCode::BadRequest)
.body(format!(
"Invalid ttl: must be at least {} seconds.",
MIN_TTL
))
.build());
} else if ttl > MAX_TTL {
return Ok(Response::builder(StatusCode::BadRequest)
.body(format!("Invalid ttl: must be at most {} seconds.", MAX_TTL))
.build());
}
Some(Duration::from_secs(ttl))
} else if no_ttl_permanent {
// When no_ttl_permanent is true, requests without TTL are permanent
None
} else {
Some(Duration::from_secs(DEFAULT_TTL))
};
let expires_at: Option<SystemTime> = if let Some(live_for) = live_for {
let now = SystemTime::now();
Some(now
.checked_add(live_for)
.ok_or(tide::Error::from_str(StatusCode::BadRequest, "Invalid ttl"))?)
} else {
None
};
// Decode the value from Base64
let value_bytes = match hex::decode(&data.value) {
Ok(value) => value,
Err(e) => {
return Ok(Response::builder(StatusCode::BadRequest)
.body(format!("Invalid request: {}", e))
.build());
}
};
// Store the data
match store_data(&data.key, &value_bytes, expires_at).await {
Ok(()) => Ok(Response::builder(StatusCode::Ok)
.body(serde_json::to_value(&ApiResponse {
message: "Data stored successfully.".to_string(),
})?)
.build()),
Err(e) => Ok(Response::builder(e.status())
.body(serde_json::to_value(&ApiResponse {
message: e.to_string(),
})?)
.build()),
}
}
async fn handle_retrieve(req: Request<()>) -> tide::Result<Response> {
let key: String = req.param("key")?.to_string();
if key.len() != 64 || !key.chars().all(|c| c.is_ascii_hexdigit()) {
return Ok(Response::builder(StatusCode::BadRequest)
.body("Invalid key: must be a 32 bytes hex string.".to_string())
.build());
}
match retrieve_data(&key).await {
Ok(value) => {
let encoded_value = hex::encode(value);
Ok(Response::builder(StatusCode::Ok)
.body(serde_json::to_value(&RetrieveResponse {
key,
value: encoded_value,
})?)
.build())
}
Err(e) => Ok(Response::builder(StatusCode::NotFound).body(e).build()),
}
}
/// Checks a metadata file and deletes the associated data file if expired
async fn handle_file_cleanup(now: u64, meta_path: &Path) -> Result<(), String> {
let meta_content = read_to_string(meta_path)
.await
.map_err(|e| format!("Failed to read metadata: {}", e.to_string()))?;
let metadata: Metadata = serde_json::from_str(&meta_content)
.map_err(|e| format!("Failed to parse metadata: {}", e.to_string()))?;
if metadata.expires_at.is_some() && metadata.expires_at.unwrap() < now {
let data_file_path = meta_path.with_extension("");
remove_file(&data_file_path)
.await
.map_err(|e| format!("Failed to remove data file: {}", e.to_string()))?;
remove_file(meta_path)
.await
.map_err(|e| format!("Failed to remove metadata file: {}", e.to_string()))?;
println!("Removed expired file: {:?}", data_file_path);
}
Ok(())
}
#[async_std::main]
async fn main() -> tide::Result<()> {
// Initialize logging
env_logger::init();
log::info!("Starting server");
// Configuration via variables d'environnement
let storage_dir = std::env::var("STORAGE_DIR").unwrap_or_else(|_| DEFAULT_STORAGE_DIR.to_string());
let port = std::env::var("PORT")
.ok()
.and_then(|p| p.parse::<u16>().ok())
.unwrap_or(DEFAULT_PORT);
// Parse command line arguments
let args: Vec<String> = env::args().collect();
let no_ttl_permanent = args.iter().any(|arg| arg == "--permanent");
if no_ttl_permanent {
println!("No-TTL requests will be treated as permanent");
} else {
println!("No-TTL requests will use default TTL of {} seconds", DEFAULT_TTL);
}
create_dir_all(STORAGE_DIR)
.await
.expect("Failed to create storage directory.");
task::spawn(cleanup_expired_files());
let svc = StorageService::new(&storage_dir);
create_dir_all(&storage_dir).await.expect("Failed to create storage directory.");
let mut app = tide::new();
app.at("/store").post(move |req| handle_store(req, no_ttl_permanent));
app.at("/retrieve/:key").get(handle_retrieve);
app.listen(format!("0.0.0.0:{}", PORT)).await?;
// background cleanup loop
let svc_clone = svc.clone();
task::spawn(async move {
loop {
if let Err(e) = svc_clone.cleanup_expired_files_once().await {
eprintln!("cleanup error: {}", e);
}
task::sleep(std::time::Duration::from_secs(60)).await;
}
});
println!("Server running at http://0.0.0.0:{}", PORT);
let mut app = create_app(no_ttl_permanent, &storage_dir);
app.listen(format!("0.0.0.0:{}", port)).await?;
println!("Server running at http://0.0.0.0:{}", port);
Ok(())
}

1
tests Symbolic link
View File

@ -0,0 +1 @@
/home/debian/4NK_env/tests/sdk_storage