From a6d54fd14a100083efa640b0162eea9a1f626b1d Mon Sep 17 00:00:00 2001 From: Cilia-Lachichi Date: Tue, 5 Aug 2025 02:10:11 -0400 Subject: [PATCH] =?UTF-8?q?Test=20technique=20compl=C3=A9t=C3=A9=20:=20pip?= =?UTF-8?q?eline=20de=20donn=C3=A9es,=20API=20FastAPI,=20tests=20et=20syst?= =?UTF-8?q?=C3=A8me=20de=20recommandations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ANSWERS.md | 269 +++- pytest.ini | 5 + requirements.txt | 35 +- src/__init__.py | 0 src/data/listen_history.json | 1202 ++++++++++++++++++ src/data/tracks.json | 1102 ++++++++++++++++ src/data/users.json | 1002 +++++++++++++++ src/moovitamix_fastapi/api/__init__.py | 0 src/moovitamix_fastapi/api/client.py | 10 + src/moovitamix_fastapi/api/routes.py | 26 + src/moovitamix_fastapi/config.py | 10 + src/moovitamix_fastapi/generate_fake_data.py | 2 +- src/moovitamix_fastapi/main.py | 4 +- src/moovitamix_fastapi/run_fetch.py | 20 + src/moovitamix_fastapi/utils/__init__.py | 0 src/moovitamix_fastapi/utils/file_writer.py | 13 + src/moovitamix_fastapi/utils/pagination.py | 8 + test/__init__.py | 0 test/conftest.py | 11 + test/test_api_client.py | 38 + test/test_file_writer.py | 18 + test/test_routes.py | 27 + test/test_run_fetch.py | 22 + 23 files changed, 3804 insertions(+), 20 deletions(-) create mode 100644 pytest.ini create mode 100644 src/__init__.py create mode 100644 src/data/listen_history.json create mode 100644 src/data/tracks.json create mode 100644 src/data/users.json create mode 100644 src/moovitamix_fastapi/api/__init__.py create mode 100644 src/moovitamix_fastapi/api/client.py create mode 100644 src/moovitamix_fastapi/api/routes.py create mode 100644 src/moovitamix_fastapi/config.py create mode 100644 src/moovitamix_fastapi/run_fetch.py create mode 100644 src/moovitamix_fastapi/utils/__init__.py create mode 100644 src/moovitamix_fastapi/utils/file_writer.py create mode 100644 src/moovitamix_fastapi/utils/pagination.py create mode 100644 test/__init__.py create mode 100644 test/conftest.py create mode 100644 test/test_api_client.py create mode 100644 test/test_file_writer.py create mode 100644 test/test_routes.py create mode 100644 test/test_run_fetch.py diff --git a/docs/ANSWERS.md b/docs/ANSWERS.md index d038faa..5bdc57b 100644 --- a/docs/ANSWERS.md +++ b/docs/ANSWERS.md @@ -1,23 +1,268 @@ -# Réponses du test +# Réponses du test technique -## _Utilisation de la solution (étape 1 à 3)_ +## Étapes 1 à 3 : Documentation technique -_Inscrire la documentation technique_ +### 1. Création de l’environnement virtuel -## Questions (étapes 4 à 7) +```bash +python -m venv env +.\env\Scripts\activate -### Étape 4 +pip install -r requirements.txt +``` -_votre réponse ici_ +### 2. Lancement de l’API et élaboration du pipeline de données -### Étape 5 +#### Lancement de l’API -_votre réponse ici_ +Pour démarrer le serveur FastAPI : -### Étape 6 +```bash +cd src +python -m uvicorn moovitamix_fastapi.main:app --reload +``` -_votre réponse ici_ +Une fois lancé, l’interface Swagger est accessible à l’adresse : [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) -### Étape 7 -_votre réponse ici_ + +#### Structure du projet + +J’ai structuré le projet autour d’un découpage modulaire pour faciliter la maintenance et les tests : +- `src/moovitamix_fastapi/` : contient entre autres l’application FastAPI et le script principale du pipeline de données. +- `src/moovitamix_fastapi/api/` : contient le client pour récupérer les données depuis les API externes. +- `src/moovitamix_fastapi/utils/` : contient l’écriture des fichiers ou la création des répertoires. +- `test/` : regroupe tous les tests unitaires. + +Le fichier principal du flux de données est `run_fetch.py`, situé à la racine du dossier `src/moovitamix_fastapi/`. Ce script constitue le point d’entrée du pipeline. Il orchestre les appels aux trois endpoints de l'API (`/tracks`, `/users`, `/listen_history`) via les fonctions du module `api/`, puis structure et sauvegarde les données extraites. + +Les données récupérées sont ensuite enregistrées localement dans le dossier `data/` sous les fichiers `tracks.json`, `users.json`, et `listen_history.json`, facilitant leur réutilisation sans dépendance à l'API pendant le développement. + +#### Tester le flux de données manuellement + +Pour exécuter manuellement le pipeline de collecte de données : +```bash +cd src +python -m moovitamix_fastapi.run_fetch +``` +Les fichiers JSON sont ensuite disponibles dans le dossier `data/`. + +### 3. Tests unitaires + + **Test de récupération des données via l’API client (`api_client.py`)** + Vérifie que le client appelle correctement les endpoints, gère la pagination et renvoie des données au format attendu. + +test_fetch_data_success : vérifie que les données sont correctement récupérées d’un endpoint. +test_fetch_data_invalid_endpoint : vérifie que les erreurs (ex : 404) sont bien levées et capturées. + + + **Test de la fonction d’écriture des fichiers JSON (`file_writer.py`)** + Contrôle que les données extraites sont correctement sauvegardées dans le dossier `data/` sous le bon format et chemin. + +test_save_data_to_json_creates_file : vérifie que le fichier est créé, et que les données sont bien enregistrées. + + **Test des routes FastAPI (`main.py`)** + test_get_tracks, test_get_users, test_get_listen_history : vérifient que les endpoints exposés renvoient bien une réponse 200 et contiennent une clé "items" avec une liste. + + **Test de gestion d’erreurs** +Implémentation correspondante : test/test_run_fetch.py + + Simulation d’erreurs réseau et de données invalides dans les appels API pour vérifier que le pipeline gère correctement ces cas sans planter. + + test_fetch_and_save_all : exécute la fonction centrale fetch_and_save_all, simule les appels API, et vérifie que les 3 fichiers (tracks.json, users.json, listen_history.json) sont bien générés dans un répertoire temporaire. + +- Remarque + +Des fixtures ont été utilisées pour gérer la création et la suppression de répertoires temporaires lors des tests + + +#### Exécution des tests + +Depuis la racine du projet (là où se trouve le dossier `test/`), exécuter simplement la commande : + +```bash +pytest +``` +--- + +## Étapes 4 à 7 : Questions + +### Étape 4 : Schéma de la base de données recommandé + +#### Tables proposées : + +| Table | Clé primaire | Clés étrangères | +| ---------------- | ------------ | --------------------- | +| `tracks` | `id_track` | | +| `users` | `id_user` | | +| `listen_history` | `id_listen` | `id_user`, `id_track` | + +### 4. Schéma de base de données et recommandation + +#### Schéma relationnel proposé + +Pour structurer les données récupérées depuis les API `/tracks`, `/users`, et `/listen_history`, j’ai conçu un schéma relationnel normalisé, simple mais extensible : + +#### Table : users + +| Colonne | Type | Contraintes | +|----------------|-------------|-------------------------------| +| id | INTEGER | PRIMARY KEY | +| first_name | TEXT | NOT NULL | +| last_name | TEXT | NOT NULL | +| email | TEXT | UNIQUE, NOT NULL | +| gender | TEXT | CHECK (gender IN ('Male', 'Female', 'Other')) | +| favorite_genres| TEXT | NULLABLE (champ CSV ou JSON) | +| created_at | TIMESTAMP | NOT NULL | +| updated_at | TIMESTAMP | NOT NULL | + +#### Table : tracks + +| Colonne | Type | Contraintes | +|----------------|-------------|-------------------------------| +| id | INTEGER | PRIMARY KEY | +| name | TEXT | NOT NULL | +| artist | TEXT | NOT NULL | +| songwriters | TEXT | NULLABLE | +| duration | TEXT | NOT NULL | +| genres | TEXT | NULLABLE (champ CSV ou JSON) | +| album | TEXT | NULLABLE | +| created_at | TIMESTAMP | NOT NULL | +| updated_at | TIMESTAMP | NOT NULL | + +#### Table : listen_history + +| Colonne | Type | Contraintes | +|----------------|-------------|---------------------------------------------------| +| id | INTEGER | PRIMARY KEY AUTOINCREMENT | +| user_id | INTEGER | FOREIGN KEY → users(id) ON DELETE CASCADE | +| track_id | INTEGER | FOREIGN KEY → tracks(id) ON DELETE CASCADE | +| listened_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | + +#### Remarques : + +- L’API `/listen_history` retourne des paires `{user_id, items}` avec une liste d’`id` de morceaux écoutés. Ces données sont normalisées dans la table `listen_history` avec une ligne par écoute. +- La table `listen_history` peut être enrichie par la suite avec des métadonnées (durée d’écoute, device utilisé, etc.) + +#### Recommandation de système de base de données + +Pour un environnement réel avec montée en charge, je recommanderais : + +- **PostgreSQL** : + - Puissant support SQL + JSON. + - Indexation avancée, support de la recherche full-text. + - Fiabilité et scalabilité. + +> Remarque : les types et contraintes sont déduits des réponses JSON observées via Swagger. + +--- + +### Étape 5 : Monitoring de la santé du pipeline de données + + +**1. Journalisation (logging structuré)** +Chaque étape du pipeline (`fetch`, `save`) génère des logs horodatés avec : +- Début / fin d’exécution +- Statut de l’appel API +- Nombre d’items récupérés +- Chemin des fichiers générés + +*Exemple :* +`[2025-08-04 08:00:01] INFO - Fetched 100 items from /tracks, saved to data/tracks.json` + +**2. Validation automatique du résultat** + +Après chaque exécution, une vérification de l’intégrité est effectuée : +- Présence des fichiers `tracks.json`, `users.json`, `listen_history.json` +- Taille minimale des fichiers +- Conformité structurelle de base (clés attendues) + +> En cas d’échec : log d’erreur, possibilité d’alerte mail a travers un serveur SMTP. + +**3. Statistiques quotidiennes** + +Un fichier `data/pipeline_stats.csv` peut stocker des résumés : + +| date | endpoint | items_fetched | duration_sec | status | +|------------|------------------|----------------|--------------|---------| +| 2025-08-04 | /tracks | 105 | 1.45 | success | +| 2025-08-04 | /users | 50 | 0.87 | success | +| 2025-08-04 | /listen_history | 140 | 1.02 | success | + + +#### Métriques clés à surveiller + +| **Métrique** | **Description** | +|---------------------------|-------------| +| Volume extrait | Nombre d’items par endpoint | +| Statut HTTP | Codes 200 / erreurs API | +| Temps d’exécution | Durée des appels | +| Intégrité des fichiers | Présence et format JSON valide | +| Horodatage d’exécution | Confirmation d’une exécution quotidienne | + +--- + +### Étape 6 : Automatisation du calcul des recommandations + +#### Méthode de calcul des recommandations + +Pour chaque utilisateur, le moteur de recommandation suivra ces etapes + +1. **Analyse des écoutes passées** : + - Regrouper les morceaux écoutés par l'utilisateur (`listen_history`) + - Extraire les genres, artistes et albums les plus fréquents + - Pondérer selon la fréquence d’écoute (plus un genre/artiste est écouté, plus il est pertinent) + +2. **Croisement avec les préférences déclarées** : + - Extraire le champ `favorite_genres` depuis `users.json` + - Attribuer un poids fixe à ces genres déclarés, même s'ils ne ressortent pas de l’historique + +3. **Filtrage de l’univers musical** : + - Éliminer les titres déjà écoutés (éviter redondance) + - Identifier les morceaux similaires selon : + **Genre** + **Artiste** + **Album** + +4. **Scoring final et sélection** : + - Pour chaque morceau candidat non encore écouté : + ```python + score = 0 + if genre in genres_écoutés: score += 2 + if genre in favorite_genres: score += 1.5 + if artiste in artistes_écoutés: score += 1.5 + if album in albums_écoutés: score += 1 + ``` + - Trier par score décroissant et conserver les **10 meilleurs morceaux** + +--- + +### Étape 7 : Automatisation du réentrainement du modèle + +#### Fréquence suggérée : + +- Par défaut : quotidienne + +Possibilité de passer à hebdomadaire si les données évoluent lentement et selon le comportement utilisateur + +#### Pipeline type : + +1. Extraction des nouvelles données (utilisateurs + écoutes) +2. Préparation des données : +Agrégation des statistiques d’écoute +Nettoyage et formatage des données d’entrée +3. Recalcul des recommandations : +Relance du script de scoring +Génération des top 10 pour chaque utilisateur +4. Stockage & historisation: +Écriture dans un fichier versionné ou table SQL (timestamp, user_id, recommendations) + +#### Orchestration : + +- Option simple : tâche planifiée (cron, schedule) + +- Option avancée : orchestrateur type Airflow, + +#### Remarques : + +Le moteur de recommandation actuel repose sur un système de scoring pondéré basé sur les comportements d'écoute et préférences. Son réentraînement consiste à recalculer les scores à partir des données mises à jour chaque semaine, via un script orchestré automatiquement. \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..072fcfb --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +# pytest.ini +[pytest] + +testpaths = test +pythonpath = src diff --git a/requirements.txt b/requirements.txt index 55593fd..afe938b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,30 @@ -faker -fastapi -uvicorn -fastapi_pagination -pytest \ No newline at end of file +annotated-types==0.7.0 +anyio==4.9.0 +certifi==2025.8.3 +charset-normalizer==3.4.2 +click==8.1.8 +colorama==0.4.6 +exceptiongroup==1.3.0 +Faker==37.5.3 +fastapi==0.116.1 +fastapi-pagination==0.12.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +idna==3.10 +iniconfig==2.1.0 +packaging==25.0 +pluggy==1.6.0 +pydantic==1.10.2 +pydantic_core==2.33.2 +Pygments==2.19.2 +pytest==8.4.1 +requests==2.32.4 +sniffio==1.3.1 +starlette==0.47.2 +tomli==2.2.1 +typing-inspection==0.4.1 +typing_extensions==4.14.1 +tzdata==2025.2 +urllib3==2.5.0 +uvicorn==0.35.0 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/data/listen_history.json b/src/data/listen_history.json new file mode 100644 index 0000000..d69b8de --- /dev/null +++ b/src/data/listen_history.json @@ -0,0 +1,1202 @@ +[ + { + "user_id": 63102, + "items": [ + 72525, + 40351, + 56337, + 62027, + 45372 + ], + "created_at": "2024-04-03T20:37:17", + "updated_at": "2025-02-07T11:57:12" + }, + { + "user_id": 11782, + "items": [ + 96877, + 27139, + 68102, + 90945, + 78306 + ], + "created_at": "2024-06-10T21:35:26", + "updated_at": "2025-05-14T13:20:21" + }, + { + "user_id": 96260, + "items": [ + 70708, + 37526, + 59023, + 83896, + 95996 + ], + "created_at": "2023-12-05T03:45:50", + "updated_at": "2024-07-06T22:25:56" + }, + { + "user_id": 65175, + "items": [ + 45220, + 98532, + 9681, + 6360, + 65413 + ], + "created_at": "2024-01-07T22:30:49", + "updated_at": "2024-10-04T09:55:40" + }, + { + "user_id": 55650, + "items": [ + 29024, + 77010, + 34936, + 23186, + 76636 + ], + "created_at": "2024-01-23T00:18:39", + "updated_at": "2025-05-04T22:08:11" + }, + { + "user_id": 8040, + "items": [ + 77010, + 92182, + 38739, + 3373, + 78306 + ], + "created_at": "2023-09-03T00:25:20", + "updated_at": "2025-06-14T04:12:40" + }, + { + "user_id": 21505, + "items": [ + 3394, + 40940, + 86729, + 8164, + 90108 + ], + "created_at": "2025-05-12T13:54:52", + "updated_at": "2025-06-29T01:21:14" + }, + { + "user_id": 1260, + "items": [ + 73277, + 98609, + 73352, + 45759, + 59447 + ], + "created_at": "2024-04-02T05:37:44", + "updated_at": "2025-05-18T13:47:26" + }, + { + "user_id": 91641, + "items": [ + 66608, + 43327, + 37593, + 52669, + 21592 + ], + "created_at": "2024-11-11T01:41:49", + "updated_at": "2025-01-28T05:38:33" + }, + { + "user_id": 80198, + "items": [ + 9945, + 60723, + 32675, + 55144, + 3937 + ], + "created_at": "2023-08-09T17:31:17", + "updated_at": "2024-03-09T11:11:38" + }, + { + "user_id": 83891, + "items": [ + 78059, + 98728, + 84415, + 2561, + 70838 + ], + "created_at": "2024-02-19T17:38:52", + "updated_at": "2025-02-19T05:07:24" + }, + { + "user_id": 11203, + "items": [ + 66706, + 17709, + 81189, + 41534, + 28914 + ], + "created_at": "2024-05-01T18:24:43", + "updated_at": "2024-11-06T11:51:00" + }, + { + "user_id": 91800, + "items": [ + 19178, + 5364, + 63739, + 31530, + 50141 + ], + "created_at": "2025-01-14T18:37:35", + "updated_at": "2025-04-22T13:37:56" + }, + { + "user_id": 67385, + "items": [ + 79137, + 36594, + 56512, + 86102, + 63531 + ], + "created_at": "2025-01-12T21:56:53", + "updated_at": "2025-03-25T05:55:36" + }, + { + "user_id": 54214, + "items": [ + 39863, + 93205, + 29849, + 66250, + 89412 + ], + "created_at": "2025-07-23T10:35:50", + "updated_at": "2025-07-27T21:33:18" + }, + { + "user_id": 49756, + "items": [ + 66250, + 73938, + 23235, + 75057, + 7244 + ], + "created_at": "2023-11-10T10:21:20", + "updated_at": "2025-01-01T07:58:31" + }, + { + "user_id": 32479, + "items": [ + 23146, + 39522, + 85779, + 68768, + 73138 + ], + "created_at": "2024-09-02T12:49:27", + "updated_at": "2024-12-20T11:31:06" + }, + { + "user_id": 96758, + "items": [ + 88999, + 51294, + 95506, + 74789, + 33074 + ], + "created_at": "2024-10-18T05:59:39", + "updated_at": "2025-03-01T15:11:07" + }, + { + "user_id": 85155, + "items": [ + 96594, + 9427, + 50790, + 95401, + 44935 + ], + "created_at": "2024-05-28T01:18:25", + "updated_at": "2024-07-08T15:50:59" + }, + { + "user_id": 90970, + "items": [ + 78001, + 4164, + 41221, + 40411, + 22007 + ], + "created_at": "2024-11-24T09:18:23", + "updated_at": "2024-11-29T21:56:49" + }, + { + "user_id": 92572, + "items": [ + 43327, + 18559, + 81596, + 36020, + 34877 + ], + "created_at": "2024-07-31T13:10:58", + "updated_at": "2025-02-28T15:55:50" + }, + { + "user_id": 48340, + "items": [ + 25691, + 95072, + 3373, + 13605, + 20364 + ], + "created_at": "2025-06-18T01:18:27", + "updated_at": "2025-07-16T23:47:24" + }, + { + "user_id": 95056, + "items": [ + 31441, + 39863, + 95259, + 97855, + 45544 + ], + "created_at": "2025-01-20T13:31:01", + "updated_at": "2025-03-17T14:00:55" + }, + { + "user_id": 25961, + "items": [ + 80448, + 31441, + 45381, + 79181, + 92815 + ], + "created_at": "2025-01-07T08:25:04", + "updated_at": "2025-03-30T15:46:27" + }, + { + "user_id": 91090, + "items": [ + 59023, + 9681, + 25194, + 84681, + 14788 + ], + "created_at": "2023-09-17T04:13:07", + "updated_at": "2025-04-24T18:28:45" + }, + { + "user_id": 87896, + "items": [ + 85779, + 518, + 95725, + 95072, + 4061 + ], + "created_at": "2025-01-30T18:12:10", + "updated_at": "2025-05-25T16:01:30" + }, + { + "user_id": 37714, + "items": [ + 83424, + 93517, + 77061, + 23938, + 48205 + ], + "created_at": "2024-01-31T20:27:58", + "updated_at": "2024-09-03T22:47:03" + }, + { + "user_id": 97027, + "items": [ + 62262, + 13352, + 26905, + 65300, + 68589 + ], + "created_at": "2024-07-31T01:26:22", + "updated_at": "2025-07-05T19:27:04" + }, + { + "user_id": 76999, + "items": [ + 67452, + 80312, + 61144, + 31632, + 55921 + ], + "created_at": "2024-07-31T04:48:37", + "updated_at": "2024-10-31T03:50:49" + }, + { + "user_id": 59590, + "items": [ + 25691, + 69530, + 3373, + 74030, + 44397 + ], + "created_at": "2025-05-19T11:52:18", + "updated_at": "2025-07-22T05:12:28" + }, + { + "user_id": 12632, + "items": [ + 33171, + 44397, + 3705, + 20063, + 43421 + ], + "created_at": "2025-04-08T18:24:50", + "updated_at": "2025-07-12T10:12:33" + }, + { + "user_id": 37408, + "items": [ + 81000, + 76970, + 69655, + 13350, + 90281 + ], + "created_at": "2024-05-01T09:08:09", + "updated_at": "2024-06-06T08:17:00" + }, + { + "user_id": 32088, + "items": [ + 17724, + 3373, + 50301, + 51283, + 61204 + ], + "created_at": "2024-06-02T00:58:34", + "updated_at": "2025-08-04T06:03:39" + }, + { + "user_id": 94805, + "items": [ + 68336, + 38687, + 97685, + 47642, + 68829 + ], + "created_at": "2025-07-13T15:04:37", + "updated_at": "2025-07-15T22:24:01" + }, + { + "user_id": 64492, + "items": [ + 38414, + 25829, + 76609, + 20530, + 44212 + ], + "created_at": "2023-10-30T05:45:41", + "updated_at": "2024-08-05T10:39:37" + }, + { + "user_id": 98380, + "items": [ + 25459, + 81596, + 26044, + 21092, + 44397 + ], + "created_at": "2024-12-02T06:16:38", + "updated_at": "2025-01-13T01:15:59" + }, + { + "user_id": 3923, + "items": [ + 36133, + 28477, + 56512, + 65868, + 66608 + ], + "created_at": "2024-10-08T19:28:56", + "updated_at": "2025-04-23T02:50:57" + }, + { + "user_id": 82398, + "items": [ + 12005, + 95506, + 94210, + 38687, + 85007 + ], + "created_at": "2024-04-11T14:15:56", + "updated_at": "2025-04-26T09:32:07" + }, + { + "user_id": 55987, + "items": [ + 77053, + 23146, + 85588, + 47186, + 63934 + ], + "created_at": "2024-11-15T18:18:08", + "updated_at": "2025-06-07T00:48:18" + }, + { + "user_id": 29259, + "items": [ + 21422, + 18671, + 78757, + 65868, + 82506 + ], + "created_at": "2025-02-10T18:16:54", + "updated_at": "2025-04-08T03:15:26" + }, + { + "user_id": 599, + "items": [ + 82907, + 15642, + 62935, + 23549, + 80312 + ], + "created_at": "2025-07-11T22:14:31", + "updated_at": "2025-07-19T05:10:05" + }, + { + "user_id": 52629, + "items": [ + 72295, + 62060, + 85779, + 87022, + 62161 + ], + "created_at": "2024-04-24T08:02:40", + "updated_at": "2024-09-09T05:06:36" + }, + { + "user_id": 4941, + "items": [ + 62042, + 43421, + 5690, + 36652, + 98425 + ], + "created_at": "2023-10-05T16:14:52", + "updated_at": "2023-10-27T04:54:34" + }, + { + "user_id": 64982, + "items": [ + 4688, + 93272, + 44634, + 3911, + 92856 + ], + "created_at": "2025-03-13T02:38:52", + "updated_at": "2025-05-04T18:12:16" + }, + { + "user_id": 93072, + "items": [ + 59447, + 19178, + 84525, + 22366, + 42939 + ], + "created_at": "2024-08-03T01:15:08", + "updated_at": "2025-06-26T02:02:34" + }, + { + "user_id": 523, + "items": [ + 51400, + 1009, + 59058, + 25829, + 40532 + ], + "created_at": "2025-01-11T05:41:17", + "updated_at": "2025-07-05T04:38:58" + }, + { + "user_id": 61745, + "items": [ + 5433, + 25639, + 34892, + 14788, + 81985 + ], + "created_at": "2025-07-05T00:29:23", + "updated_at": "2025-07-05T01:58:25" + }, + { + "user_id": 31018, + "items": [ + 92542, + 98154, + 92452, + 65868, + 54687 + ], + "created_at": "2024-11-12T07:46:33", + "updated_at": "2025-05-20T07:35:27" + }, + { + "user_id": 37964, + "items": [ + 11538, + 34852, + 92348, + 34892, + 9161 + ], + "created_at": "2023-08-05T23:28:45", + "updated_at": "2025-01-19T20:40:32" + }, + { + "user_id": 99954, + "items": [ + 21747, + 25972, + 16694, + 25132, + 97685 + ], + "created_at": "2024-01-17T18:54:25", + "updated_at": "2024-05-16T15:31:57" + }, + { + "user_id": 18539, + "items": [ + 2315, + 87793, + 37994, + 522, + 171 + ], + "created_at": "2023-11-07T06:50:44", + "updated_at": "2025-08-01T08:13:19" + }, + { + "user_id": 90332, + "items": [ + 62591, + 97373, + 70195, + 58997, + 21422 + ], + "created_at": "2025-05-01T12:21:39", + "updated_at": "2025-08-04T00:19:22" + }, + { + "user_id": 62637, + "items": [ + 25185, + 83424, + 45220, + 69187, + 45350 + ], + "created_at": "2023-12-21T03:20:07", + "updated_at": "2025-04-13T22:46:39" + }, + { + "user_id": 26027, + "items": [ + 71919, + 36343, + 77555, + 53949, + 17956 + ], + "created_at": "2023-10-31T08:34:27", + "updated_at": "2023-11-11T00:54:56" + }, + { + "user_id": 43269, + "items": [ + 95668, + 79974, + 1009, + 45762, + 40162 + ], + "created_at": "2024-08-06T07:40:54", + "updated_at": "2024-12-15T05:22:16" + }, + { + "user_id": 90611, + "items": [ + 85884, + 84929, + 59963, + 96183, + 11625 + ], + "created_at": "2025-01-09T16:17:29", + "updated_at": "2025-06-12T05:34:23" + }, + { + "user_id": 30317, + "items": [ + 59189, + 60723, + 61412, + 96183, + 14176 + ], + "created_at": "2023-11-29T00:19:44", + "updated_at": "2025-04-10T13:04:29" + }, + { + "user_id": 44472, + "items": [ + 63876, + 54389, + 25639, + 3373, + 59023 + ], + "created_at": "2024-05-14T15:31:54", + "updated_at": "2025-04-16T16:50:45" + }, + { + "user_id": 31979, + "items": [ + 69510, + 62101, + 96010, + 36474, + 24012 + ], + "created_at": "2025-06-01T14:31:53", + "updated_at": "2025-07-01T09:26:45" + }, + { + "user_id": 63235, + "items": [ + 75068, + 67644, + 75070, + 63613, + 7857 + ], + "created_at": "2024-02-03T14:11:08", + "updated_at": "2024-09-02T08:46:09" + }, + { + "user_id": 88825, + "items": [ + 57846, + 10864, + 96877, + 2909, + 13359 + ], + "created_at": "2024-11-10T18:22:33", + "updated_at": "2025-03-26T23:44:09" + }, + { + "user_id": 89666, + "items": [ + 81632, + 76416, + 98609, + 44461, + 90331 + ], + "created_at": "2024-04-02T08:24:49", + "updated_at": "2024-10-27T04:31:03" + }, + { + "user_id": 4250, + "items": [ + 83424, + 51460, + 48826, + 97503, + 14581 + ], + "created_at": "2024-07-26T06:50:57", + "updated_at": "2024-08-12T15:58:09" + }, + { + "user_id": 44350, + "items": [ + 79137, + 8545, + 52185, + 62262, + 4572 + ], + "created_at": "2024-02-11T02:30:06", + "updated_at": "2025-05-22T07:39:08" + }, + { + "user_id": 70214, + "items": [ + 44079, + 78339, + 62699, + 73352, + 61144 + ], + "created_at": "2024-11-23T01:55:21", + "updated_at": "2025-07-17T01:24:05" + }, + { + "user_id": 95878, + "items": [ + 86093, + 63567, + 79176, + 91824, + 40935 + ], + "created_at": "2025-06-04T13:41:37", + "updated_at": "2025-06-25T11:52:25" + }, + { + "user_id": 32770, + "items": [ + 84170, + 77010, + 21747, + 16971, + 94871 + ], + "created_at": "2025-02-02T08:30:00", + "updated_at": "2025-03-15T19:30:22" + }, + { + "user_id": 34802, + "items": [ + 55083, + 76168, + 75068, + 92815, + 87256 + ], + "created_at": "2024-03-29T17:18:13", + "updated_at": "2024-05-30T11:00:10" + }, + { + "user_id": 27441, + "items": [ + 76642, + 40, + 18490, + 63492, + 46135 + ], + "created_at": "2024-09-12T11:58:39", + "updated_at": "2025-07-24T11:29:13" + }, + { + "user_id": 29038, + "items": [ + 70195, + 51347, + 27317, + 23534, + 98064 + ], + "created_at": "2025-02-16T10:23:39", + "updated_at": "2025-06-18T01:34:56" + }, + { + "user_id": 99981, + "items": [ + 93501, + 23579, + 82414, + 90709, + 98431 + ], + "created_at": "2024-09-10T00:50:27", + "updated_at": "2025-06-12T23:05:37" + }, + { + "user_id": 82099, + "items": [ + 29024, + 94845, + 87922, + 3213, + 89735 + ], + "created_at": "2024-03-26T22:44:24", + "updated_at": "2025-01-13T12:20:52" + }, + { + "user_id": 95443, + "items": [ + 73243, + 22593, + 54197, + 65715, + 35663 + ], + "created_at": "2023-10-29T17:29:41", + "updated_at": "2024-10-26T22:32:55" + }, + { + "user_id": 3293, + "items": [ + 3897, + 63492, + 43613, + 50790, + 69655 + ], + "created_at": "2025-05-03T15:03:47", + "updated_at": "2025-07-26T07:37:28" + }, + { + "user_id": 54635, + "items": [ + 65047, + 85779, + 63934, + 77005, + 88473 + ], + "created_at": "2023-11-28T05:00:28", + "updated_at": "2025-02-01T16:14:55" + }, + { + "user_id": 54733, + "items": [ + 15825, + 73360, + 81000, + 44705, + 81985 + ], + "created_at": "2024-10-31T12:16:06", + "updated_at": "2025-01-05T20:39:28" + }, + { + "user_id": 62558, + "items": [ + 5908, + 171, + 76358, + 5364, + 88752 + ], + "created_at": "2023-11-05T17:05:57", + "updated_at": "2024-06-05T03:47:22" + }, + { + "user_id": 23429, + "items": [ + 74547, + 89304, + 68895, + 70280, + 73796 + ], + "created_at": "2023-10-03T22:48:08", + "updated_at": "2024-04-22T13:12:08" + }, + { + "user_id": 61339, + "items": [ + 28254, + 87256, + 92702, + 91809, + 59023 + ], + "created_at": "2024-12-10T03:00:11", + "updated_at": "2024-12-14T06:43:29" + }, + { + "user_id": 3556, + "items": [ + 51283, + 78069, + 17956, + 89501, + 7244 + ], + "created_at": "2025-03-10T08:20:32", + "updated_at": "2025-03-26T17:41:06" + }, + { + "user_id": 68834, + "items": [ + 62394, + 63613, + 23549, + 5199, + 95577 + ], + "created_at": "2024-11-25T12:44:15", + "updated_at": "2024-12-22T07:05:05" + }, + { + "user_id": 58467, + "items": [ + 84561, + 40000, + 76113, + 81596, + 18671 + ], + "created_at": "2025-07-12T03:01:57", + "updated_at": "2025-08-02T06:29:33" + }, + { + "user_id": 66452, + "items": [ + 24544, + 16537, + 80110, + 52829, + 96010 + ], + "created_at": "2025-04-25T20:52:31", + "updated_at": "2025-06-17T00:10:38" + }, + { + "user_id": 2116, + "items": [ + 23146, + 31299, + 9070, + 85884, + 17825 + ], + "created_at": "2025-04-02T11:26:55", + "updated_at": "2025-07-01T22:51:34" + }, + { + "user_id": 76799, + "items": [ + 25972, + 22742, + 94871, + 12153, + 62922 + ], + "created_at": "2025-06-15T13:27:11", + "updated_at": "2025-07-31T10:04:56" + }, + { + "user_id": 70802, + "items": [ + 75070, + 80448, + 83812, + 59463, + 96400 + ], + "created_at": "2024-08-06T21:33:09", + "updated_at": "2024-08-13T16:14:26" + }, + { + "user_id": 2933, + "items": [ + 100000, + 23534, + 89304, + 43808, + 74106 + ], + "created_at": "2023-09-05T00:52:14", + "updated_at": "2023-09-25T03:21:06" + }, + { + "user_id": 99958, + "items": [ + 66250, + 76556, + 84929, + 34936, + 90389 + ], + "created_at": "2024-07-10T08:40:22", + "updated_at": "2025-01-11T00:24:43" + }, + { + "user_id": 24687, + "items": [ + 29436, + 23321, + 63740, + 41770, + 28254 + ], + "created_at": "2024-10-23T07:03:24", + "updated_at": "2025-06-30T11:17:22" + }, + { + "user_id": 42147, + "items": [ + 3911, + 67466, + 23321, + 79974, + 29707 + ], + "created_at": "2024-06-23T13:55:40", + "updated_at": "2024-09-23T01:53:44" + }, + { + "user_id": 27500, + "items": [ + 79155, + 83114, + 36343, + 18490, + 88537 + ], + "created_at": "2024-06-03T06:27:34", + "updated_at": "2024-07-13T04:18:54" + }, + { + "user_id": 42730, + "items": [ + 58951, + 35028, + 29296, + 3937, + 4167 + ], + "created_at": "2024-09-04T05:53:02", + "updated_at": "2025-01-11T07:52:35" + }, + { + "user_id": 62154, + "items": [ + 48385, + 86096, + 54389, + 90331, + 58463 + ], + "created_at": "2023-11-13T23:47:27", + "updated_at": "2024-01-02T15:07:09" + }, + { + "user_id": 95251, + "items": [ + 89501, + 77612, + 42220, + 59963, + 28168 + ], + "created_at": "2024-04-12T03:41:31", + "updated_at": "2025-07-30T07:27:19" + }, + { + "user_id": 98990, + "items": [ + 30413, + 55083, + 2984, + 195, + 25185 + ], + "created_at": "2025-01-17T07:33:55", + "updated_at": "2025-04-06T02:44:35" + }, + { + "user_id": 11150, + "items": [ + 44093, + 70431, + 54414, + 39065, + 40532 + ], + "created_at": "2025-02-22T08:47:45", + "updated_at": "2025-02-28T01:49:16" + }, + { + "user_id": 29671, + "items": [ + 85684, + 15462, + 44935, + 10864, + 69565 + ], + "created_at": "2024-12-26T16:16:58", + "updated_at": "2025-02-04T05:32:14" + }, + { + "user_id": 55332, + "items": [ + 86111, + 5441, + 40935, + 78412, + 92289 + ], + "created_at": "2024-06-02T16:30:40", + "updated_at": "2025-01-07T08:23:56" + }, + { + "user_id": 36489, + "items": [ + 39863, + 50638, + 54197, + 64868, + 77888 + ], + "created_at": "2024-08-12T21:38:13", + "updated_at": "2024-09-26T04:10:02" + }, + { + "user_id": 86478, + "items": [ + 91824, + 64868, + 84650, + 2994, + 32370 + ], + "created_at": "2025-03-10T18:49:01", + "updated_at": "2025-04-08T02:38:35" + } +] \ No newline at end of file diff --git a/src/data/tracks.json b/src/data/tracks.json new file mode 100644 index 0000000..bf10854 --- /dev/null +++ b/src/data/tracks.json @@ -0,0 +1,1102 @@ +[ + { + "id": 93386, + "name": "television", + "artist": "Sophia Davis", + "songwriters": "Kerry Middleton", + "duration": "26:34", + "genres": "experience", + "album": "paper", + "created_at": "2025-01-11T22:39:59", + "updated_at": "2025-01-08T17:58:47" + }, + { + "id": 79155, + "name": "fund", + "artist": "Samuel Bates", + "songwriters": "Gabriel Travis", + "duration": "10:17", + "genres": "painting", + "album": "car", + "created_at": "2024-06-22T20:17:44", + "updated_at": "2025-05-19T19:57:41" + }, + { + "id": 50790, + "name": "officer", + "artist": "Nicole Banks", + "songwriters": "Robert Jones", + "duration": "06:43", + "genres": "day", + "album": "place", + "created_at": "2025-07-07T06:07:07", + "updated_at": "2025-04-25T14:40:23" + }, + { + "id": 4031, + "name": "would", + "artist": "Melinda Brady", + "songwriters": "Tyler Rubio", + "duration": "45:53", + "genres": "laugh", + "album": "simple", + "created_at": "2025-08-03T09:20:34", + "updated_at": "2024-09-13T17:41:15" + }, + { + "id": 52829, + "name": "call", + "artist": "Carl Moore", + "songwriters": "Justin Reid", + "duration": "31:21", + "genres": "score", + "album": "writer", + "created_at": "2024-08-05T15:11:39", + "updated_at": "2025-04-05T14:09:18" + }, + { + "id": 20390, + "name": "before", + "artist": "Dana Mayer", + "songwriters": "Mary Kelly", + "duration": "59:34", + "genres": "find", + "album": "cut", + "created_at": "2023-10-02T08:03:12", + "updated_at": "2024-12-08T13:46:32" + }, + { + "id": 38414, + "name": "enjoy", + "artist": "Jeremy Smith", + "songwriters": "Joseph Anderson", + "duration": "13:56", + "genres": "sense", + "album": "item", + "created_at": "2025-05-05T22:07:39", + "updated_at": "2024-10-02T02:56:37" + }, + { + "id": 95506, + "name": "where", + "artist": "Rachel Chen", + "songwriters": "Michael Mcfarland", + "duration": "44:11", + "genres": "science", + "album": "nothing", + "created_at": "2024-03-20T04:31:58", + "updated_at": "2024-08-29T03:33:09" + }, + { + "id": 71561, + "name": "hard", + "artist": "Lindsay Rodriguez", + "songwriters": "Traci Huerta", + "duration": "17:35", + "genres": "drug", + "album": "the", + "created_at": "2025-02-20T09:31:09", + "updated_at": "2025-02-10T19:03:25" + }, + { + "id": 59023, + "name": "campaign", + "artist": "Jeffery Mills", + "songwriters": "Johnny Jones", + "duration": "38:55", + "genres": "first", + "album": "under", + "created_at": "2023-12-18T20:11:59", + "updated_at": "2025-06-09T23:24:43" + }, + { + "id": 53294, + "name": "commercial", + "artist": "Robert Riley", + "songwriters": "Tyler Johnson", + "duration": "33:51", + "genres": "impact", + "album": "choice", + "created_at": "2025-06-19T15:26:20", + "updated_at": "2025-05-11T02:10:04" + }, + { + "id": 24012, + "name": "program", + "artist": "Christina Allison", + "songwriters": "Emily Wall", + "duration": "42:45", + "genres": "down", + "album": "price", + "created_at": "2025-07-30T08:11:20", + "updated_at": "2025-01-20T10:38:00" + }, + { + "id": 56537, + "name": "measure", + "artist": "Alison Lawson", + "songwriters": "Desiree Horton", + "duration": "32:57", + "genres": "enjoy", + "album": "Democrat", + "created_at": "2024-05-18T05:54:00", + "updated_at": "2025-03-27T08:04:39" + }, + { + "id": 15488, + "name": "PM", + "artist": "James Curtis MD", + "songwriters": "Tanya Hale MD", + "duration": "20:33", + "genres": "these", + "album": "fly", + "created_at": "2024-02-25T15:01:34", + "updated_at": "2025-03-13T15:52:19" + }, + { + "id": 77888, + "name": "Republican", + "artist": "Jimmy Campbell", + "songwriters": "Chelsea Moore", + "duration": "02:51", + "genres": "out", + "album": "language", + "created_at": "2024-10-21T23:25:47", + "updated_at": "2024-10-13T05:09:05" + }, + { + "id": 87219, + "name": "two", + "artist": "Jessica Cox", + "songwriters": "Jonathan Krause", + "duration": "02:35", + "genres": "let", + "album": "accept", + "created_at": "2025-08-04T02:12:20", + "updated_at": "2024-08-29T23:32:33" + }, + { + "id": 14285, + "name": "behavior", + "artist": "Cody Crawford", + "songwriters": "Alex Hayes", + "duration": "08:21", + "genres": "action", + "album": "simple", + "created_at": "2024-12-07T03:27:51", + "updated_at": "2025-04-21T04:38:27" + }, + { + "id": 32675, + "name": "present", + "artist": "Samuel Brooks", + "songwriters": "Donna Hancock", + "duration": "38:46", + "genres": "meet", + "album": "miss", + "created_at": "2023-08-17T23:24:41", + "updated_at": "2024-09-23T09:26:38" + }, + { + "id": 41534, + "name": "hold", + "artist": "Danny Stevens", + "songwriters": "Daniel Jones", + "duration": "09:43", + "genres": "maintain", + "album": "argue", + "created_at": "2024-07-05T10:59:05", + "updated_at": "2024-11-30T15:29:39" + }, + { + "id": 74753, + "name": "senior", + "artist": "Andrew Delgado", + "songwriters": "Jonathan Carrillo", + "duration": "51:42", + "genres": "must", + "album": "property", + "created_at": "2023-12-06T06:08:06", + "updated_at": "2024-10-12T01:35:31" + }, + { + "id": 8164, + "name": "respond", + "artist": "Deanna Clark", + "songwriters": "Jonathan Cunningham", + "duration": "27:17", + "genres": "only", + "album": "himself", + "created_at": "2024-08-05T09:17:02", + "updated_at": "2025-07-16T04:50:35" + }, + { + "id": 41412, + "name": "parent", + "artist": "Christina Berg", + "songwriters": "Frank Smith", + "duration": "23:45", + "genres": "read", + "album": "those", + "created_at": "2025-01-10T01:44:51", + "updated_at": "2024-10-25T05:30:16" + }, + { + "id": 63005, + "name": "business", + "artist": "Keith Gardner", + "songwriters": "Christopher Schroeder", + "duration": "34:48", + "genres": "next", + "album": "avoid", + "created_at": "2024-08-06T00:26:37", + "updated_at": "2025-01-30T14:21:48" + }, + { + "id": 15462, + "name": "upon", + "artist": "Heidi Dougherty", + "songwriters": "Eric Whitney", + "duration": "20:24", + "genres": "fast", + "album": "than", + "created_at": "2025-04-12T10:53:47", + "updated_at": "2024-10-08T17:30:34" + }, + { + "id": 37115, + "name": "industry", + "artist": "Phyllis Wilkinson", + "songwriters": "Kimberly Thompson", + "duration": "27:40", + "genres": "story", + "album": "security", + "created_at": "2025-06-10T02:38:09", + "updated_at": "2025-07-19T01:30:34" + }, + { + "id": 87267, + "name": "last", + "artist": "Julie Smith", + "songwriters": "Melissa Thomas", + "duration": "54:19", + "genres": "decision", + "album": "point", + "created_at": "2025-05-31T06:56:01", + "updated_at": "2024-11-08T20:44:22" + }, + { + "id": 29707, + "name": "choice", + "artist": "Robert Costa", + "songwriters": "Tanya Harris", + "duration": "12:24", + "genres": "live", + "album": "red", + "created_at": "2025-07-21T16:30:34", + "updated_at": "2024-08-16T04:51:06" + }, + { + "id": 21444, + "name": "lay", + "artist": "Samantha Martin", + "songwriters": "Michelle Hopkins", + "duration": "21:54", + "genres": "will", + "album": "everything", + "created_at": "2024-05-11T04:14:51", + "updated_at": "2024-11-13T15:39:24" + }, + { + "id": 40940, + "name": "first", + "artist": "Shane Foley", + "songwriters": "Courtney Wells", + "duration": "37:36", + "genres": "skill", + "album": "side", + "created_at": "2024-04-01T17:43:08", + "updated_at": "2025-05-16T13:33:50" + }, + { + "id": 6519, + "name": "south", + "artist": "Olivia Collins", + "songwriters": "Amy Mcintyre", + "duration": "15:23", + "genres": "detail", + "album": "food", + "created_at": "2023-11-04T03:36:19", + "updated_at": "2025-04-18T14:28:07" + }, + { + "id": 12268, + "name": "suffer", + "artist": "Betty Wilson", + "songwriters": "Michele Graham", + "duration": "12:19", + "genres": "wall", + "album": "total", + "created_at": "2024-07-01T05:09:35", + "updated_at": "2025-07-14T06:41:54" + }, + { + "id": 62942, + "name": "any", + "artist": "Maria Silva", + "songwriters": "Mary Stephens", + "duration": "17:01", + "genres": "miss", + "album": "training", + "created_at": "2023-10-21T06:07:43", + "updated_at": "2025-03-02T16:32:43" + }, + { + "id": 34799, + "name": "natural", + "artist": "Alex Walker", + "songwriters": "Sarah Oneal", + "duration": "29:28", + "genres": "magazine", + "album": "oil", + "created_at": "2024-04-07T07:00:55", + "updated_at": "2024-12-01T00:24:07" + }, + { + "id": 44935, + "name": "agency", + "artist": "Cristian Jones", + "songwriters": "Christopher Lopez", + "duration": "40:23", + "genres": "anyone", + "album": "lay", + "created_at": "2024-11-24T22:07:33", + "updated_at": "2025-06-26T19:51:59" + }, + { + "id": 90571, + "name": "alone", + "artist": "Rebecca Turner", + "songwriters": "Susan Bell", + "duration": "27:16", + "genres": "wide", + "album": "baby", + "created_at": "2025-03-24T17:50:50", + "updated_at": "2024-12-30T10:24:19" + }, + { + "id": 32503, + "name": "recognize", + "artist": "Meredith Rodriguez", + "songwriters": "Devin Williams", + "duration": "15:58", + "genres": "bill", + "album": "wish", + "created_at": "2025-04-24T00:44:36", + "updated_at": "2025-01-20T20:20:10" + }, + { + "id": 34121, + "name": "pass", + "artist": "Derek Gaines", + "songwriters": "Hailey Gross", + "duration": "34:57", + "genres": "big", + "album": "it", + "created_at": "2024-06-10T08:29:07", + "updated_at": "2024-10-08T23:54:24" + }, + { + "id": 76636, + "name": "live", + "artist": "Andrew Grant", + "songwriters": "Dennis Lopez", + "duration": "58:08", + "genres": "which", + "album": "you", + "created_at": "2024-03-19T18:56:58", + "updated_at": "2025-08-04T09:15:39" + }, + { + "id": 45668, + "name": "their", + "artist": "Sharon Davis", + "songwriters": "Nathan Baker", + "duration": "06:59", + "genres": "country", + "album": "work", + "created_at": "2024-10-30T00:24:56", + "updated_at": "2025-06-07T08:18:34" + }, + { + "id": 28477, + "name": "floor", + "artist": "Lisa Ellison", + "songwriters": "Sabrina Hanson", + "duration": "32:25", + "genres": "third", + "album": "yard", + "created_at": "2025-06-21T12:42:00", + "updated_at": "2025-01-27T00:08:34" + }, + { + "id": 24004, + "name": "daughter", + "artist": "Taylor Thompson", + "songwriters": "Pamela Bradley", + "duration": "32:02", + "genres": "significant", + "album": "exactly", + "created_at": "2024-07-28T19:33:36", + "updated_at": "2025-02-09T15:35:16" + }, + { + "id": 61452, + "name": "world", + "artist": "Jo Richardson", + "songwriters": "Jasmine Gordon", + "duration": "02:38", + "genres": "computer", + "album": "truth", + "created_at": "2024-08-17T08:08:46", + "updated_at": "2025-05-08T02:35:15" + }, + { + "id": 26044, + "name": "per", + "artist": "Bradley Lewis", + "songwriters": "Amanda Lee", + "duration": "56:17", + "genres": "about", + "album": "half", + "created_at": "2025-04-13T15:27:13", + "updated_at": "2025-06-28T00:05:37" + }, + { + "id": 74547, + "name": "agreement", + "artist": "Jonathan Jones", + "songwriters": "Kellie Navarro", + "duration": "38:52", + "genres": "happen", + "album": "eight", + "created_at": "2024-01-07T12:28:51", + "updated_at": "2025-07-17T10:53:38" + }, + { + "id": 78221, + "name": "nation", + "artist": "Jessica Ray", + "songwriters": "Joshua Mcdaniel", + "duration": "38:58", + "genres": "dream", + "album": "light", + "created_at": "2023-11-27T02:08:59", + "updated_at": "2024-11-02T11:16:07" + }, + { + "id": 94853, + "name": "hard", + "artist": "Jeffrey Donaldson MD", + "songwriters": "Kevin Turner", + "duration": "03:11", + "genres": "north", + "album": "well", + "created_at": "2025-05-25T20:55:19", + "updated_at": "2025-01-11T18:38:50" + }, + { + "id": 88824, + "name": "international", + "artist": "James Mason", + "songwriters": "Victor Brooks", + "duration": "16:14", + "genres": "occur", + "album": "point", + "created_at": "2024-04-22T02:26:01", + "updated_at": "2025-07-23T20:10:43" + }, + { + "id": 86111, + "name": "recognize", + "artist": "Stuart Cherry", + "songwriters": "Shannon Hernandez", + "duration": "11:15", + "genres": "suddenly", + "album": "himself", + "created_at": "2024-09-21T12:46:02", + "updated_at": "2025-02-13T22:51:48" + }, + { + "id": 61512, + "name": "yes", + "artist": "Donald Mueller", + "songwriters": "Michael Mendez", + "duration": "43:50", + "genres": "prevent", + "album": "purpose", + "created_at": "2025-08-03T21:50:43", + "updated_at": "2025-07-15T17:38:25" + }, + { + "id": 32511, + "name": "base", + "artist": "Ashley Ruiz", + "songwriters": "Angela Martin", + "duration": "37:10", + "genres": "student", + "album": "tonight", + "created_at": "2024-12-21T23:21:57", + "updated_at": "2025-04-30T23:50:29" + }, + { + "id": 45425, + "name": "read", + "artist": "Pamela Guzman", + "songwriters": "Jesus Potter", + "duration": "43:13", + "genres": "foot", + "album": "avoid", + "created_at": "2023-08-09T03:21:22", + "updated_at": "2025-06-16T11:09:58" + }, + { + "id": 74003, + "name": "financial", + "artist": "Eddie Moody", + "songwriters": "William Fernandez", + "duration": "41:13", + "genres": "debate", + "album": "religious", + "created_at": "2023-09-04T09:39:14", + "updated_at": "2025-05-20T22:52:14" + }, + { + "id": 58665, + "name": "defense", + "artist": "Ryan Gonzalez", + "songwriters": "Leah Contreras", + "duration": "35:55", + "genres": "director", + "album": "history", + "created_at": "2025-01-30T11:04:08", + "updated_at": "2025-02-11T11:21:58" + }, + { + "id": 29005, + "name": "rule", + "artist": "Robert Hill", + "songwriters": "Barbara Hughes", + "duration": "42:43", + "genres": "generation", + "album": "food", + "created_at": "2025-05-13T17:48:09", + "updated_at": "2024-11-11T18:31:55" + }, + { + "id": 46176, + "name": "before", + "artist": "Andrew Gonzalez", + "songwriters": "Scott Melton", + "duration": "14:47", + "genres": "time", + "album": "hard", + "created_at": "2024-02-06T16:21:51", + "updated_at": "2025-06-11T03:47:56" + }, + { + "id": 35042, + "name": "my", + "artist": "Megan Santos", + "songwriters": "Tonya Riley", + "duration": "47:01", + "genres": "senior", + "album": "here", + "created_at": "2024-09-17T20:10:43", + "updated_at": "2025-06-09T18:04:34" + }, + { + "id": 98934, + "name": "maintain", + "artist": "Timothy Perez", + "songwriters": "Kim Friedman MD", + "duration": "28:40", + "genres": "believe", + "album": "ball", + "created_at": "2025-07-27T09:43:49", + "updated_at": "2025-06-28T16:24:33" + }, + { + "id": 41221, + "name": "sort", + "artist": "Dana Ho", + "songwriters": "Corey Davis", + "duration": "29:56", + "genres": "in", + "album": "whom", + "created_at": "2025-02-09T00:08:10", + "updated_at": "2024-08-21T09:10:59" + }, + { + "id": 37619, + "name": "still", + "artist": "Christopher Mcdonald", + "songwriters": "Kiara Carr", + "duration": "14:58", + "genres": "place", + "album": "PM", + "created_at": "2025-01-27T22:38:17", + "updated_at": "2024-08-27T10:50:03" + }, + { + "id": 99020, + "name": "manage", + "artist": "Matthew Greer", + "songwriters": "Ryan Mclaughlin", + "duration": "27:16", + "genres": "amount", + "album": "civil", + "created_at": "2023-12-11T01:16:30", + "updated_at": "2024-12-10T12:10:30" + }, + { + "id": 26606, + "name": "pick", + "artist": "Nicole Hill", + "songwriters": "Hannah Adams", + "duration": "00:13", + "genres": "Mr", + "album": "guy", + "created_at": "2024-01-08T02:52:12", + "updated_at": "2025-07-05T21:18:18" + }, + { + "id": 49990, + "name": "statement", + "artist": "Michael Guerrero", + "songwriters": "Joseph Lewis", + "duration": "11:46", + "genres": "seven", + "album": "record", + "created_at": "2024-04-04T18:13:38", + "updated_at": "2024-12-25T08:52:37" + }, + { + "id": 28606, + "name": "bank", + "artist": "Robert Leon", + "songwriters": "Tyler Robinson", + "duration": "07:51", + "genres": "democratic", + "album": "various", + "created_at": "2024-11-04T12:53:22", + "updated_at": "2025-07-14T02:34:24" + }, + { + "id": 64705, + "name": "science", + "artist": "Taylor Dougherty", + "songwriters": "Jeffrey Price", + "duration": "52:51", + "genres": "off", + "album": "include", + "created_at": "2023-08-25T13:32:10", + "updated_at": "2025-04-23T13:01:57" + }, + { + "id": 88537, + "name": "blue", + "artist": "Catherine Gilbert", + "songwriters": "David Schaefer", + "duration": "04:37", + "genres": "management", + "album": "serve", + "created_at": "2025-04-29T06:58:25", + "updated_at": "2025-04-21T13:28:17" + }, + { + "id": 20063, + "name": "truth", + "artist": "Cameron Jones", + "songwriters": "Mr. Kevin King", + "duration": "27:21", + "genres": "million", + "album": "government", + "created_at": "2023-11-20T02:29:54", + "updated_at": "2025-07-30T22:40:23" + }, + { + "id": 5441, + "name": "after", + "artist": "Mark Gamble", + "songwriters": "Amanda Williams", + "duration": "50:52", + "genres": "present", + "album": "still", + "created_at": "2024-05-29T14:38:43", + "updated_at": "2024-10-03T12:14:35" + }, + { + "id": 58951, + "name": "learn", + "artist": "Amanda Le", + "songwriters": "William Carpenter", + "duration": "42:50", + "genres": "deep", + "album": "factor", + "created_at": "2024-04-08T11:45:59", + "updated_at": "2024-08-19T00:06:06" + }, + { + "id": 40156, + "name": "like", + "artist": "Steven Norris", + "songwriters": "Joseph Luna", + "duration": "52:49", + "genres": "style", + "album": "benefit", + "created_at": "2024-04-30T22:50:10", + "updated_at": "2024-09-16T22:16:43" + }, + { + "id": 1854, + "name": "discussion", + "artist": "John Gonzalez", + "songwriters": "Scott Jones", + "duration": "49:46", + "genres": "hotel", + "album": "hundred", + "created_at": "2024-01-05T12:25:16", + "updated_at": "2025-04-18T16:48:47" + }, + { + "id": 10462, + "name": "suddenly", + "artist": "Michael Hobbs", + "songwriters": "Nathan Lynn", + "duration": "59:34", + "genres": "against", + "album": "care", + "created_at": "2024-02-27T01:42:18", + "updated_at": "2024-10-02T01:41:19" + }, + { + "id": 21592, + "name": "company", + "artist": "Christina David", + "songwriters": "Destiny Williams", + "duration": "16:13", + "genres": "unit", + "album": "child", + "created_at": "2024-01-01T22:52:18", + "updated_at": "2024-11-19T13:21:17" + }, + { + "id": 78059, + "name": "class", + "artist": "Hector Turner", + "songwriters": "Shawn Gillespie", + "duration": "10:04", + "genres": "capital", + "album": "herself", + "created_at": "2023-10-29T05:38:24", + "updated_at": "2024-08-11T17:25:46" + }, + { + "id": 53748, + "name": "relate", + "artist": "Barbara Dixon", + "songwriters": "Timothy Mathews MD", + "duration": "40:08", + "genres": "head", + "album": "edge", + "created_at": "2025-06-15T20:31:32", + "updated_at": "2025-01-17T15:41:17" + }, + { + "id": 54390, + "name": "join", + "artist": "Douglas Clarke", + "songwriters": "Jason Black", + "duration": "45:14", + "genres": "live", + "album": "find", + "created_at": "2024-03-18T06:57:30", + "updated_at": "2024-12-09T13:00:19" + }, + { + "id": 85930, + "name": "family", + "artist": "Jasmine Hernandez", + "songwriters": "Frederick Jones", + "duration": "06:24", + "genres": "huge", + "album": "foot", + "created_at": "2025-07-15T13:24:00", + "updated_at": "2025-03-17T21:58:22" + }, + { + "id": 94318, + "name": "provide", + "artist": "Catherine Hull", + "songwriters": "Nathan Williams", + "duration": "02:38", + "genres": "control", + "album": "reason", + "created_at": "2024-08-02T02:17:36", + "updated_at": "2024-08-16T18:09:23" + }, + { + "id": 78488, + "name": "they", + "artist": "Stephanie Gibson", + "songwriters": "Glenda Best", + "duration": "57:33", + "genres": "some", + "album": "seven", + "created_at": "2024-03-02T14:11:59", + "updated_at": "2025-01-10T10:36:25" + }, + { + "id": 93517, + "name": "government", + "artist": "Erin Short", + "songwriters": "John Stewart", + "duration": "27:02", + "genres": "experience", + "album": "ten", + "created_at": "2025-04-21T20:57:27", + "updated_at": "2024-10-14T21:53:19" + }, + { + "id": 28612, + "name": "medical", + "artist": "Sergio Pace", + "songwriters": "Sarah Fox", + "duration": "50:48", + "genres": "class", + "album": "consider", + "created_at": "2025-04-07T21:06:30", + "updated_at": "2024-12-09T06:19:57" + }, + { + "id": 69187, + "name": "view", + "artist": "Keith Malone", + "songwriters": "Dennis Coleman", + "duration": "36:12", + "genres": "away", + "album": "maintain", + "created_at": "2024-12-20T08:40:28", + "updated_at": "2025-07-24T23:25:48" + }, + { + "id": 40000, + "name": "want", + "artist": "Colleen Little", + "songwriters": "Lindsay Mckenzie", + "duration": "16:49", + "genres": "on", + "album": "health", + "created_at": "2024-01-26T14:43:52", + "updated_at": "2024-11-29T19:14:53" + }, + { + "id": 65449, + "name": "science", + "artist": "Rodney Colon", + "songwriters": "Thomas Howe", + "duration": "18:28", + "genres": "past", + "album": "yard", + "created_at": "2024-01-02T18:22:16", + "updated_at": "2025-06-22T20:36:53" + }, + { + "id": 93251, + "name": "purpose", + "artist": "Lauren Johnson", + "songwriters": "Caitlin Perry", + "duration": "14:41", + "genres": "town", + "album": "begin", + "created_at": "2023-11-10T12:52:02", + "updated_at": "2025-01-15T12:47:41" + }, + { + "id": 87145, + "name": "study", + "artist": "Joshua Mcclain", + "songwriters": "Thomas Mcdaniel", + "duration": "07:49", + "genres": "question", + "album": "baby", + "created_at": "2025-04-29T00:51:03", + "updated_at": "2025-01-23T01:11:53" + }, + { + "id": 49911, + "name": "senior", + "artist": "Andrea Ramos MD", + "songwriters": "Anthony Jackson", + "duration": "18:27", + "genres": "program", + "album": "rate", + "created_at": "2024-03-04T04:23:10", + "updated_at": "2025-06-27T08:28:30" + }, + { + "id": 82499, + "name": "especially", + "artist": "James Phillips", + "songwriters": "Travis Bishop", + "duration": "51:15", + "genres": "successful", + "album": "nature", + "created_at": "2023-10-26T05:25:17", + "updated_at": "2024-12-26T13:18:02" + }, + { + "id": 77020, + "name": "tell", + "artist": "Yvette Oliver", + "songwriters": "Angel Cervantes", + "duration": "09:11", + "genres": "politics", + "album": "main", + "created_at": "2024-11-01T17:08:34", + "updated_at": "2025-03-15T16:35:26" + }, + { + "id": 55834, + "name": "happen", + "artist": "Dawn Burns", + "songwriters": "Jesse Griffin", + "duration": "43:23", + "genres": "degree", + "album": "security", + "created_at": "2024-07-22T16:54:53", + "updated_at": "2025-01-01T02:07:21" + }, + { + "id": 100000, + "name": "ever", + "artist": "James Dawson", + "songwriters": "William Atkinson", + "duration": "09:19", + "genres": "war", + "album": "start", + "created_at": "2024-04-13T00:14:02", + "updated_at": "2024-12-04T17:55:19" + }, + { + "id": 92191, + "name": "town", + "artist": "Caitlyn Perkins", + "songwriters": "Aaron Johnson", + "duration": "09:23", + "genres": "clear", + "album": "place", + "created_at": "2025-07-12T20:50:36", + "updated_at": "2024-10-04T23:30:57" + }, + { + "id": 68336, + "name": "left", + "artist": "Carrie Johnson", + "songwriters": "Tiffany Hernandez", + "duration": "35:07", + "genres": "understand", + "album": "she", + "created_at": "2023-12-19T08:22:30", + "updated_at": "2025-03-17T02:04:32" + }, + { + "id": 16039, + "name": "tonight", + "artist": "Kevin Alexander", + "songwriters": "Robert Parker", + "duration": "54:01", + "genres": "list", + "album": "necessary", + "created_at": "2023-10-30T02:02:01", + "updated_at": "2025-07-08T15:33:29" + }, + { + "id": 62034, + "name": "probably", + "artist": "Amy Robertson", + "songwriters": "Mary Lyons", + "duration": "55:04", + "genres": "tough", + "album": "attack", + "created_at": "2024-01-17T12:36:52", + "updated_at": "2025-04-10T19:11:31" + }, + { + "id": 47594, + "name": "quickly", + "artist": "Bryan Drake", + "songwriters": "Tammy Barr", + "duration": "27:33", + "genres": "number", + "album": "listen", + "created_at": "2025-01-23T03:49:09", + "updated_at": "2025-03-17T07:00:10" + }, + { + "id": 4230, + "name": "wind", + "artist": "Steven Sawyer", + "songwriters": "Larry Goodman", + "duration": "24:48", + "genres": "article", + "album": "water", + "created_at": "2025-01-02T21:29:11", + "updated_at": "2024-09-09T15:45:52" + }, + { + "id": 84525, + "name": "term", + "artist": "Joy Vang", + "songwriters": "Robin Marks", + "duration": "09:28", + "genres": "he", + "album": "real", + "created_at": "2024-07-24T07:49:29", + "updated_at": "2024-11-02T22:10:47" + }, + { + "id": 34829, + "name": "hundred", + "artist": "Joseph Gardner", + "songwriters": "Karen Hopkins", + "duration": "15:01", + "genres": "grow", + "album": "education", + "created_at": "2025-03-19T11:55:15", + "updated_at": "2025-01-22T06:46:29" + }, + { + "id": 60260, + "name": "movie", + "artist": "James Miller", + "songwriters": "Brian James PhD", + "duration": "19:09", + "genres": "Mr", + "album": "candidate", + "created_at": "2023-08-10T07:12:30", + "updated_at": "2025-07-21T01:34:01" + }, + { + "id": 19761, + "name": "really", + "artist": "Ronald Lee", + "songwriters": "Kaitlyn Jenkins", + "duration": "35:48", + "genres": "age", + "album": "site", + "created_at": "2023-08-31T00:39:46", + "updated_at": "2025-07-09T03:30:00" + } +] \ No newline at end of file diff --git a/src/data/users.json b/src/data/users.json new file mode 100644 index 0000000..1995aff --- /dev/null +++ b/src/data/users.json @@ -0,0 +1,1002 @@ +[ + { + "id": 63102, + "first_name": "Barbara", + "last_name": "Johnson", + "email": "porternatalie@example.net", + "gender": "Genderqueer", + "favorite_genres": "Techno", + "created_at": "2023-08-14T18:35:10", + "updated_at": "2025-03-24T23:51:41" + }, + { + "id": 11782, + "first_name": "Tanner", + "last_name": "Lambert", + "email": "dpetersen@example.org", + "gender": "Genderqueer", + "favorite_genres": "Rock", + "created_at": "2023-10-19T07:32:03", + "updated_at": "2024-10-13T01:09:39" + }, + { + "id": 96260, + "first_name": "Barbara", + "last_name": "Allen", + "email": "vwade@example.org", + "gender": "Male", + "favorite_genres": "Metal", + "created_at": "2024-05-28T16:27:26", + "updated_at": "2025-01-06T12:26:28" + }, + { + "id": 65175, + "first_name": "Benjamin", + "last_name": "Massey", + "email": "austinlawrence@example.org", + "gender": "Bigender", + "favorite_genres": "Electronic", + "created_at": "2024-12-18T11:26:09", + "updated_at": "2024-08-30T11:36:26" + }, + { + "id": 55650, + "first_name": "Ricky", + "last_name": "Santos", + "email": "rramos@example.org", + "gender": "Gender questioning", + "favorite_genres": "Techno", + "created_at": "2024-11-10T23:02:53", + "updated_at": "2025-03-30T12:49:50" + }, + { + "id": 8040, + "first_name": "Shawn", + "last_name": "Kelly", + "email": "hartmanmicheal@example.org", + "gender": "Genderfluid", + "favorite_genres": "Electronic", + "created_at": "2024-10-04T11:29:57", + "updated_at": "2024-12-16T13:48:00" + }, + { + "id": 21505, + "first_name": "Kimberly", + "last_name": "Young", + "email": "jameskenneth@example.org", + "gender": "Non-binary", + "favorite_genres": "Indie", + "created_at": "2025-06-19T23:49:30", + "updated_at": "2025-04-26T14:01:25" + }, + { + "id": 1260, + "first_name": "Adam", + "last_name": "Jones", + "email": "patriciastephens@example.net", + "gender": "Non-binary", + "favorite_genres": "Punk", + "created_at": "2025-06-18T00:01:26", + "updated_at": "2024-09-15T04:30:25" + }, + { + "id": 91641, + "first_name": "William", + "last_name": "Hall", + "email": "tararoth@example.net", + "gender": "Bigender", + "favorite_genres": "Alternative", + "created_at": "2023-11-28T03:15:49", + "updated_at": "2024-08-15T00:25:32" + }, + { + "id": 80198, + "first_name": "Michael", + "last_name": "White", + "email": "aimee01@example.net", + "gender": "Non-binary", + "favorite_genres": "Techno", + "created_at": "2024-04-01T16:58:13", + "updated_at": "2024-10-13T23:47:38" + }, + { + "id": 83891, + "first_name": "Dylan", + "last_name": "Jacobson", + "email": "qmcguire@example.org", + "gender": "Genderfluid", + "favorite_genres": "Hip Hop", + "created_at": "2024-11-21T01:53:00", + "updated_at": "2024-11-04T16:33:01" + }, + { + "id": 11203, + "first_name": "Kimberly", + "last_name": "Johnson", + "email": "eric37@example.com", + "gender": "Male", + "favorite_genres": "Reggae", + "created_at": "2025-01-23T04:06:02", + "updated_at": "2025-02-28T22:40:21" + }, + { + "id": 91800, + "first_name": "Joseph", + "last_name": "Flynn", + "email": "qlamb@example.net", + "gender": "Gender questioning", + "favorite_genres": "Metal", + "created_at": "2025-07-18T14:11:03", + "updated_at": "2025-04-24T05:03:20" + }, + { + "id": 67385, + "first_name": "Rebekah", + "last_name": "Cervantes", + "email": "xparker@example.com", + "gender": "Gender questioning", + "favorite_genres": "Punk", + "created_at": "2024-10-26T16:56:44", + "updated_at": "2025-01-12T04:05:16" + }, + { + "id": 54214, + "first_name": "Jon", + "last_name": "Duncan", + "email": "bkidd@example.org", + "gender": "Gender nonconforming", + "favorite_genres": "Folk", + "created_at": "2025-06-02T14:44:31", + "updated_at": "2024-11-09T18:49:46" + }, + { + "id": 49756, + "first_name": "Justin", + "last_name": "Lester", + "email": "tmarshall@example.org", + "gender": "Non-binary", + "favorite_genres": "Pop", + "created_at": "2024-12-04T19:31:30", + "updated_at": "2025-06-17T16:27:25" + }, + { + "id": 32479, + "first_name": "Sara", + "last_name": "Hartman", + "email": "lrandolph@example.com", + "gender": "Female", + "favorite_genres": "Funk", + "created_at": "2024-06-28T00:19:01", + "updated_at": "2024-09-06T07:11:15" + }, + { + "id": 96758, + "first_name": "Lori", + "last_name": "Wilson", + "email": "sgriffin@example.com", + "gender": "Male", + "favorite_genres": "Funk", + "created_at": "2023-12-10T02:08:07", + "updated_at": "2024-08-08T01:38:04" + }, + { + "id": 85155, + "first_name": "Tony", + "last_name": "Lopez", + "email": "michael10@example.org", + "gender": "Gender nonconforming", + "favorite_genres": "Reggae", + "created_at": "2024-05-30T02:38:22", + "updated_at": "2025-02-22T18:12:17" + }, + { + "id": 90970, + "first_name": "Anthony", + "last_name": "Smith", + "email": "ashley75@example.org", + "gender": "Gender nonconforming", + "favorite_genres": "Hip Hop", + "created_at": "2023-12-24T13:05:57", + "updated_at": "2025-02-16T01:38:22" + }, + { + "id": 92572, + "first_name": "Kristen", + "last_name": "Ramirez", + "email": "john95@example.com", + "gender": "Genderfluid", + "favorite_genres": "Techno", + "created_at": "2024-05-22T21:55:41", + "updated_at": "2025-01-18T08:48:31" + }, + { + "id": 48340, + "first_name": "Scott", + "last_name": "Patton", + "email": "calebsmith@example.org", + "gender": "Male", + "favorite_genres": "R&B", + "created_at": "2025-06-08T03:53:09", + "updated_at": "2025-01-20T20:40:13" + }, + { + "id": 95056, + "first_name": "Travis", + "last_name": "Garrett", + "email": "mary18@example.org", + "gender": "Male", + "favorite_genres": "Alternative", + "created_at": "2023-11-20T22:57:33", + "updated_at": "2024-10-24T11:15:31" + }, + { + "id": 25961, + "first_name": "Carolyn", + "last_name": "Sweeney", + "email": "nmartinez@example.net", + "gender": "Female", + "favorite_genres": "Classical", + "created_at": "2024-05-14T13:05:38", + "updated_at": "2025-01-26T01:49:49" + }, + { + "id": 91090, + "first_name": "Amanda", + "last_name": "Burch", + "email": "cindy09@example.org", + "gender": "Bigender", + "favorite_genres": "Alternative", + "created_at": "2024-11-05T07:56:12", + "updated_at": "2024-08-17T17:13:10" + }, + { + "id": 87896, + "first_name": "David", + "last_name": "Goodwin", + "email": "jamesgreene@example.org", + "gender": "Genderfluid", + "favorite_genres": "Punk", + "created_at": "2024-02-19T16:54:33", + "updated_at": "2025-06-05T15:05:32" + }, + { + "id": 37714, + "first_name": "Tami", + "last_name": "Harris", + "email": "bryanhunter@example.org", + "gender": "Genderfluid", + "favorite_genres": "Classical", + "created_at": "2024-08-01T05:59:43", + "updated_at": "2024-10-12T17:26:31" + }, + { + "id": 97027, + "first_name": "Donald", + "last_name": "Morrison", + "email": "john82@example.net", + "gender": "Gender nonconforming", + "favorite_genres": "Blues", + "created_at": "2024-02-28T21:06:18", + "updated_at": "2025-06-28T19:56:42" + }, + { + "id": 76999, + "first_name": "Sandra", + "last_name": "Roy", + "email": "ryan04@example.net", + "gender": "Agender", + "favorite_genres": "Techno", + "created_at": "2025-06-28T10:37:10", + "updated_at": "2025-05-25T22:03:33" + }, + { + "id": 59590, + "first_name": "April", + "last_name": "Cannon", + "email": "aduncan@example.net", + "gender": "Agender", + "favorite_genres": "Funk", + "created_at": "2023-08-21T10:43:01", + "updated_at": "2025-01-21T23:45:49" + }, + { + "id": 12632, + "first_name": "Aaron", + "last_name": "Gonzalez", + "email": "richardsonrussell@example.com", + "gender": "Male", + "favorite_genres": "Punk", + "created_at": "2024-08-26T10:15:53", + "updated_at": "2025-02-06T07:55:04" + }, + { + "id": 37408, + "first_name": "Mark", + "last_name": "Stafford", + "email": "daltonjoshua@example.com", + "gender": "Bigender", + "favorite_genres": "Hip Hop", + "created_at": "2023-11-05T20:26:13", + "updated_at": "2024-08-07T15:42:46" + }, + { + "id": 32088, + "first_name": "Heidi", + "last_name": "Scott", + "email": "amyelliott@example.net", + "gender": "Agender", + "favorite_genres": "Punk", + "created_at": "2025-01-20T07:38:36", + "updated_at": "2025-02-05T03:49:13" + }, + { + "id": 94805, + "first_name": "Rachel", + "last_name": "Bowers", + "email": "mitchellteresa@example.org", + "gender": "Agender", + "favorite_genres": "Metal", + "created_at": "2023-10-07T16:53:25", + "updated_at": "2024-12-03T19:08:20" + }, + { + "id": 64492, + "first_name": "Anna", + "last_name": "Brown", + "email": "leeanita@example.net", + "gender": "Female", + "favorite_genres": "R&B", + "created_at": "2025-05-23T05:11:57", + "updated_at": "2024-11-16T07:05:11" + }, + { + "id": 98380, + "first_name": "Anna", + "last_name": "Olson", + "email": "wlee@example.com", + "gender": "Genderfluid", + "favorite_genres": "R&B", + "created_at": "2024-12-09T00:03:56", + "updated_at": "2025-06-21T08:34:25" + }, + { + "id": 3923, + "first_name": "Sarah", + "last_name": "Williamson", + "email": "natasharoberts@example.net", + "gender": "Agender", + "favorite_genres": "Metal", + "created_at": "2024-06-13T22:25:20", + "updated_at": "2025-07-13T19:18:24" + }, + { + "id": 82398, + "first_name": "Scott", + "last_name": "Ward", + "email": "adampalmer@example.org", + "gender": "Agender", + "favorite_genres": "Rock", + "created_at": "2025-06-16T05:19:10", + "updated_at": "2025-07-19T10:46:27" + }, + { + "id": 55987, + "first_name": "Lisa", + "last_name": "Frederick", + "email": "nnorris@example.org", + "gender": "Genderfluid", + "favorite_genres": "Classical", + "created_at": "2024-09-03T22:40:12", + "updated_at": "2024-08-13T05:30:13" + }, + { + "id": 29259, + "first_name": "Amanda", + "last_name": "Bray", + "email": "stevejohnson@example.com", + "gender": "Gender questioning", + "favorite_genres": "Indie", + "created_at": "2023-09-05T11:35:29", + "updated_at": "2025-04-16T04:35:09" + }, + { + "id": 599, + "first_name": "Chad", + "last_name": "Clark", + "email": "evanskelly@example.net", + "gender": "Genderfluid", + "favorite_genres": "Alternative", + "created_at": "2024-02-14T16:21:37", + "updated_at": "2025-03-17T00:28:42" + }, + { + "id": 52629, + "first_name": "Samantha", + "last_name": "Hendrix", + "email": "lgarcia@example.com", + "gender": "Gender nonconforming", + "favorite_genres": "Metal", + "created_at": "2025-01-18T13:39:50", + "updated_at": "2025-04-13T14:23:09" + }, + { + "id": 4941, + "first_name": "Molly", + "last_name": "Duncan", + "email": "justinanderson@example.com", + "gender": "Gender nonconforming", + "favorite_genres": "R&B", + "created_at": "2023-12-17T23:55:59", + "updated_at": "2024-08-24T13:28:56" + }, + { + "id": 64982, + "first_name": "Anna", + "last_name": "Clark", + "email": "jeffery36@example.org", + "gender": "Agender", + "favorite_genres": "Classical", + "created_at": "2024-04-03T06:45:59", + "updated_at": "2024-08-16T19:00:20" + }, + { + "id": 93072, + "first_name": "Kristina", + "last_name": "Williams", + "email": "kbrooks@example.org", + "gender": "Male", + "favorite_genres": "Alternative", + "created_at": "2023-10-05T00:38:36", + "updated_at": "2025-02-02T22:06:25" + }, + { + "id": 523, + "first_name": "Beth", + "last_name": "Mccoy", + "email": "lambcolleen@example.org", + "gender": "Gender nonconforming", + "favorite_genres": "Folk", + "created_at": "2024-07-23T02:23:22", + "updated_at": "2024-10-08T21:05:08" + }, + { + "id": 61745, + "first_name": "Jennifer", + "last_name": "Cooper", + "email": "merrittangel@example.net", + "gender": "Female", + "favorite_genres": "Country", + "created_at": "2024-02-09T14:36:04", + "updated_at": "2024-09-09T19:23:16" + }, + { + "id": 31018, + "first_name": "Brett", + "last_name": "Soto", + "email": "gibsontheresa@example.com", + "gender": "Genderqueer", + "favorite_genres": "Pop", + "created_at": "2023-09-21T13:32:24", + "updated_at": "2024-11-24T10:01:15" + }, + { + "id": 37964, + "first_name": "Patricia", + "last_name": "Hernandez", + "email": "rodneydouglas@example.com", + "gender": "Non-binary", + "favorite_genres": "Folk", + "created_at": "2024-08-17T19:22:29", + "updated_at": "2024-11-30T18:52:31" + }, + { + "id": 99954, + "first_name": "Jose", + "last_name": "Chen", + "email": "hawkinsjessica@example.org", + "gender": "Female", + "favorite_genres": "Hip Hop", + "created_at": "2025-03-12T20:08:42", + "updated_at": "2025-03-27T12:33:52" + }, + { + "id": 18539, + "first_name": "Earl", + "last_name": "Perry", + "email": "vmitchell@example.net", + "gender": "Female", + "favorite_genres": "Electronic", + "created_at": "2024-05-24T23:20:53", + "updated_at": "2024-10-21T07:39:25" + }, + { + "id": 90332, + "first_name": "Christine", + "last_name": "Garcia", + "email": "eduardoblanchard@example.net", + "gender": "Bigender", + "favorite_genres": "R&B", + "created_at": "2023-11-01T20:18:17", + "updated_at": "2025-01-15T17:52:29" + }, + { + "id": 62637, + "first_name": "Scott", + "last_name": "Sanchez", + "email": "karen08@example.org", + "gender": "Gender questioning", + "favorite_genres": "Hip Hop", + "created_at": "2024-11-23T05:07:28", + "updated_at": "2025-03-15T20:48:42" + }, + { + "id": 26027, + "first_name": "Krista", + "last_name": "Gardner", + "email": "laura13@example.org", + "gender": "Gender nonconforming", + "favorite_genres": "Rock", + "created_at": "2024-10-31T02:59:27", + "updated_at": "2024-11-07T06:33:29" + }, + { + "id": 43269, + "first_name": "Evelyn", + "last_name": "Wolfe", + "email": "jhuff@example.net", + "gender": "Agender", + "favorite_genres": "Reggae", + "created_at": "2024-01-27T19:17:43", + "updated_at": "2024-08-09T02:58:42" + }, + { + "id": 90611, + "first_name": "Ashley", + "last_name": "Elliott", + "email": "srich@example.com", + "gender": "Male", + "favorite_genres": "Classical", + "created_at": "2024-06-22T22:26:44", + "updated_at": "2025-06-06T15:25:54" + }, + { + "id": 30317, + "first_name": "Cody", + "last_name": "May", + "email": "lguzman@example.net", + "gender": "Male", + "favorite_genres": "Indie", + "created_at": "2024-11-02T10:09:25", + "updated_at": "2024-12-12T07:24:58" + }, + { + "id": 44472, + "first_name": "Caitlin", + "last_name": "Mitchell", + "email": "gibsonscott@example.org", + "gender": "Genderqueer", + "favorite_genres": "Punk", + "created_at": "2024-08-09T22:22:03", + "updated_at": "2024-08-05T19:47:43" + }, + { + "id": 31979, + "first_name": "Alicia", + "last_name": "Hernandez", + "email": "jennifer65@example.net", + "gender": "Bigender", + "favorite_genres": "Jazz", + "created_at": "2023-12-22T18:31:49", + "updated_at": "2025-06-16T12:38:54" + }, + { + "id": 63235, + "first_name": "Charles", + "last_name": "Davis", + "email": "zfuller@example.net", + "gender": "Gender nonconforming", + "favorite_genres": "Funk", + "created_at": "2024-01-09T10:20:15", + "updated_at": "2025-03-02T13:57:41" + }, + { + "id": 88825, + "first_name": "Richard", + "last_name": "Nguyen", + "email": "jasongarcia@example.com", + "gender": "Male", + "favorite_genres": "Indie", + "created_at": "2023-12-23T04:55:06", + "updated_at": "2025-03-23T15:22:51" + }, + { + "id": 89666, + "first_name": "Maria", + "last_name": "Fuentes", + "email": "jefferyware@example.org", + "gender": "Female", + "favorite_genres": "Techno", + "created_at": "2024-01-11T14:42:19", + "updated_at": "2024-12-31T04:09:40" + }, + { + "id": 4250, + "first_name": "Ann", + "last_name": "Kent", + "email": "mathisdarlene@example.org", + "gender": "Bigender", + "favorite_genres": "Indie", + "created_at": "2024-02-26T20:27:37", + "updated_at": "2025-02-11T16:02:34" + }, + { + "id": 44350, + "first_name": "Erica", + "last_name": "Hughes", + "email": "david55@example.org", + "gender": "Genderqueer", + "favorite_genres": "Folk", + "created_at": "2025-07-28T15:45:07", + "updated_at": "2024-10-10T05:14:43" + }, + { + "id": 70214, + "first_name": "Michael", + "last_name": "Melton", + "email": "patrickshane@example.net", + "gender": "Bigender", + "favorite_genres": "Hip Hop", + "created_at": "2023-09-28T09:27:27", + "updated_at": "2025-07-11T11:05:07" + }, + { + "id": 95878, + "first_name": "Thomas", + "last_name": "Marks", + "email": "melissafernandez@example.net", + "gender": "Agender", + "favorite_genres": "Pop", + "created_at": "2024-10-13T14:15:07", + "updated_at": "2025-04-20T17:41:35" + }, + { + "id": 32770, + "first_name": "Nathan", + "last_name": "Douglas", + "email": "williamhall@example.org", + "gender": "Non-binary", + "favorite_genres": "Hip Hop", + "created_at": "2025-07-23T06:32:42", + "updated_at": "2024-10-13T02:20:21" + }, + { + "id": 34802, + "first_name": "Richard", + "last_name": "Poole", + "email": "karlamanning@example.com", + "gender": "Agender", + "favorite_genres": "Funk", + "created_at": "2024-10-06T00:06:00", + "updated_at": "2024-08-15T00:03:51" + }, + { + "id": 27441, + "first_name": "Michele", + "last_name": "Campbell", + "email": "leekevin@example.org", + "gender": "Bigender", + "favorite_genres": "Folk", + "created_at": "2023-09-11T05:39:04", + "updated_at": "2025-05-30T05:24:34" + }, + { + "id": 29038, + "first_name": "Teresa", + "last_name": "Shepard", + "email": "gonzalesmelissa@example.net", + "gender": "Gender nonconforming", + "favorite_genres": "R&B", + "created_at": "2024-05-13T10:58:22", + "updated_at": "2024-11-22T12:48:58" + }, + { + "id": 99981, + "first_name": "Kimberly", + "last_name": "Sutton", + "email": "martinlauren@example.net", + "gender": "Agender", + "favorite_genres": "Alternative", + "created_at": "2023-08-21T14:54:32", + "updated_at": "2024-10-02T16:59:04" + }, + { + "id": 82099, + "first_name": "Scott", + "last_name": "Smith", + "email": "ldiaz@example.org", + "gender": "Male", + "favorite_genres": "Rock", + "created_at": "2024-06-14T07:22:14", + "updated_at": "2025-06-01T12:35:39" + }, + { + "id": 95443, + "first_name": "Maxwell", + "last_name": "Salazar", + "email": "qthomas@example.org", + "gender": "Gender questioning", + "favorite_genres": "Funk", + "created_at": "2024-09-18T02:39:48", + "updated_at": "2024-10-13T03:40:41" + }, + { + "id": 3293, + "first_name": "Glenn", + "last_name": "Guerrero", + "email": "misty36@example.com", + "gender": "Genderfluid", + "favorite_genres": "Folk", + "created_at": "2024-09-05T03:07:11", + "updated_at": "2025-01-12T15:59:50" + }, + { + "id": 54635, + "first_name": "Melissa", + "last_name": "Martinez", + "email": "ipalmer@example.com", + "gender": "Agender", + "favorite_genres": "Hip Hop", + "created_at": "2024-01-27T21:46:15", + "updated_at": "2025-02-19T19:38:06" + }, + { + "id": 54733, + "first_name": "Elizabeth", + "last_name": "Harvey", + "email": "jessicahoffman@example.net", + "gender": "Genderfluid", + "favorite_genres": "Punk", + "created_at": "2024-11-25T03:34:25", + "updated_at": "2024-12-12T10:02:53" + }, + { + "id": 62558, + "first_name": "Maria", + "last_name": "Anderson", + "email": "derekgarcia@example.net", + "gender": "Genderqueer", + "favorite_genres": "Reggae", + "created_at": "2025-07-13T22:49:31", + "updated_at": "2024-08-30T15:42:00" + }, + { + "id": 23429, + "first_name": "Willie", + "last_name": "Banks", + "email": "teresarobinson@example.com", + "gender": "Genderqueer", + "favorite_genres": "Folk", + "created_at": "2025-02-18T16:24:52", + "updated_at": "2024-09-01T17:23:14" + }, + { + "id": 61339, + "first_name": "Jessica", + "last_name": "Franklin", + "email": "cdavis@example.com", + "gender": "Genderqueer", + "favorite_genres": "Folk", + "created_at": "2023-10-02T08:21:23", + "updated_at": "2024-11-19T21:48:12" + }, + { + "id": 3556, + "first_name": "Pamela", + "last_name": "Shaffer", + "email": "denise54@example.com", + "gender": "Female", + "favorite_genres": "Folk", + "created_at": "2025-03-13T01:54:59", + "updated_at": "2025-04-16T09:18:20" + }, + { + "id": 68834, + "first_name": "Sandra", + "last_name": "Humphrey", + "email": "rodriguezvanessa@example.com", + "gender": "Female", + "favorite_genres": "Country", + "created_at": "2024-06-10T23:38:23", + "updated_at": "2024-08-13T14:39:58" + }, + { + "id": 58467, + "first_name": "Jasmine", + "last_name": "Garrett", + "email": "roberthendricks@example.net", + "gender": "Non-binary", + "favorite_genres": "Electronic", + "created_at": "2024-04-09T07:13:01", + "updated_at": "2025-01-26T14:31:28" + }, + { + "id": 66452, + "first_name": "Alexis", + "last_name": "Moore", + "email": "davidevans@example.org", + "gender": "Gender questioning", + "favorite_genres": "Jazz", + "created_at": "2025-01-23T09:27:48", + "updated_at": "2025-06-17T16:43:27" + }, + { + "id": 2116, + "first_name": "Christopher", + "last_name": "Mason", + "email": "zlucas@example.org", + "gender": "Gender nonconforming", + "favorite_genres": "Metal", + "created_at": "2024-09-01T14:13:35", + "updated_at": "2024-09-22T11:01:22" + }, + { + "id": 76799, + "first_name": "Amanda", + "last_name": "Kelley", + "email": "kenneth30@example.org", + "gender": "Female", + "favorite_genres": "Jazz", + "created_at": "2024-10-15T02:10:12", + "updated_at": "2024-10-19T01:12:22" + }, + { + "id": 70802, + "first_name": "Savannah", + "last_name": "Allison", + "email": "zdixon@example.org", + "gender": "Gender questioning", + "favorite_genres": "Electronic", + "created_at": "2024-09-20T17:57:54", + "updated_at": "2025-04-28T22:46:11" + }, + { + "id": 2933, + "first_name": "Stephanie", + "last_name": "Leblanc", + "email": "bliu@example.com", + "gender": "Genderfluid", + "favorite_genres": "Electronic", + "created_at": "2025-05-25T20:20:33", + "updated_at": "2025-07-18T02:35:40" + }, + { + "id": 99958, + "first_name": "Robert", + "last_name": "Randall", + "email": "sonya81@example.com", + "gender": "Male", + "favorite_genres": "Indie", + "created_at": "2024-05-25T11:41:12", + "updated_at": "2024-10-28T00:16:48" + }, + { + "id": 24687, + "first_name": "George", + "last_name": "Garcia", + "email": "garciasharon@example.org", + "gender": "Genderqueer", + "favorite_genres": "Metal", + "created_at": "2024-04-01T21:19:55", + "updated_at": "2025-04-12T12:49:32" + }, + { + "id": 42147, + "first_name": "Carrie", + "last_name": "White", + "email": "zstephens@example.com", + "gender": "Genderqueer", + "favorite_genres": "Pop", + "created_at": "2024-01-25T14:04:13", + "updated_at": "2024-09-24T01:00:09" + }, + { + "id": 27500, + "first_name": "Susan", + "last_name": "Hernandez", + "email": "antoniohenderson@example.net", + "gender": "Gender questioning", + "favorite_genres": "Classical", + "created_at": "2024-11-09T11:05:15", + "updated_at": "2025-03-29T10:44:58" + }, + { + "id": 42730, + "first_name": "Madison", + "last_name": "Jordan", + "email": "jessicaromero@example.org", + "gender": "Non-binary", + "favorite_genres": "R&B", + "created_at": "2024-01-07T00:46:46", + "updated_at": "2025-06-05T11:47:46" + }, + { + "id": 62154, + "first_name": "Stephanie", + "last_name": "Wright", + "email": "bethreed@example.net", + "gender": "Non-binary", + "favorite_genres": "Folk", + "created_at": "2023-12-19T12:04:08", + "updated_at": "2024-09-25T15:17:47" + }, + { + "id": 95251, + "first_name": "Luke", + "last_name": "Huerta", + "email": "zfloyd@example.com", + "gender": "Bigender", + "favorite_genres": "Alternative", + "created_at": "2024-08-29T19:03:41", + "updated_at": "2024-11-08T18:36:18" + }, + { + "id": 98990, + "first_name": "Evelyn", + "last_name": "Mcclure", + "email": "jane12@example.com", + "gender": "Non-binary", + "favorite_genres": "Rock", + "created_at": "2023-10-25T14:29:36", + "updated_at": "2024-10-10T17:21:08" + }, + { + "id": 11150, + "first_name": "Heather", + "last_name": "Hill", + "email": "danielzimmerman@example.org", + "gender": "Male", + "favorite_genres": "Reggae", + "created_at": "2025-03-14T08:34:29", + "updated_at": "2024-09-26T10:16:23" + }, + { + "id": 29671, + "first_name": "Travis", + "last_name": "Maldonado", + "email": "smithmelissa@example.org", + "gender": "Agender", + "favorite_genres": "Reggae", + "created_at": "2023-09-29T23:06:34", + "updated_at": "2025-04-18T02:01:35" + }, + { + "id": 55332, + "first_name": "Kayla", + "last_name": "Adams", + "email": "ortizmichael@example.com", + "gender": "Female", + "favorite_genres": "R&B", + "created_at": "2023-12-18T04:33:12", + "updated_at": "2024-08-18T21:49:09" + }, + { + "id": 36489, + "first_name": "Kelly", + "last_name": "Cole", + "email": "gordoncole@example.net", + "gender": "Female", + "favorite_genres": "Electronic", + "created_at": "2023-09-01T03:50:02", + "updated_at": "2025-01-08T07:58:37" + }, + { + "id": 86478, + "first_name": "Virginia", + "last_name": "Sosa", + "email": "uvargas@example.net", + "gender": "Female", + "favorite_genres": "R&B", + "created_at": "2024-01-20T15:15:04", + "updated_at": "2025-07-07T19:57:20" + } +] \ No newline at end of file diff --git a/src/moovitamix_fastapi/api/__init__.py b/src/moovitamix_fastapi/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/moovitamix_fastapi/api/client.py b/src/moovitamix_fastapi/api/client.py new file mode 100644 index 0000000..9ab70a5 --- /dev/null +++ b/src/moovitamix_fastapi/api/client.py @@ -0,0 +1,10 @@ +# src/moovitamix_fastapi/api/client.py + +import requests +from moovitamix_fastapi.config import BASE_API_URL + +def fetch_data(endpoint: str) -> list: + url = f"{BASE_API_URL}{endpoint}" + response = requests.get(url, params={"page": 1, "size": 100}) + response.raise_for_status() + return response.json()["items"] \ No newline at end of file diff --git a/src/moovitamix_fastapi/api/routes.py b/src/moovitamix_fastapi/api/routes.py new file mode 100644 index 0000000..8828fe8 --- /dev/null +++ b/src/moovitamix_fastapi/api/routes.py @@ -0,0 +1,26 @@ +# src/moovitamix_fastapi/api/routes.py + +from fastapi import APIRouter +from fastapi_pagination import paginate +from moovitamix_fastapi.utils.pagination import CustomPage +from moovitamix_fastapi.classes_out import ListenHistoryOut, TracksOut, UsersOut +from moovitamix_fastapi.generate_fake_data import FakeDataGenerator +from moovitamix_fastapi.config import DATA_RANGE_OBSERVATIONS + +router = APIRouter(tags=["HTTP methods"]) + +# Génération des données simulées +generator = FakeDataGenerator(DATA_RANGE_OBSERVATIONS) +tracks, users, listen_history = generator.generate_fake_data() + +@router.get("/tracks", response_model=CustomPage[TracksOut]) +async def get_tracks(): + return paginate(tracks) + +@router.get("/users", response_model=CustomPage[UsersOut]) +async def get_users(): + return paginate(users) + +@router.get("/listen_history", response_model=CustomPage[ListenHistoryOut]) +async def get_listen_history(): + return paginate(listen_history) \ No newline at end of file diff --git a/src/moovitamix_fastapi/config.py b/src/moovitamix_fastapi/config.py new file mode 100644 index 0000000..be9f267 --- /dev/null +++ b/src/moovitamix_fastapi/config.py @@ -0,0 +1,10 @@ +# src/moovitamix_fastapi/config.py + +# Nombre d’éléments à générer pour les fausses données +DATA_RANGE_OBSERVATIONS = 1000 + +# URL de base de l’API locale (utilisée dans run_fetch.py par exemple) +BASE_API_URL = "http://localhost:8000" + +# Répertoire où les fichiers JSON seront enregistrés +DATA_DIR = "data" \ No newline at end of file diff --git a/src/moovitamix_fastapi/generate_fake_data.py b/src/moovitamix_fastapi/generate_fake_data.py index 8177694..ea7cc09 100644 --- a/src/moovitamix_fastapi/generate_fake_data.py +++ b/src/moovitamix_fastapi/generate_fake_data.py @@ -5,7 +5,7 @@ import random -from classes_out import ListenHistoryOut, TracksOut, UsersOut +from moovitamix_fastapi.classes_out import ListenHistoryOut, TracksOut, UsersOut class FakeDataGenerator: diff --git a/src/moovitamix_fastapi/main.py b/src/moovitamix_fastapi/main.py index bd1d01f..fb09031 100644 --- a/src/moovitamix_fastapi/main.py +++ b/src/moovitamix_fastapi/main.py @@ -1,9 +1,9 @@ -from classes_out import ListenHistoryOut, TracksOut, UsersOut +from moovitamix_fastapi.classes_out import ListenHistoryOut, TracksOut, UsersOut from fastapi import FastAPI, Query from fastapi.openapi.docs import get_swagger_ui_html from fastapi.responses import RedirectResponse from fastapi_pagination import Page, add_pagination, paginate -from generate_fake_data import FakeDataGenerator +from moovitamix_fastapi.generate_fake_data import FakeDataGenerator Page = Page.with_custom_options( size=Query(100, ge=1, le=100), diff --git a/src/moovitamix_fastapi/run_fetch.py b/src/moovitamix_fastapi/run_fetch.py new file mode 100644 index 0000000..aa532f0 --- /dev/null +++ b/src/moovitamix_fastapi/run_fetch.py @@ -0,0 +1,20 @@ +# src/moovitamix_fastapi/run_fetch.py + +from moovitamix_fastapi.api.client import fetch_data +from moovitamix_fastapi.utils.file_writer import save_data_to_json +from moovitamix_fastapi.config import DATA_DIR + +def fetch_and_save_all(output_dir=None): + output_dir = output_dir or DATA_DIR + for endpoint, filename in { + "/tracks": "tracks.json", + "/users": "users.json", + "/listen_history": "listen_history.json" + }.items(): + print(f"Récupération des données depuis {endpoint}...") + data = fetch_data(endpoint) + save_data_to_json(filename, data, output_dir=output_dir) + print(f"Données enregistrées dans {output_dir}/{filename}") + +if __name__ == "__main__": + fetch_and_save_all() diff --git a/src/moovitamix_fastapi/utils/__init__.py b/src/moovitamix_fastapi/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/moovitamix_fastapi/utils/file_writer.py b/src/moovitamix_fastapi/utils/file_writer.py new file mode 100644 index 0000000..d83bfb4 --- /dev/null +++ b/src/moovitamix_fastapi/utils/file_writer.py @@ -0,0 +1,13 @@ +# src/moovitamix_fastapi/utils/file_writer.py + +import os +import json +from moovitamix_fastapi.config import DATA_DIR + +def save_data_to_json(filename: str, data: list, output_dir: str = DATA_DIR): + os.makedirs(output_dir, exist_ok=True) + file_path = os.path.join(output_dir, filename) + with open(file_path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + diff --git a/src/moovitamix_fastapi/utils/pagination.py b/src/moovitamix_fastapi/utils/pagination.py new file mode 100644 index 0000000..071c500 --- /dev/null +++ b/src/moovitamix_fastapi/utils/pagination.py @@ -0,0 +1,8 @@ +# src/moovitamix_fastapi/utils/pagination.py + +from fastapi import Query +from fastapi_pagination import Page + +CustomPage = Page.with_custom_options( + size=Query(100, ge=1, le=100), +) \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..c61a73c --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,11 @@ +import pytest +import tempfile +import os +import shutil + +@pytest.fixture +def temp_data_dir(): + """Fixture qui crée un dossier temporaire pour les fichiers de test.""" + temp_dir = tempfile.mkdtemp() + yield temp_dir + shutil.rmtree(temp_dir) \ No newline at end of file diff --git a/test/test_api_client.py b/test/test_api_client.py new file mode 100644 index 0000000..1ba6099 --- /dev/null +++ b/test/test_api_client.py @@ -0,0 +1,38 @@ +# test/test_api_client.py + +import pytest +from moovitamix_fastapi.api.client import fetch_data + +def test_fetch_data_success(monkeypatch): + """Teste la récupération des données avec un endpoint valide""" + + class MockResponse: + def raise_for_status(self): + pass + + def json(self): + return {"items": [{"id": 1, "name": "test track"}]} + + def mock_get(url, params): + return MockResponse() + + monkeypatch.setattr("moovitamix_fastapi.api.client.requests.get", mock_get) + + result = fetch_data("/tracks") + assert isinstance(result, list) + assert result[0]["name"] == "test track" + +def test_fetch_data_invalid_endpoint(monkeypatch): + """Teste la gestion d'une erreur HTTP """ + + class MockResponse: + def raise_for_status(self): + raise Exception("404 Not Found") + + def mock_get(url, params): + return MockResponse() + + monkeypatch.setattr("moovitamix_fastapi.api.client.requests.get", mock_get) + + with pytest.raises(Exception): + fetch_data("/invalid_endpoint") diff --git a/test/test_file_writer.py b/test/test_file_writer.py new file mode 100644 index 0000000..7f49fe9 --- /dev/null +++ b/test/test_file_writer.py @@ -0,0 +1,18 @@ +# test/test_file_writer.py +import os +import json +from moovitamix_fastapi.utils.file_writer import save_data_to_json + +def test_save_data_to_json_creates_file(temp_data_dir): + data = [{"id": 1, "name": "test"}] + filename = "test.json" + + save_data_to_json(filename, data, output_dir=temp_data_dir) + + file_path = os.path.join(temp_data_dir, filename) + assert os.path.exists(file_path) + + with open(file_path, "r", encoding="utf-8") as f: + saved_data = json.load(f) + + assert saved_data == data \ No newline at end of file diff --git a/test/test_routes.py b/test/test_routes.py new file mode 100644 index 0000000..133c545 --- /dev/null +++ b/test/test_routes.py @@ -0,0 +1,27 @@ +# test/test_routes.py + +from fastapi.testclient import TestClient +from moovitamix_fastapi.main import app + +client = TestClient(app) + +def test_get_tracks(): + response = client.get("/tracks") + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + +def test_get_users(): + response = client.get("/users") + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + +def test_get_listen_history(): + response = client.get("/listen_history") + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) diff --git a/test/test_run_fetch.py b/test/test_run_fetch.py new file mode 100644 index 0000000..d5c1f53 --- /dev/null +++ b/test/test_run_fetch.py @@ -0,0 +1,22 @@ +# test/test_run_fetch.py + +import os +import json +from moovitamix_fastapi.run_fetch import fetch_and_save_all + +def test_fetch_and_save_all(temp_data_dir, monkeypatch): + + def fake_fetch_data(endpoint): + return [{"endpoint": endpoint}] + + monkeypatch.setattr("moovitamix_fastapi.api.client.fetch_data", fake_fetch_data) + + fetch_and_save_all(output_dir=temp_data_dir) + + for name in ["tracks.json", "users.json", "listen_history.json"]: + path = os.path.join(temp_data_dir, name) + assert os.path.exists(path) + + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + assert isinstance(data, list)