start integrate suno and generate lyrics with gemini

This commit is contained in:
Thomas Demirdjian
2025-08-08 17:30:03 +02:00
parent 7bcc2e9779
commit 2a5a0149e8
81 changed files with 8321 additions and 26379 deletions
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ["expo", "prettier"],
plugins: ["prettier"],
rules: {
"prettier/prettier": "error",
},
};
+5
View File
@@ -0,0 +1,5 @@
{
"projects": {
"default": "musicland-d33f9"
}
}
+11
View File
@@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Android" type="ReactNative" factoryName="React Native" activateToolWindowBeforeRun="false">
<node-interpreter value="project" />
<react-native value="$PROJECT_DIR$/node_modules/react-native" />
<platform value="ANDROID" />
<envs />
<only-packager />
<browser value="98ca6316-2f89-46d9-a9e5-fa9e2b0625b3" />
<method v="2" />
</configuration>
</component>
+17
View File
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build ios local" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="eas build --local -p ios --non-interactive &amp;&amp; open . &amp;&amp; open /Applications/Transporter.app" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
+17
View File
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clean and Xcode" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="export EXPO_NO_GIT_STATUS=1 &amp;&amp; yarn clean &amp;&amp; yarn xcode" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
+17
View File
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy cloud functions" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="firebase deploy --only functions" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$/functions" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
+17
View File
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Functions emulator" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="firebase emulators:start --only functions " />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
+2
View File
@@ -32,6 +32,7 @@ import {
Inter_600SemiBold, Inter_600SemiBold,
Inter_700Bold, Inter_700Bold,
} from "@expo-google-fonts/inter"; } from "@expo-google-fonts/inter";
import { LogBox } from "react-native";
console.disableYellowBox = true; console.disableYellowBox = true;
console.reportErrorsAsExceptions = false; console.reportErrorsAsExceptions = false;
@@ -62,6 +63,7 @@ const App = () => {
useEffect(() => { useEffect(() => {
if (loaded) { if (loaded) {
LogBox.ignoreAllLogs();
setAppIsReady(true); setAppIsReady(true);
} }
}, [loaded]); }, [loaded]);
+1 -1
View File
@@ -28,7 +28,7 @@
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="minuit"/> <data android:scheme="musicland"/>
<data android:scheme="com.minuit.starter"/> <data android:scheme="com.minuit.starter"/>
<data android:scheme="exp+minuitstarter"/> <data android:scheme="exp+minuitstarter"/>
</intent-filter> </intent-filter>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 20 KiB

+1 -1
View File
@@ -1,5 +1,5 @@
<resources> <resources>
<string name="app_name">minuit.starter</string> <string name="app_name">musicland</string>
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string> <string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string> <string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
</resources> </resources>
+1 -1
View File
@@ -14,7 +14,7 @@
</style> </style>
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen"> <style name="Theme.App.SplashScreen" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/splashscreen_background</item> <item name="windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="android:windowBackground">@drawable/splashscreen_logo</item> <item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item>
<item name="postSplashScreenTheme">@style/AppTheme</item> <item name="postSplashScreenTheme">@style/AppTheme</item>
</style> </style>
</resources> </resources>
+1 -1
View File
@@ -21,7 +21,7 @@ extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
} }
} }
rootProject.name = 'minuit.starter' rootProject.name = 'musicland'
dependencyResolutionManagement { dependencyResolutionManagement {
versionCatalogs { versionCatalogs {
+2 -2
View File
@@ -2,9 +2,9 @@
"expo": { "expo": {
"owner": "minuitagency", "owner": "minuitagency",
"slug": "minuitstarter", "slug": "minuitstarter",
"name": "minuit.starter", "name": "musicland",
"version": "2024.04.1", "version": "2024.04.1",
"scheme": "minuit", "scheme": "musicland",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/alternateIcons/4.png", "icon": "./assets/alternateIcons/4.png",
"backgroundColor": "#0F0C14", "backgroundColor": "#0F0C14",
-251
View File
@@ -1,251 +0,0 @@
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const entryFile = path.resolve(__dirname, "index.js");
const srcDir = path.resolve(__dirname, "src");
// Extensions de fichiers à analyser
const scriptExtensions = [".js", ".jsx", ".ts", ".tsx", ".web.js"];
const imageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".svg"];
// Fonction pour obtenir tous les fichiers dans un dossier avec des extensions spécifiques
function getAllFiles(dir, extensions, fileList = []) {
const files = fs.readdirSync(dir);
files.forEach(function (file) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
getAllFiles(filePath, extensions, fileList);
} else if (extensions.includes(path.extname(file))) {
fileList.push(filePath);
}
});
return fileList;
}
// Vérifie si un chemin est un fichier valide
function isValidFile(filePath) {
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
}
// Résout le chemin de l'import en un fichier valide
function resolveImport(filePath, importPath) {
let importedFile = path.resolve(path.dirname(filePath), importPath);
// Liste des extensions à tester, y compris .web.js
const allExtensions = scriptExtensions;
// Si le chemin n'a pas d'extension, essayer avec différentes extensions
if (!path.extname(importedFile)) {
for (const ext of allExtensions) {
if (isValidFile(importedFile + ext)) {
return importedFile + ext;
}
}
}
// Si un fichier avec l'extension actuelle existe, le retourner
if (isValidFile(importedFile)) {
return importedFile;
}
// Si le chemin est un répertoire, chercher un index avec les extensions
if (fs.existsSync(importedFile) && fs.statSync(importedFile).isDirectory()) {
const indexFiles = allExtensions.map((ext) => "index" + ext);
for (const indexFile of indexFiles) {
const indexFilePath = path.join(importedFile, indexFile);
if (isValidFile(indexFilePath)) {
return indexFilePath;
}
}
}
// Aucun fichier valide trouvé
return null;
}
// Analyse des fichiers pour trouver les imports
function getDependencies(
filePath,
visitedFiles = new Set(),
usedDependencies = new Set(),
usedFiles = new Set(),
usedAssets = new Set()
) {
if (visitedFiles.has(filePath))
return { usedDependencies, usedFiles, usedAssets };
visitedFiles.add(filePath);
usedFiles.add(filePath);
const content = fs.readFileSync(filePath, "utf-8");
let ast;
try {
ast = parser.parse(content, {
sourceType: "module",
plugins: ["jsx", "typescript", "classProperties", "dynamicImport"],
});
} catch (error) {
console.error(`Erreur lors de l'analyse du fichier ${filePath}:`, error);
return { usedDependencies, usedFiles, usedAssets };
}
traverse(ast, {
ImportDeclaration({ node }) {
const importPath = node.source.value;
handleImport(
filePath,
importPath,
visitedFiles,
usedDependencies,
usedFiles,
usedAssets
);
},
CallExpression({ node }) {
if (
node.callee.type === "Import" ||
(node.callee.name === "require" && node.arguments.length)
) {
const importArg = node.arguments[0];
if (importArg && importArg.type === "StringLiteral") {
const importPath = importArg.value;
handleImport(
filePath,
importPath,
visitedFiles,
usedDependencies,
usedFiles,
usedAssets
);
}
}
},
});
// Vérifier si un fichier .web.js correspondant existe
if (filePath.endsWith(".js")) {
const webFilePath = filePath.replace(/\.js$/, ".web.js");
if (isValidFile(webFilePath)) {
getDependencies(
webFilePath,
visitedFiles,
usedDependencies,
usedFiles,
usedAssets
);
}
}
return { usedDependencies, usedFiles, usedAssets };
}
// Fonction pour gérer les imports
function handleImport(
filePath,
importPath,
visitedFiles,
usedDependencies,
usedFiles,
usedAssets
) {
if (importPath.startsWith(".")) {
// Chemin relatif
const resolvedPath = resolveImport(filePath, importPath);
if (resolvedPath) {
const ext = path.extname(resolvedPath);
if (scriptExtensions.includes(ext)) {
getDependencies(
resolvedPath,
visitedFiles,
usedDependencies,
usedFiles,
usedAssets
);
} else if (imageExtensions.includes(ext)) {
usedAssets.add(resolvedPath);
}
}
} else {
// Dépendance de node_modules
const dep = importPath.split("/")[0];
usedDependencies.add(dep);
}
}
(async () => {
// Étape 1: Obtenir tous les fichiers utilisés
const allScriptFiles = getAllFiles(srcDir, scriptExtensions);
const allAssetFiles = getAllFiles(srcDir, imageExtensions);
const { usedDependencies, usedFiles, usedAssets } =
getDependencies(entryFile);
// Étape 2: Supprimer les fichiers scripts inutilisés
const unusedScriptFiles = allScriptFiles.filter(
(file) => !usedFiles.has(file)
);
unusedScriptFiles.forEach((file) => {
fs.unlinkSync(file);
console.log(`Fichier script supprimé: ${file}`);
});
// Étape 3: Supprimer les images non utilisées
const unusedAssetFiles = allAssetFiles.filter(
(file) => !usedAssets.has(file)
);
unusedAssetFiles.forEach((file) => {
fs.unlinkSync(file);
console.log(`Fichier image supprimé: ${file}`);
});
// Étape 4: Optimiser les images utilisées sans perte de qualité
async function optimizeImages(usedAssets) {
// Import dynamique des modules ES
const imagemin = (await import("imagemin")).default;
const imageminOptipng = (await import("imagemin-optipng")).default;
const imageminJpegtran = (await import("imagemin-jpegtran")).default;
const imageminGifsicle = (await import("imagemin-gifsicle")).default;
const imageminSvgo = (await import("imagemin-svgo")).default;
for (const file of usedAssets) {
const ext = path.extname(file).toLowerCase();
const plugins = [];
if (ext === ".png") {
plugins.push(imageminOptipng({ optimizationLevel: 3 }));
} else if (ext === ".jpg" || ext === ".jpeg") {
plugins.push(imageminJpegtran({ progressive: true }));
} else if (ext === ".gif") {
plugins.push(imageminGifsicle({ optimizationLevel: 3 }));
} else if (ext === ".svg") {
plugins.push(imageminSvgo());
} else {
continue;
}
try {
const optimized = await imagemin([file], {
destination: path.dirname(file),
plugins: plugins,
});
if (optimized && optimized.length > 0) {
console.log(`Image optimisée: ${file}`);
} else {
console.log(`Image déjà optimisée ou non optimisable: ${file}`);
}
} catch (error) {
console.error(
`Erreur lors de l'optimisation de l'image ${file}:`,
error
);
}
}
}
await optimizeImages(usedAssets);
})();
+20 -2
View File
@@ -1,12 +1,30 @@
{ {
"functions": { "functions": {
"source": "functions",
"codebase": "default",
"ignore": [ "ignore": [
"node_modules", "node_modules",
".git", ".git",
"firebase-debug.log", "firebase-debug.log",
"firebase-debug.*.log" "firebase-debug.*.log"
], ]
"predeploy": [] },
"emulators": {
"auth": {
"port": 9099
},
"functions": {
"host": "0.0.0.0",
"port": 5001
},
"firestore": {
"port": 8080
},
"ui": {
"enabled": true,
"port": 4000
},
"singleProjectMode": true
}, },
"react-native": { "react-native": {
"crashlytics_debug_enabled": true "crashlytics_debug_enabled": true
-3
View File
@@ -1,3 +0,0 @@
# GOOGLE GENAI
GOOGLE_GENAI_API_KEY=...
+25
View File
@@ -0,0 +1,25 @@
module.exports = {
env: {
es6: true,
node: true,
},
parserOptions: {
ecmaVersion: 2018,
},
extends: ["eslint:recommended", "google"],
rules: {
"no-restricted-globals": ["error", "name", "length"],
"prefer-arrow-callback": "error",
quotes: ["error", "double", { allowTemplateLiterals: true }],
},
overrides: [
{
files: ["**/*.spec.*"],
env: {
mocha: true,
},
rules: {},
},
],
globals: {},
};
-1
View File
@@ -1 +0,0 @@
node_modules/
+3
View File
@@ -0,0 +1,3 @@
exports.GEMINI_API_KEY = "AIzaSyBoPQC5ZaMKP73TlKGpZQp1mAw8ArHiH9Y";
// exports.SUNO_API_KEY = "543b5650b30d93e98d8046a830244c80"; client api key
exports.SUNO_API_KEY = "138bace8405b869f917156440acb6fed"; // perso api key
-113
View File
@@ -1,113 +0,0 @@
[debug] [2024-11-29T15:16:33.623Z] ----------------------------------------------------------------------
[debug] [2024-11-29T15:16:33.625Z] Command: /Users/theo/.nvm/versions/node/v18.17.0/bin/node /Users/theo/.nvm/versions/node/v18.17.0/bin/firebase emulators:start
[debug] [2024-11-29T15:16:33.625Z] CLI Version: 13.18.0
[debug] [2024-11-29T15:16:33.626Z] Platform: darwin
[debug] [2024-11-29T15:16:33.626Z] Node Version: v18.17.0
[debug] [2024-11-29T15:16:33.628Z] Time: Fri Nov 29 2024 16:16:33 GMT+0100 (Central European Standard Time)
[debug] [2024-11-29T15:16:33.629Z] ----------------------------------------------------------------------
[debug]
[debug] [2024-11-29T15:16:33.795Z] Object "" in "firebase.json" has unknown property: {"additionalProperty":"react-native"}
[debug] [2024-11-29T15:16:33.804Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
[debug] [2024-11-29T15:16:33.805Z] > authorizing via signed-in user (letawny@gmail.com)
[info] i emulators: Starting emulators: functions {"metadata":{"emulator":{"name":"hub"},"message":"Starting emulators: functions"}}
[debug] [2024-11-29T15:16:35.338Z] [logging] Logging Emulator only supports listening on one address (127.0.0.1). Not listening on ::1
[debug] [2024-11-29T15:16:35.339Z] assigned listening specs for emulators {"user":{"hub":[{"address":"127.0.0.1","family":"IPv4","port":4400},{"address":"::1","family":"IPv6","port":4400}],"ui":[{"address":"127.0.0.1","family":"IPv4","port":4000},{"address":"::1","family":"IPv6","port":4000}],"logging":[{"address":"127.0.0.1","family":"IPv4","port":4500}]},"metadata":{"message":"assigned listening specs for emulators"}}
[debug] [2024-11-29T15:16:35.347Z] [hub] writing locator at /var/folders/xw/v22l0r3n74lfydgqkyv8br5c0000gp/T/hub-starsclick.json
[debug] [2024-11-29T15:16:36.711Z] [functions] Functions Emulator only supports listening on one address (127.0.0.1). Not listening on ::1
[debug] [2024-11-29T15:16:36.712Z] [eventarc] Eventarc Emulator only supports listening on one address (127.0.0.1). Not listening on ::1
[debug] [2024-11-29T15:16:36.712Z] [tasks] Cloud Tasks Emulator only supports listening on one address (127.0.0.1). Not listening on ::1
[debug] [2024-11-29T15:16:36.713Z] late-assigned ports for functions and eventarc emulators {"user":{"hub":[{"address":"127.0.0.1","family":"IPv4","port":4400},{"address":"::1","family":"IPv6","port":4400}],"ui":[{"address":"127.0.0.1","family":"IPv4","port":4000},{"address":"::1","family":"IPv6","port":4000}],"logging":[{"address":"127.0.0.1","family":"IPv4","port":4500}],"functions":[{"address":"127.0.0.1","family":"IPv4","port":5001}],"eventarc":[{"address":"127.0.0.1","family":"IPv4","port":9299}],"tasks":[{"address":"127.0.0.1","family":"IPv4","port":9499}]},"metadata":{"message":"late-assigned ports for functions and eventarc emulators"}}
[warn] ⚠ functions: The following emulators are not running, calls to these services from the Functions emulator will affect production: auth, firestore, database, hosting, pubsub, storage, dataconnect {"metadata":{"emulator":{"name":"functions"},"message":"The following emulators are not running, calls to these services from the Functions emulator will affect production: \u001b[1mauth, firestore, database, hosting, pubsub, storage, dataconnect\u001b[22m"}}
[debug] [2024-11-29T15:16:36.719Z] defaultcredentials: writing to file /Users/theo/.config/firebase/letawny_gmail_com_application_default_credentials.json
[debug] [2024-11-29T15:16:36.725Z] Setting GAC to /Users/theo/.config/firebase/letawny_gmail_com_application_default_credentials.json {"metadata":{"emulator":{"name":"functions"},"message":"Setting GAC to /Users/theo/.config/firebase/letawny_gmail_com_application_default_credentials.json"}}
[debug] [2024-11-29T15:16:36.728Z] > refreshing access token with scopes: []
[debug] [2024-11-29T15:16:36.730Z] >>> [apiv2][query] POST https://www.googleapis.com/oauth2/v3/token [none]
[debug] [2024-11-29T15:16:36.730Z] >>> [apiv2][body] POST https://www.googleapis.com/oauth2/v3/token [omitted]
[debug] [2024-11-29T15:16:36.876Z] <<< [apiv2][status] POST https://www.googleapis.com/oauth2/v3/token 200
[debug] [2024-11-29T15:16:36.878Z] <<< [apiv2][body] POST https://www.googleapis.com/oauth2/v3/token [omitted]
[debug] [2024-11-29T15:16:36.900Z] >>> [apiv2][query] GET https://firebase.googleapis.com/v1beta1/projects/starsclick/adminSdkConfig [none]
[debug] [2024-11-29T15:16:37.423Z] <<< [apiv2][status] GET https://firebase.googleapis.com/v1beta1/projects/starsclick/adminSdkConfig 200
[debug] [2024-11-29T15:16:37.423Z] <<< [apiv2][body] GET https://firebase.googleapis.com/v1beta1/projects/starsclick/adminSdkConfig {"projectId":"starsclick","storageBucket":"starsclick.appspot.com","locationId":"europe-west"}
[debug] [2024-11-29T15:16:37.462Z] Ignoring unsupported arg: auto_download {"metadata":{"emulator":{"name":"ui"},"message":"Ignoring unsupported arg: auto_download"}}
[debug] [2024-11-29T15:16:37.463Z] Ignoring unsupported arg: port {"metadata":{"emulator":{"name":"ui"},"message":"Ignoring unsupported arg: port"}}
[debug] [2024-11-29T15:16:37.463Z] Starting Emulator UI with command {"binary":"node","args":["/Users/theo/.cache/firebase/emulators/ui-v1.13.0/server/server.mjs"],"optionalArgs":[],"joinArgs":false,"shell":false} {"metadata":{"emulator":{"name":"ui"},"message":"Starting Emulator UI with command {\"binary\":\"node\",\"args\":[\"/Users/theo/.cache/firebase/emulators/ui-v1.13.0/server/server.mjs\"],\"optionalArgs\":[],\"joinArgs\":false,\"shell\":false}"}}
[info] i ui: Emulator UI logging to ui-debug.log {"metadata":{"emulator":{"name":"ui"},"message":"Emulator UI logging to \u001b[1mui-debug.log\u001b[22m"}}
[debug] [2024-11-29T15:16:37.616Z] Web / API server started at 127.0.0.1:4000
{"metadata":{"emulator":{"name":"ui"},"message":"Web / API server started at 127.0.0.1:4000\n"}}
[debug] [2024-11-29T15:16:37.616Z] Web / API server started at ::1:4000
{"metadata":{"emulator":{"name":"ui"},"message":"Web / API server started at ::1:4000\n"}}
[info] i functions: Watching "/Users/theo/starsclick/functions" for Cloud Functions... {"metadata":{"emulator":{"name":"functions"},"message":"Watching \"/Users/theo/starsclick/functions\" for Cloud Functions..."}}
[debug] [2024-11-29T15:16:37.683Z] Validating nodejs source
[warn] ⚠ functions: package.json indicates an outdated version of firebase-functions. Please upgrade using npm install --save firebase-functions@latest in your functions directory.
[warn] ⚠ functions: Please note that there will be breaking changes when you upgrade.
[debug] [2024-11-29T15:16:38.518Z] > [functions] package.json contents: {
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase emulators:start --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "18"
},
"main": "index.js",
"dependencies": {
"@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.25.7",
"@babel/register": "^7.25.7",
"@genkit-ai/google-cloud": "^0.9.4",
"@genkit-ai/googleai": "^0.9.4",
"@genkit-ai/vertexai": "^0.9.4",
"@react-pdf/renderer": "^2.3.0",
"axios": "^1.6.2",
"crypto-js": "^4.2.0",
"dotenv": "^16.0.3",
"firebase-admin": "^11.8.0",
"firebase-functions": "^4.3.1",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"stripe": "^16.1.0",
"react": "^17.0.0"
},
"devDependencies": {
"firebase-functions-test": "^3.1.0"
},
"private": true
}
[debug] [2024-11-29T15:16:38.518Z] Building nodejs source
[debug] [2024-11-29T15:16:38.518Z] Failed to find version of module node: reached end of search path /Users/theo/starsclick/functions/node_modules
[info] ✔ functions: Using node@18 from host.
[info] i functions: Loaded environment variables from .env.
[debug] [2024-11-29T15:16:38.522Z] Could not find functions.yaml. Must use http discovery
[debug] [2024-11-29T15:16:38.537Z] Found firebase-functions binary at '/Users/theo/starsclick/functions/node_modules/.bin/firebase-functions'
[info] Serving at port 8113
[debug] [2024-11-29T15:16:41.014Z] Got response from /__/functions.yaml {"endpoints":{"users-userUpdateListener":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":null,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"eventTrigger":{"eventType":"google.cloud.firestore.document.v1.written","eventFilters":{"database":"(default)","namespace":"(default)"},"eventFilterPathPatterns":{"document":"users/{userID}"},"retry":false},"entryPoint":"users.userUpdateListener"},"users-deleteAccount":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":null,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"callableTrigger":{},"entryPoint":"users.deleteAccount"},"payments-createCheckoutSession":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":null,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"callableTrigger":{},"entryPoint":"payments.createCheckoutSession"},"payments-getPremiumStatus":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":null,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"callableTrigger":{},"entryPoint":"payments.getPremiumStatus"},"payments-createStripeCustomerPortalSession":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":null,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"callableTrigger":{},"entryPoint":"payments.createStripeCustomerPortalSession"},"documents-generateDocumentFromTemplate":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":null,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"callableTrigger":{},"entryPoint":"documents.generateDocumentFromTemplate"},"documents-testDocumentGeneration":{"platform":"gcfv1","availableMemoryMb":512,"timeoutSeconds":null,"minInstances":null,"maxInstances":null,"ingressSettings":null,"serviceAccountEmail":null,"vpc":null,"httpsTrigger":{},"entryPoint":"documents.testDocumentGeneration"}},"specVersion":"v1alpha1","requiredAPIs":[]}
[info] ✔ functions: Loaded functions definitions from source: users.userUpdateListener, users.deleteAccount, payments.createCheckoutSession, payments.getPremiumStatus, payments.createStripeCustomerPortalSession, documents.generateDocumentFromTemplate, documents.testDocumentGeneration. {"metadata":{"emulator":{"name":"functions"},"message":"Loaded functions definitions from source: users.userUpdateListener, users.deleteAccount, payments.createCheckoutSession, payments.getPremiumStatus, payments.createStripeCustomerPortalSession, documents.generateDocumentFromTemplate, documents.testDocumentGeneration."}}
[info] i functions[us-central1-users-userUpdateListener]: function ignored because the firestore emulator does not exist or is not running. {"metadata":{"emulator":{"name":"functions"},"message":"function ignored because the firestore emulator does not exist or is not running."}}
[info] ✔ functions[us-central1-users-deleteAccount]: http function initialized (http://127.0.0.1:5001/starsclick/us-central1/users-deleteAccount). {"metadata":{"emulator":{"name":"functions"},"message":"\u001b[1mhttp\u001b[22m function initialized (http://127.0.0.1:5001/starsclick/us-central1/users-deleteAccount)."}}
[info] ✔ functions[us-central1-payments-createCheckoutSession]: http function initialized (http://127.0.0.1:5001/starsclick/us-central1/payments-createCheckoutSession). {"metadata":{"emulator":{"name":"functions"},"message":"\u001b[1mhttp\u001b[22m function initialized (http://127.0.0.1:5001/starsclick/us-central1/payments-createCheckoutSession)."}}
[info] ✔ functions[us-central1-payments-getPremiumStatus]: http function initialized (http://127.0.0.1:5001/starsclick/us-central1/payments-getPremiumStatus). {"metadata":{"emulator":{"name":"functions"},"message":"\u001b[1mhttp\u001b[22m function initialized (http://127.0.0.1:5001/starsclick/us-central1/payments-getPremiumStatus)."}}
[info] ✔ functions[us-central1-payments-createStripeCustomerPortalSession]: http function initialized (http://127.0.0.1:5001/starsclick/us-central1/payments-createStripeCustomerPortalSession). {"metadata":{"emulator":{"name":"functions"},"message":"\u001b[1mhttp\u001b[22m function initialized (http://127.0.0.1:5001/starsclick/us-central1/payments-createStripeCustomerPortalSession)."}}
[info] ✔ functions[us-central1-documents-generateDocumentFromTemplate]: http function initialized (http://127.0.0.1:5001/starsclick/us-central1/documents-generateDocumentFromTemplate). {"metadata":{"emulator":{"name":"functions"},"message":"\u001b[1mhttp\u001b[22m function initialized (http://127.0.0.1:5001/starsclick/us-central1/documents-generateDocumentFromTemplate)."}}
[info] ✔ functions[us-central1-documents-testDocumentGeneration]: http function initialized (http://127.0.0.1:5001/starsclick/us-central1/documents-testDocumentGeneration). {"metadata":{"emulator":{"name":"functions"},"message":"\u001b[1mhttp\u001b[22m function initialized (http://127.0.0.1:5001/starsclick/us-central1/documents-testDocumentGeneration)."}}
[info]
┌─────────────────────────────────────────────────────────────┐
│ ✔ All emulators ready! It is now safe to connect your app. │
│ i View Emulator UI at http://127.0.0.1:4000/ │
└─────────────────────────────────────────────────────────────┘
┌───────────┬────────────────┬─────────────────────────────────┐
│ Emulator │ Host:Port │ View in Emulator UI │
├───────────┼────────────────┼─────────────────────────────────┤
│ Functions │ 127.0.0.1:5001 │ http://127.0.0.1:4000/functions │
└───────────┴────────────────┴─────────────────────────────────┘
Emulator Hub running at 127.0.0.1:4400
Other reserved ports: 4500
Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
-39
View File
@@ -1,39 +0,0 @@
const admin = require("firebase-admin");
const serviceAccount = require("./serviceAccountProd.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
let db = admin.firestore();
exports.db = db;
const { vertexAI, gemini15Flash } = require("@genkit-ai/vertexai");
const { genkit } = require("genkit");
require("dotenv").config();
require("@babel/register")({
presets: ["@babel/preset-env", "@babel/preset-react"],
});
// configure a Genkit instance
exports.genkitInstance = genkit({
plugins: [vertexAI({ projectId: "starsclick", location: "us-central1" })],
logLevel: "debug",
enableTracingAndMetrics: true,
model: gemini15Flash, // set default model
});
exports.refsList = {
users: db.collection("users"),
documents: db.collection("documents"),
logs: db.collection("logs"),
};
exports.users = require("./src/users");
exports.payments = require("./src/payments");
exports.documents = require("./src/documents");
-23534
View File
File diff suppressed because it is too large Load Diff
-38
View File
@@ -1,38 +0,0 @@
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase emulators:start --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "18"
},
"main": "index.js",
"dependencies": {
"@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.25.7",
"@babel/register": "^7.25.7",
"@genkit-ai/google-cloud": "^0.9.4",
"@genkit-ai/googleai": "^0.9.4",
"@genkit-ai/vertexai": "^0.9.4",
"@react-pdf/renderer": "^2.3.0",
"axios": "^1.6.2",
"crypto-js": "^4.2.0",
"dotenv": "^16.0.3",
"firebase-admin": "^11.8.0",
"firebase-functions": "^4.3.1",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"stripe": "^16.1.0",
"react": "^17.0.0"
},
"devDependencies": {
"firebase-functions-test": "^3.1.0"
},
"private": true
}
+13
View File
@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "musicland-d33f9",
"private_key_id": "c1110a2aa31eb3ac7cf42d931249a2572bf68e37",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZKlW0nTIg4Il2\nPtr/BYi247mWp8pxpCwS2ggwGBU7iO44jLQAgLWtZdHrQ7kpRfjVuceJDrkLrS82\ntkt8wQpytCFKfZjil9fdyS1hrVIQdfaPppovbRtzDpe0varA8XxgWXG8L+CQtCQd\nNhcixKONFhgOWTKB/tvEtGEExtpRRTb7y21o/EFbfpZqSbGEPcota2DPPvNh1r1y\nnhF7AcboGFTouDaTVL/oFIYYm3eWQP6nJRiM15xPqEhar+2z3lG9Y6HoRkkynFFj\nwvhQ6AUidA4A1ZW90HC33jsY5u7MAbzXY/QZ2MC82gaf26rda/7hThmk0RUSEaOv\n/G2f2lQBAgMBAAECggEAKnssdEDZQ3cjiTFbyjm9toQo2FlHdAeF1eC1PqmfUfjj\nobmcvzrzqNlBxM/uzjzDDHxPlHsJ5ewt2S3sd/iOrku5pBB70/UrgS8oMBxDU590\nOFmMEDVh1KKqfw3QC12vEoYcXoak8V2VWqjC9/i0KBCFsY0ZziZizAb/lbSVns52\nuJQhjvZURHQQOQmrZDm5mhklKCjE6qDxAMjL0yIhDQLAATzqeVPPeP8Rvq6u7yiB\noiB40OGiGHLdt28Q7IAXSQ+/Vqig7aw/qwrfZrFqgbgCYJKo1GiYFaDgRbH6GiaE\nhBJg7LtM0EaDR5G0raBWv5szqpfOb2DC6K802dcQRQKBgQDQm7HoJjmvEu9a6hxF\nVwS3rdJO67dTtxFMZM0HespQu659NT8f21QsRf/2JIcwktTnGzSeVTca0GZ4z8de\nkP9Wid0VPcA6Y3U7yplBf7Xh/5KW7/O6iSCEN03Y3/lH6vK6tBe5xCT3kfYKkrAd\n/xePLLBx++dAL5YFixMzBFfI5wKBgQC79i2S6Bcu6WQc0yykVHkAMStPz4gsAYA5\nqqCuWEwFcgEEY5AiP/oH056Hu2Z2HJHPp4OKYWSbqdxkNM214vysO0yhrF59BjQX\nyo+hwQfBqS0JfH7bJgUjNuDjP947qGtRXVo2i3FRJuZ4NmS2cGC9gT00p6rZRrbL\nJkaKSuVW1wKBgH3xsgxVn7fqd6Xs99qJOejnQbSyLUGWohP8O6jLYoAxf5xKXG0Q\nTxZXwLxLu28BBxbsXu1Ex3sh3OwNjfGzGuWVYGvllzvpNis+Gx7nStlCIq+L+Rti\nlMYeWZqZRng8CgXcmwJlwGfTsLcbbsb9bqzbLz/x9ODUmf+IwrOnaJR7AoGBALA7\nPjm6YFo8vAAGw5mEFjtm3owjMjLIaXgoEiXFZenJHUkHLNHgGEcUVFRBEe12FgST\ntFKfxFvrJFfT5ng+TdgSdWQ0oBcrJNaVtpvbx7D9NNvrHWnOOQzuXGbVsEFRROXx\n2C7CCUokZfyaS01/CkVEEqX2+spjceoBhiwN0v/bAoGBAK4sYw+3o3yjH26/M/Ni\nn5hwxofTktvAkYi7GlSFaRf2QwwxMBeA6qM/AJn7rer6pRzWwApbae/kOyfZfivq\nLnfpwloJMtds/Mm2egz0zoX7WIZ15uEx4vha2IUmi4Qz92kUzgaD1jTN8ZLUCypn\npR/zlJVkLv5BFqOtfX8shz1T\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@musicland-d33f9.iam.gserviceaccount.com",
"client_id": "114560176426659117925",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40musicland-d33f9.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
-185
View File
@@ -1,185 +0,0 @@
import React from "react";
import { Document, Page, Text, View, Font, Image } from "@react-pdf/renderer";
import { presidentSignatureBase64 } from "../data/presidentSignatureBase64";
import { RenderCompanyLogo, SignatureForm, Styles } from "./SharedElements";
Font.register({
family: "Helvetica",
fonts: [
{ src: "https://fonts.gstatic.com/s/helvetica/v15/Helvetica-Regular.ttf" },
{
src: "https://fonts.gstatic.com/s/helvetica/v15/Helvetica-Bold.ttf",
fontWeight: "bold",
},
],
});
const Contract = ({
domiciliationData = {},
appConfig = {},
contractFields = {},
} = {}) => {
const { company } = appConfig;
const {
intro_contrat,
raison_sociale,
forme_juridique,
qualite,
civilite,
prenoms,
nom_de_jeune_fille_si_madame,
nom,
adresse,
code_postal,
ville,
adresse_de_domiciliation,
date_souhaitee_du_debut_de_la_domiciliation,
} = contractFields;
const sections = [
{
title: "Contrat de domiciliation",
content: <RenderCompanyLogo logoBase64={company?.logoBase64} />,
},
{
title: null,
content: (
<Text style={Styles.textParagraph}>{`
Entre les soussignés :
${intro_contrat}
Ci-après désignée « le domiciliataire »
Dune part et,
La société ${raison_sociale} ${forme_juridique} représentée par son  ${qualite} , ${civilite} ${prenoms} ${nom_de_jeune_fille_si_madame} ${nom} demeurant ${adresse} ${code_postal} ${ville}.
Ci-après désignée « Le domicilié »
Il a été arrêté et convenu ce qui suit :
Article préliminaire : consentement des parties
Le Domicilié déclare avoir pris connaissance de tous les documents afférents à la présente (annexes, autorisation du pli du courrier et attestation comptable) et déclare donner acceptation expresse pour la totalité des clauses figurants dans les documents.
Article 1 : OBJET DU CONTRAT
Le présent contrat a pour objet la domiciliation du siège social du Domicilié conformément aux dispositions des articles R123-167 à R 123-171 du Code de Commerce.
Article 2 : OBLIGATION DES PARTIES
2.1 Obligations du domiciliataire
2.1.1 Obligations légales.
Conformément aux dispositions législatives des articles R 123-167 à R 123-171 du code de Commerce, le Domiciliataire sengage, pendant toute la durée du présent contrat, à :
• Etre immatriculé au Registre du Commerce et des Sociétés ou au Répertoire des Métiers, durant loccupation des locaux.
• Mettre à la disposition du Domicilié des locaux dotés dune pièce destinée à assurer la confidentialité nécessaire et à permettre une réunion régulière des organes de direction, dadministration ou de surveillance de lentreprise ainsi que la tenue, la conservation et consultation des livres, registres et documents prescrits par la loi (Décret N°85.1280 du 5 décembre 1985, Article 2.6.1 modifié par Décret 2007-750 du 9 mai 2007).
• Détenir, pour le Domicilié, un dossier contenant les pièces justificatives relatives au domicile de son représentant légal, ses coordonnées téléphoniques ainsi que les lieux dactivité et de détention des documents comptables lorsquils ne sont pas conservés chez elle.
• Informer le greffier du tribunal, à lexpiration du contrat ou en cas de résiliation anticipée de celui-ci, de la cessation de la domiciliation de lentreprise dans ses locaux.
• Communiquer aux huissiers de justice, munis dun titre exécutoire, les renseignements propres à joindre le domicilié.
• Fournir, chaque trimestre, au centre des impôts et aux organismes de recouvrement des cotisations et contributions de sécurité sociale compétents, une liste des personnes qui se sont domiciliées dans ses locaux au cours de cette période ou qui ont mis fin à leur domiciliation ainsi que chaque année, une liste des personnes domiciliées au 1er
2.1.2 Prestations
Le domiciliataire sengage à fournir au Domicilié les prestations de services suivantes :
Domiciliation commerciale.
Cette prestation permet au domicilié de fixer son siège social dans les locaux du Domiciliataire situés au ${adresse_de_domiciliation}.
Domiciliation postale.
La prestation sus évoquée permet uniquement au Domicilié de recevoir son courrier (recommandé et pli spécial avec autorisation) , sans pour autant en faire une utilisation dans sa communication commerciale qui serait susceptible de publicité mensongère ou trompeuse, le tout conformément aux dispositions législatives en vigueur et notamment larticle L-121-1 du Code de la consommation.
La mise à disposition des locaux.
Cette prestation permet au domicilié de bénéficier de locaux dotés dune pièce destinée à assurer la confidentialité nécessaire et y permettre une réunion régulière des organes chargés de la direction, de ladministration ou de la surveillance de lentreprise ainsi que la tenue, la conservation et la consultation des livres, registres et documents prescrits par les lois et règlements. Cette prestation est soumise à une rémunération supplémentaire proposée sur devis.
2.2 Obligations du Domicilié
2.2.1. Obligations générales
Dès la signature du présent contrat et durant toute la durée de celui-ci, le domicilié sengage à :
• Justifier de son inscription au Registre du Commerce ou des Métiers ou toutes autres administrations compétentes dans les trois mois qui suivent la date dengagement de la domiciliation, faute de quoi le Domiciliataire se réserve le droit de commander les documents justificatifs au frais du domicilié, au coût de 20 € HT (Vingt euros hors-taxes) pour le K-bis et de 40 € HT (quarante euros hors-taxes) pour les statuts.
• certifier lexactitude des renseignements fournis à lappui de la signature du contrat avec le Domiciliataire, certifier ne pas être en situation de liquidation de biens, redressement judiciaire en ce qui concerne lentreprise ou les entreprises quil dirige, que ces établissements soient lobjet ou non dudit contrat, certifier ne pas être à titre personnel frappé de faillite personnelle ou dinterdiction de gérer, attester lexactitude de tous les renseignements fournis au Domiciliataire tant en ce qui concerne son état civil que lentreprise représentée.
Dans tous les cas, le Domicilié sera responsable de ses dettes après son départ du ${adresse_de_domiciliation}.
Tout renseignement fourni par le Domicilié pourra être communiqué sur demande aux représentants des organismes officiels et le domicilié en donne dès à présent son accord.
• Utiliser effectivement et exclusivement les locaux, soit comme siège de lentreprise, soit si le siège est situé à l’étranger comme agence, succursale ou représentation ;
• Tenir informé le Domiciliataire de toute modification concernant son activité ;
En cas de changement, soit dadresse, soit d’état civil personnel, soit de dénomination sociale, soit de nom commercial, soit de sigle (afin d’éviter les homonymes), soit de forme juridique ou dobjet, soit de dirigeant, soit de lutilisateur des prestations fournies au titre du présent contrat, le Domicilié devra fournir tous les documents afférents à ces modifications au Domiciliataire et présenter son successeur ou le nouvel utilisateur, avant de déclarer tout changement auprès du Greffe du Tribunal de Commerce ou de la Chambre des Métiers ou de toute autre administration compétente.
• Déclarer tout changement statutaire et/ou relatif à sa forme juridique et son objet, ainsi quau nom et au domicile personnel des personnes ayant le pouvoir de lengager à titre habituel ; donner mandat au Domiciliataire, qui laccepte, de recevoir en son nom toute notification. Faute dexécution dune telle obligation, le domicilié sacquittera dun règlement de 30 € HT (trente euros hors-taxes) correspondant aux frais de recherches des informations à fournir.
Les colis et plis spéciaux (DHL, CHRONOPOST, UPS, ...) ne seront acceptés quaprès signature dun pouvoir de réception, aux conditions indiquées sur ledit document.
Le Domicilié sengage à signer la procuration postale fournie avec le contrat au bénéfice du Domiciliataire afin de pouvoir réceptionner en son nom ou au nom de son entreprise les envois recommandés.
2.2.2. Obligations particulières relatives à la formule choisie par le domicilié
- Le domicilié sengage à respecter les modalités dexécution et obligations contractuelles qui figurent en annexe du présent contrat.
- Lesdites annexes reflètent loffre à laquelle le domicilié a souscrit et doivent être considérées comme partie intégrante du présent contrat et doivent être à cet égard signées par les parties.
2.2.3. Redevance
En contrepartie des obligations auxquelles le Domiciliataire sest engagé à larticle 2.1, le domicilié sengage à régler une redevance mensuelle dont le montant figure en annexe conformément à loffre choisie. Ladite redevance est payable par prélèvement bancaire automatique, dit “ mandat SEPA “ le dix du mois en cours. Elle couvre les différentes prestations décrites dans chacune des offres à lexception de la mise à disposition des locaux qui fait lobjet dune tarification supplémentaire et des éventuels frais de réexpédition.
Lesdits frais de réexpédition du courrier prévus dans certaines offres seront facturés en sus au Domicilié et payables par prélèvement bancaire automatique, dit “ Mandat SEPA “ le dix du mois en cours. Une facture dématérialisée sera adressée mensuellement au gérant du Domicilié.
En cas danomalie ou erreur de prélèvement, ou en cas de règlement autre que le prélèvement mensuel ou le règlement de loffre à lannée, la somme forfaitaire mensuelle sera majorée au tarif bancaire en vigueur par incident ou erreur commise, au titre des frais administratifs.
Pour les règlements par chèque, prélèvement ou carte bancaire remis à lencaissement et impayés, des frais au tarif bancaire en vigueur seront imputés automatiquement sur la prochaine facture de domiciliation.
Le Domicilié donne dès à présent son accord pour une révision chaque année du tarif mensuel des prestations de services définies à lArticle 2.1.2. en fonction du taux dinflation (indice IPC) ou de l’évolution des services auxquels il souscrit, ainsi que pour le mode de règlement proposé par le Domiciliataire.
Le domicilié sera tenu informé de la révision du tarif mensuel des prestations de services, un mois avant ladite révision.
Ce contrat est ferme et définitif à la signature et aucun remboursement même partiel ne saurait être revendiqué par le domicilié.
Article 3 : Accès à lespace et utilisation des locaux
Sauf autorisation exceptionnelle mentionnée dans les conditions particulières ou par avenant à ce présent contrat, le Domicilié naura en aucun cas le droit de sous louer ou donner accès à lespace à un tiers, hors prestations permises par le présent contrat.
Article 4 : Durée du contrat
Le présent contratest consenti pour une durée minimale de 3 mois à compter du ${date_souhaitee_du_debut_de_la_domiciliation}.
4.1. Renouvellement
Le présent contrat peut être renouvelé, par tacite reconduction, sauf résiliation notifiée par lune ou lautre des parties par lettre recommandée avec accusé de réception et expédiée au moins 30 (trente) jours avant le terme annuel.
4.2. Résiliation
Avant le terme de ce préavis, le Domicilié devra impérativement, pour valider sa résiliation définitive de contrat, adresser par courrier, une photocopie de son nouveau Kbis ou tout document remis par ladministration compétente, justifiant le transfert de siège social ou de sa radiation.
En labsence de ce justificatif, le Domiciliataire se réserve le droit de poursuivre le contrat de domiciliation dans les termes initiaux.
Ladresse de correspondance est :
I DOM YOU ${adresse_de_domiciliation}
À lexpiration du présent contrat ou en cas de résiliation de celui-ci, le Domiciliataire sengage à informer le greffe du Tribunal de Commerce de Paris de la cessation de la domiciliation.
Au terme de ce contrat, le courrier sera refusé par le Domiciliataire avec lannotation « Nhabite Pas à lAdresse Indiquée ».
Article 5 : Dépôt de garantie
Le Domicilié verse, à la date de signature du présent contrat, à titre de dépôt de garantie, un montant correspondant à 3mois de redevance, en sus du mois correspondant à loffre souscrite.
Le dépôt de garantie sera encaissé par le Domiciliataire dès sa remise par le Domicilié.
À chaque réajustement de la redevance, le dépôt de garantie sera diminué ou majoré de manière à toujours correspondre à 3 mois de redevance, hors taxes.
Ce dépôt de garantie est destiné, en cas de résiliation du contrat, au paiement des sommes dues pour la fourniture des services, lexécution parfaite des clauses du présent contrat et des sommes dues par le Domicilié dont le Domiciliataire pourrait être rendu responsable.
En effet, ce dépôt de garantie pourra être utilisé soit pour défaut de règlement, soit pour la conséquence de dommages occasionnés par le Domicilié ou ses commettants.
Ledit dépôt de garantie ne saurait en aucun cas dispenser le Domicilié de payer toutes les redevances jusquau terme prévu.
Dans lhypothèse où le dépôt de garantie versé par le Domicilié est inférieur au montant des remises en état opérées par le Domiciliataire au terme du contrat, le Domicilié sengage à rembourser au Domiciliataire la différence entre la valeur des remises en état justifiée sur facture et le dépôt de garantie.
À la fin du contrat, ce dépôt de garantie sera remboursé déduction faite des sommes qui pourraient être dues au Domiciliataire.
Les sommes versées à titre de dépôt de garantie ne sauraient produire dintérêt au profit du Domicilié.
Article 6 : Responsabilité des Parties
Dans la limite maximale admise par le droit français, le Domiciliataire décline toute responsabilité à l’égard du Domicilié en raison de la perte ou dun dommage subi par le client en relation avec le présent contrat, avec les prestations, le ou les espaces, à moins que la perte ou le dommage ne résulte dun acte intentionnel ou dune négligence du Domiciliataire.
Le Domiciliataire décline toute responsabilité en raison de la perte résultant dun manquement relatif à la fourniture dune prestation par suite dune panne mécanique, dune grève, de la déchéance des droits du Domiciliataire sur les espaces ou pour toute autre raison à moins que le Domiciliataire nait agi intentionnellement ou par négligence.
En tout état de cause, le Domiciliataire ne sera responsable dune perte ou dun dommage que si le Domicilié len avise par écrit et lui octroie un délai qui ne peut être inférieur à 20 (vingt) jours pour y remédier.
Si le Domicilié considère que le Domiciliataire a failli dans la fourniture des prestations prévues à larticle 2.1.2 des conditions particulières du contrat, le Domicilié devra en aviser le Domiciliataire par écrit et lui octroyer un délai qui ne peut être inférieur à 20 (vingt) jours afin que le Domiciliataire puisse y remédier.
En tout état de cause, seuls les préjudices directs pourront permettre lengagement de la responsabilité des parties, étant précisé que pour ce qui est de la responsabilité du Domiciliataire, cette dernière ne pourra être supérieure au total du montant des sommes encaissées par ce dernier et payées par le Domicilié dans le cadre de lexécution du contrat.
Article 7 : Assurance
Le Domicilié est responsable du matériel quil entrepose ${adresse_de_domiciliation}
Le Domiciliataire ne pourra être tenu responsable dun vol dans les locaux.
Il est donc vivement conseillé au Domicilié de sassurer pour son activité professionnelle (assurance civile professionnelle) et pour les espaces quil occupe (assurance multirisques bureaux).
Article 8 : Élection de domicile
Les parties font élection de domicile au ${adresse_de_domiciliation}.
Article 9 : Différend - Attribution de juridiction
Tout litige pouvant survenir entre les parties à loccasion de lexécution du présent contrat devra être porté devant le Tribunal de Commerce de Paris.
Article 10 : Confidentialité
Les parties sengagent à traiter comme confidentielles toutes informations quelles seraient amenées à obtenir dans le cadre de ce contrat.`}</Text>
),
},
SignatureForm(),
{
title: "Pour le responsable",
content: (
<>
<Image
src={`data:image/png;base64,${presidentSignatureBase64}`}
style={{ width: 200 }}
/>
</>
),
},
].filter(({ condition = true }) => condition);
return (
<Document>
<Page style={Styles.body}>
{sections.map((section, index) => (
<View key={index} style={Styles.section}>
{section.title && (
<Text style={Styles.sectionTitle}>{section.title}</Text>
)}
{section.content}
</View>
))}
</Page>
</Document>
);
};
export default Contract;
-206
View File
@@ -1,206 +0,0 @@
import React from "react";
import { Document, Page, Text, View, Font, Image } from "@react-pdf/renderer";
import moment from "moment-timezone";
import {
DisplayCustomerInformation,
formatPrice,
RenderCompanyLogo,
RenderTermsOfSale,
Styles,
} from "./SharedElements";
Font.register({
family: "Helvetica",
fonts: [
{ src: "https://fonts.gstatic.com/s/helvetica/v15/Helvetica-Regular.ttf" },
{
src: "https://fonts.gstatic.com/s/helvetica/v15/Helvetica-Bold.ttf",
fontWeight: "bold",
},
],
});
const Invoice = ({
itemList = [],
billingDetails = {},
number = "-",
paymentTimestamp = null,
creationTimestamp = new Date().toLocaleDateString("fr-FR"),
appConfig = {},
}) => {
const { company } = appConfig;
const subTotalNoTax = itemList.reduce(
(sum, item) => sum + (item?.unitPrice || 0) * (item?.quantity || 0),
0
);
let taxList = {};
itemList.forEach((item) => {
const { taxBreakdown = [] } = item;
taxBreakdown.forEach((tax) => {
const { type, amount, displayName, percentageDecimal = null } = tax;
if (type) {
taxList[type] = {
displayName,
percentageDecimal,
amount: (taxList?.[type]?.amount || 0) + amount,
};
}
});
});
const montantTotalTTC =
subTotalNoTax +
Object.values(taxList).reduce((sum, tax) => sum + tax.amount, 0);
const taxExemptionLegalString = "";
const sections = [
{
title: `Facture n°${number}`,
content: <RenderCompanyLogo logoBase64={company?.logoBase64} />,
},
{
title: null,
content: (
<View style={Styles.headerRow}>
<View style={Styles.leftColumn}>
{[
`Date d'émission: ${moment(creationTimestamp)
.tz("Europe/Paris")
.format("DD/MM/YYYY à HH:mm")}`,
paymentTimestamp
? `Date de paiement: ${moment(paymentTimestamp)
.tz("Europe/Paris")
.format("DD/MM/YYYY à HH:mm")}`
: null,
"Statut: payée",
]
.filter(Boolean)
.map((line, index) => (
<Text key={index} style={Styles.textParagraph}>
{line}
</Text>
))}
</View>
<DisplayCustomerInformation billingDetails={billingDetails} />
</View>
),
},
{
title: "Détails des prestations",
content: (
<View style={Styles.table}>
<View style={Styles.tableRow}>
<Text style={Styles.tableColHeaderLeft}>Description</Text>
<Text style={Styles.tableColHeaderRight}>Prix HT</Text>
</View>
{itemList.map((item, index) => (
<View key={index} style={Styles.tableRow}>
<View>
<Text style={Styles.tableColLeft}>{item.description}</Text>
</View>
<Text style={Styles.tableColRight}>
{formatPrice({ amount: item.unitPrice * item.quantity })}
</Text>
</View>
))}
<View style={Styles.summaryRow}>
<Text style={Styles.summaryLabel}>Sous-total HT :</Text>
<Text style={Styles.summaryValue}>
{formatPrice({ amount: subTotalNoTax })}
</Text>
</View>
{Object.values(taxList)?.map((itemTax, index) => (
<View key={index} style={Styles.summaryRow}>
<Text style={Styles.summaryLabel}>
{itemTax?.displayName}{" "}
{itemTax?.percentageDecimal
? `(${itemTax?.percentageDecimal}%)`
: ""}{" "}
:
</Text>
<Text style={Styles.summaryValue}>
{formatPrice({ amount: itemTax.amount })}
</Text>
</View>
))}
<View style={Styles.summaryRow}>
<Text style={Styles.summaryLabel}>Total TTC :</Text>
<Text style={Styles.summaryValue}>
{formatPrice({ amount: montantTotalTTC })}
</Text>
</View>
</View>
),
},
{
title: "Mentions légales",
content: (
<Text style={Styles.textParagraph}>
{`En cas de retard de paiement, une pénalité de 3 fois le taux d'intérêt légal sera appliquée, à laquelle s'ajoutera une indemnité forfaitaire pour frais de recouvrement de 40€. ${taxExemptionLegalString}`}
</Text>
),
},
RenderTermsOfSale(),
].filter(({ content }) => content);
return (
<Document>
<Page style={Styles.body}>
{sections.map((section, index) => (
<View key={index} style={Styles.section}>
{section.title && (
<Text style={Styles.sectionTitle}>{section.title}</Text>
)}
{section.content}
</View>
))}
</Page>
</Document>
);
};
// Liste des taux de TVA standard pour les pays de l'UE
const euVatRates = {
AT: 0.2, // Autriche
BE: 0.21, // Belgique
BG: 0.2, // Bulgarie
HR: 0.25, // Croatie
CY: 0.19, // Chypre
CZ: 0.21, // République tchèque
DK: 0.25, // Danemark
EE: 0.2, // Estonie
FI: 0.24, // Finlande
FR: 0.2, // France
DE: 0.19, // Allemagne
GR: 0.24, // Grèce
HU: 0.27, // Hongrie
IE: 0.23, // Irlande
IT: 0.22, // Italie
LV: 0.21, // Lettonie
LT: 0.21, // Lituanie
LU: 0.17, // Luxembourg
MT: 0.18, // Malte
NL: 0.21, // Pays-Bas
PL: 0.23, // Pologne
PT: 0.23, // Portugal
RO: 0.19, // Roumanie
SK: 0.2, // Slovaquie
SI: 0.22, // Slovénie
ES: 0.21, // Espagne
SE: 0.25, // Suède
};
// Fonction pour vérifier si le pays fait partie de l'UE
function isCustomerFromEurope({ countryCode }) {
return euVatRates.hasOwnProperty(countryCode);
}
export default Invoice;
-199
View File
@@ -1,199 +0,0 @@
import React from "react";
import { Document, Page, Text, View, Font, Image } from "@react-pdf/renderer";
import {
DisplayCustomerInformation,
formatPrice,
RenderCompanyInformation,
RenderCompanyLogo,
RenderTermsOfSale,
SignatureForm,
Styles,
} from "./SharedElements";
import moment from "moment-timezone";
Font.register({
family: "Helvetica",
fonts: [
{ src: "https://fonts.gstatic.com/s/helvetica/v15/Helvetica-Regular.ttf" },
{
src: "https://fonts.gstatic.com/s/helvetica/v15/Helvetica-Bold.ttf",
fontWeight: "bold",
},
],
});
const Quote = ({
itemList = [],
project = {},
billingDetails = {},
quoteNumber = "-",
creationTimestamp = new Date(),
appConfig = {},
frequentlyAskedQuestion = [],
planning = [],
paymentTerms = "",
}) => {
const { summary = "" } = project;
const { dailyRate = {}, company } = appConfig;
const dailyRateEuro = dailyRate?.amount || 500;
const TVA_RATE = 0.2;
const sousTotalHT = itemList.reduce(
(sum, phase) => sum + phase.totalNumberOfDay * dailyRateEuro,
0
);
const tva = sousTotalHT * TVA_RATE;
const montantTotalTTC = sousTotalHT + tva;
const sections = [
{
title: `Devis n°${quoteNumber}`,
content: <RenderCompanyLogo logoBase64={company?.logoBase64} />,
},
{
title: null,
content: (
<View style={Styles.headerRow}>
<View style={Styles.leftColumn}>
<Text style={Styles.textParagraph}>
Date d'émission:{" "}
{moment(creationTimestamp)
.tz("Europe/Paris")
.format("DD/MM/YYYY à HH:mm")}
{"\n"}
Période de validité: 60 jours
</Text>
</View>
{Object.keys(billingDetails).length > 0 && (
<DisplayCustomerInformation billingDetails={billingDetails} />
)}
</View>
),
},
{
title: "Résumé du Projet",
content: <Text style={Styles.textParagraph}>{summary}</Text>,
condition: summary.length > 0,
},
{
title: "Détails des Prestations",
content: (
<View style={Styles.table}>
<View style={Styles.tableRow}>
<Text style={Styles.tableColHeaderLeft}>Description</Text>
<Text style={Styles.tableColHeaderRight}>Prix HT</Text>
</View>
{itemList.map((phase, index) => (
<View key={index} style={Styles.tableRow}>
<View>
<Text style={Styles.tableColLeft}>{phase.title}</Text>
<Text style={Styles.tableColLeft}>{phase.description}</Text>
<Text style={Styles.tableColLeft}>
Nombre de jours: {phase?.totalNumberOfDay || 0}
</Text>
</View>
<Text style={Styles.tableColRight}>
{formatPrice({
amount: phase.totalNumberOfDay * dailyRateEuro,
})}
</Text>
</View>
))}
<View style={Styles.summaryRow}>
<Text style={Styles.summaryLabel}>Sous-total HT :</Text>
<Text style={Styles.summaryValue}>
{formatPrice({ amount: sousTotalHT })}
</Text>
</View>
<View style={Styles.summaryRow}>
<Text style={Styles.summaryLabel}>TVA (20%) :</Text>
<Text style={Styles.summaryValue}>
{formatPrice({ amount: tva })}
</Text>
</View>
<View style={Styles.summaryRow}>
<Text style={Styles.summaryLabel}>Total TTC :</Text>
<Text style={Styles.summaryValue}>
{formatPrice({ amount: montantTotalTTC })}
</Text>
</View>
</View>
),
condition: itemList.length > 0,
},
RenderCompanyInformation({ company }),
{
title: "Modalités de paiement",
content: (
<Text style={Styles.textParagraph}>
{paymentTerms ||
"Acompte de 40% à la signature du devis, 30% après livraison du design et solde à la livraison."}
</Text>
),
},
{
title: "Questions fréquentes",
content: (
<View>
{frequentlyAskedQuestion.map((faq, index) => (
<View key={index}>
<Text style={Styles.textParagraph}>{faq.question}</Text>
<Text style={{ ...Styles.textParagraph, opacity: 0.5 }}>
{faq.answer}
</Text>
</View>
))}
</View>
),
condition: frequentlyAskedQuestion.length > 0,
},
{
title: "Planning prévisionnel",
content: (
<View>
<Text style={Styles.textParagraph}>
Planning estimatif des semaines de travail, sujet à modification et
ajustement périodique.
</Text>
{planning.map((event, index) => (
<View key={index}>
<Text style={Styles.textParagraph}>
{`${event?.weekNumberStart} - ${event?.weekNumberEnd}: ${event.title}`}
</Text>
<Text style={{ ...Styles.textParagraph, opacity: 0.5 }}>
{event.description}
</Text>
</View>
))}
</View>
),
condition: planning.length > 0,
},
RenderTermsOfSale(),
SignatureForm(),
].filter(({ condition = true }) => condition);
return (
<Document>
<Page style={Styles.body}>
{sections.map((section, index) => (
<View key={index} style={Styles.section}>
{section.title && (
<Text style={Styles.sectionTitle}>{section.title}</Text>
)}
{section.content}
</View>
))}
</Page>
</Document>
);
};
export default Quote;
-147
View File
@@ -1,147 +0,0 @@
import React from "react";
import { Text, View, Image, StyleSheet } from "@react-pdf/renderer";
import numbro from "numbro";
const DisplayCustomerInformation = ({ billingDetails } = {}) => {
return (
<View style={Styles.rightColumn}>
{[
`Client: ${billingDetails.legalName || "NC"}`,
billingDetails.billingAddress,
// `${billingDetails.zipCode}, ${billingDetails.city}, ${billingDetails.countryCode}`,
`TVA: ${billingDetails.taxIdentificationNumber || "NC"}`,
`SIREN: ${billingDetails.SIREN || "NC"}`,
]
.filter(Boolean)
.map((line, index) => (
<Text key={index} style={Styles.textParagraph}>
{line}
</Text>
))}
</View>
);
};
const RenderCompanyLogo = ({ logoBase64 } = {}) => {
return (
<Image src={`data:image/png;base64,${logoBase64}`} style={{ width: 200 }} />
);
};
const RenderTermsOfSale = () => {
return {
title: "Conditions générales de vente",
content: <Text style={Styles.textParagraph}>{"ICI TES CGV"}</Text>,
};
};
const RenderCompanyInformation = ({ company }) => {
return {
title: "Informations du prestataire",
content: (
<Text style={Styles.textParagraph}>
{`${company?.name}, ${company?.address}, ${company?.postalCode || "-"}, ${company?.city || "-"},\nSIRET: ${company?.identifier}, RCS: ${company?.city} ${company?.RCS}, TVA: ${company?.taxIdentifier || "-"}.\nPour toute question, vous pourrez nous contacter au ${company?.phoneNumber} ou à l'adresse ${company?.supportEmail}.`}
</Text>
),
};
};
const SignatureForm = () => {
return {
title: "Signature du client",
content: (
<>
<Text style={Styles.textParagraph}>
Fait à {"{{Ville de signature;font_size=12}}"}, le
{" {{Date;type=datenow;font_size=12}}"}
</Text>
<Text style={Styles.textParagraph}>
Nom et prénom du signataire:{" "}
{"{{Nom et Prénom du signataire;font_size=12}}"}
</Text>
<Text style={Styles.textParagraph}>
Fonction du signataire: {"{{Fonction du signataire;font_size=12}}"}
</Text>
<Text style={Styles.textParagraph}>
Téléphone: {"{{type=phone;required=true;font_size=12}}"}
</Text>
<Text style={Styles.textParagraph}>Signature:</Text>
<View style={Styles.signatureBox}>
<Text>{"{{Sign;type=signature;width=200;height=50}}"}</Text>
</View>
</>
),
};
};
const formatPrice = ({ amount = 0, average = false }) =>
`${numbro(amount || 0).format({
spaceSeparated: true,
thousandSeparated: true,
average,
mantissa: average ? 1 : 2,
})} €`;
const Styles = StyleSheet.create({
body: {
padding: 40,
fontFamily: "Helvetica",
fontSize: 12,
lineHeight: 1.5,
color: "#000",
},
section: { marginBottom: 30 },
sectionTitle: { fontSize: 14, opacity: 0.5, marginBottom: 5 },
textParagraph: { marginBottom: 5 },
headerRow: {
flexDirection: "row",
justifyContent: "space-between",
},
leftColumn: { width: "50%", textAlign: "left" },
rightColumn: { width: "50%", textAlign: "right" },
table: { marginBottom: 20 },
tableRow: {
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: "#ccc",
},
tableColHeaderLeft: { width: "70%", fontWeight: "bold", textAlign: "left" },
tableColHeaderRight: { width: "30%", fontWeight: "bold", textAlign: "right" },
tableColLeft: { width: "70%", textAlign: "left" },
tableColRight: { width: "30%", textAlign: "right" },
summaryRow: {
flexDirection: "row",
justifyContent: "flex-end",
marginTop: 10,
},
summaryLabel: {
width: "70%",
textAlign: "right",
paddingRight: 10,
fontWeight: "bold",
},
summaryValue: { width: "30%", textAlign: "right" },
signatureBox: {
width: 200,
height: 50,
borderWidth: 1,
borderStyle: "solid",
},
});
export {
Styles,
formatPrice,
DisplayCustomerInformation,
RenderTermsOfSale,
RenderCompanyLogo,
RenderCompanyInformation,
SignatureForm,
};
File diff suppressed because one or more lines are too long
-95
View File
@@ -1,95 +0,0 @@
const admin = require("firebase-admin");
const moment = require("moment");
const { onCall, HttpsError } = require("firebase-functions/v2/https");
const functions = require("firebase-functions/v1");
const { generateDocument } = require("./utils/helpers.js");
const { logoBase64 } = require("./data/logoBase64.js");
const documentTemplateList = {
QUOTE: {
path: "./components/Quote.js",
},
INVOICE: {
path: "./components/Invoice.js",
},
CONTRACT: {
path: "./components/Contract.js",
},
};
const appConfig = {
company: {
logoBase64,
name: "SAS StarsClick",
address: "8 rue de la Paix",
postalCode: "75002",
city: "Paris",
identifier: "123456789",
RCS: "123 456 789",
taxIdentifier: "FR 12 345 678 912",
phoneNumber: "01 23 45 67 89",
supportEmail: "contact@starsclick.fr",
},
};
exports.generateDocumentFromTemplate = onCall(
async ({ auth = {}, data = {} }) => {
try {
const { type, documentData = {} } = data;
const { path } = documentTemplateList[type] || {};
if (!path) {
throw new Error("Invalid document type");
}
const { default: DocumentImport } = await import(path);
const { downloadURL } = await generateDocument({
template: DocumentImport,
data: {
...documentData,
creationTimestamp: moment().format("DD/MM/YYYY"),
},
filePath: `documents/${moment().format("DD-MM-YYYY-HH-mm-ss")}.pdf`,
});
console.log(`PDF URL: ${downloadURL}`);
return { documentUrl: downloadURL };
} catch (error) {
console.log(error);
}
}
);
exports.testDocumentGeneration = functions
.runWith({ memory: "512MB" })
.https.onRequest(async (req, res) => {
try {
// const { type } = req.query;
const type = "INVOICE";
const { path } = documentTemplateList[type] || {};
if (!path) {
throw new Error("Invalid document type");
}
const { default: DocumentTemplate } = await import(path);
const { downloadURL } = await generateDocument({
template: DocumentTemplate,
data: {
appConfig,
},
filePath: `documents/${moment().format("DD-MM-YYYY-HH-mm-ss")}.pdf`,
});
console.log(`PDF URL: ${downloadURL}`);
} catch (error) {
console.log(error);
}
});
+95
View File
@@ -0,0 +1,95 @@
const {onCall} = require("firebase-functions/https");
const {z} = require("genkit");
const {generateAI} = require("../index");
exports.generateLyrics = onCall({}, async ({auth = {}, data = {}}) => {
try {
const {
objective = "Célébrer l'agence Minuit et mettre en avant son " +
"expertise digitale, son esprit d'équipe et sa créativité.",
context = "L'agence Minuit accompagne les startups et entreprises " +
"innovantes dans la création de produits digitaux, du prototype " +
"à la version de financement, jusqu'à l'optimisation et la mise " +
"à l'échelle. Spécialisée dans le développement sur-mesure " +
"d'applications mobiles, elle valorise l'humain, le design et " +
"l'accompagnement personnalisé. Esprit nocturne, équipe passionnée.",
emotion = "La Joie : expose un bonheur profond, l'émerveillement, " +
"la gratitude, satisfaction intense, énergie positive.",
style = "Upbeat : Pour une ambiance joyeuse et rythmée.",
audience = "L'équipe Minuit et ses clients fidèles, startups " +
"ambitieuses et partenaires visionnaires.",
structure = ["couplet", "refrain", "couplet", "refrain"],
rhymes = "Avec rimes",
} = data;
console.log("Function generate lyrics start with data", data);
const system = `
Tu es un parolier expert en chanson française moderne et populaire.
Tu écris des chansons marquantes, accessibles, sur des thématiques ` +
`d'innovation, d'esprit d'équipe et de créativité digitale.
Ton ` +
`écriture doit toucher à la fois l'humain, le collectif et la ` +
`passion d'entreprendre.
Tu dois générer un titre accrocheur et ` +
`mémorable pour la chanson en plus des paroles.
Respecte ` +
`précisément la structure demandée et fournis le résultat dans un ` +
`format JSON clair, respectant strictement le schéma demandé.
`.trim();
// Construction du prompt utilisateur
const prompt = `
Objectif : ${objective}
Contexte : ${context}
Émotion : ${emotion}
Style musical : ${style}
Audience cible : ${audience}
Structure de la chanson : ${structure.join(" => ")}
Schéma de rimes : ${rhymes}
Génère un titre accrocheur et les paroles de la chanson en suivant ` +
`strictement cette structure.
Le titre doit être court, ` +
`mémorable et refléter l'esprit de l'agence Minuit.
Pour chaque ` +
`partie ("couplet" ou "refrain"), écris 4 à 16 lignes adaptées, ` +
`expressives et cohérentes avec le style et l'émotion souhaitée.
`.trim();
// Définition du schéma zod/genkit pour la validation du retour
const lyricsSchema = z.object({
title: z.string().describe("Titre de la chanson, court et accrocheur"),
lyrics: z
.array(
z.object({
type: z
.string()
.describe(
"Type de section : \"couplet\" ou \"refrain\" " +
"selon la structure",
),
lyrics: z
.string()
.describe(
"Paroles de la section, chaque ligne séparée " +
"par un retour à la ligne",
),
}),
)
.describe(
"Paroles de la chanson sous forme de tableau de sections " +
"structurées.",
),
success: z.boolean().describe("Indique si la génération a réussi"),
});
return await generateAI({
system,
prompt,
schema: lyricsSchema,
});
} catch (e) {
console.log(e);
}
});
+445
View File
@@ -0,0 +1,445 @@
const { onCall, onRequest } = require("firebase-functions/v2/https");
const fetch = require("node-fetch");
const admin = require("firebase-admin");
const { logger } = require("firebase-functions/logger");
const { SUNO_API_KEY } = require("../config/keys");
// Initialiser Firestore
const db = admin.firestore();
/**
* ===== CONFIG GLOBALE =====
*/
const SUNO_API_BASE = "https://api.sunoapi.org";
const SUNO_API_PATH = "/api/v1/generate";
const SUNO_STATUS_PATH = "/api/v1/generate/record-info";
const SUNO_MODEL = "V4_5";
const SUNO_INSTRUMENTAL = false;
const SUNO_CALLBACK_URL =
"https://us-central1-musicland-d33f9.cloudfunctions.net/music-sunoCallback";
/**
* Limite la longueur d'une chaîne
* @param {string} str - La chaîne à limiter
* @param {number} max - La longueur maximale
* @return {string} La chaîne limitée
*/
function clampLen(str = "", max) {
if (!max) return str || "";
if (!str) return "";
return str.length <= max ? str : str.slice(0, max);
}
/**
* Construit le style musical à partir des paramètres
* @param {Object} params - Les paramètres de style
* @return {string} Le style formaté
*/
function buildStyle({ genres = [], voice = "", instruments = [], tempo = "" }) {
const flatGenres = (genres || []).map((g) => ("" + g).trim()).filter(Boolean);
const flatInstruments = (instruments || [])
.map((i) => ("" + i).trim())
.filter(Boolean);
const styleParts = [
flatGenres.length ? `Genres: ${flatGenres.join(", ")}` : "",
tempo ? `Tempo: ${tempo}` : "",
flatInstruments.length ? `Instruments: ${flatInstruments.join(", ")}` : "",
voice ? `Vocal: ${voice}` : "",
"Mix: propre, punchy, large stéréo, radio-ready",
].filter(Boolean);
const style = styleParts.join(" ; ");
return clampLen(style, 600);
}
/**
* Convertit les paroles en prompt avec tags
* @param {Array} lyrics - Tableau des paroles
* @return {string} Prompt formaté avec tags
*/
function lyricsToTaggedPrompt(lyrics = []) {
if (!Array.isArray(lyrics) || lyrics.length === 0) {
return "";
}
const sections = lyrics
.map((section) => {
if (!section || typeof section !== "object") return "";
const { type = "verse", content = "" } = section;
if (!content.trim()) return "";
const tag = type.toLowerCase();
return `[${tag}]\n${content.trim()}`;
})
.filter(Boolean);
return sections.join("\n\n");
}
/**
* Construit le prompt pour Suno
* @param {Object} params - Paramètres du prompt
* @return {string} Prompt formaté
*/
function buildSunoPrompt({
title,
audience = "",
projectContext = "",
emotionGuide = "",
styleGuide = "",
taggedLyrics = "",
}) {
const parts = [
title ? `Titre: ${title}` : "",
audience ? `Public cible: ${audience}` : "",
projectContext ? `Contexte: ${projectContext}` : "",
emotionGuide ? `Émotion: ${emotionGuide}` : "",
styleGuide ? `Style: ${styleGuide}` : "",
taggedLyrics ? `Paroles:\n${taggedLyrics}` : "",
].filter(Boolean);
return parts.join("\n\n");
}
/**
* Construit les guides de style et d'émotion
* @param {Object} params - Paramètres des guides
* @return {Object} Guides formatés
*/
function buildGuides({
genres = [],
tempo = "",
voice = "",
instruments = [],
}) {
const styleElements = [];
if (genres && genres.length > 0) {
styleElements.push(`Genres: ${genres.join(", ")}`);
}
if (tempo) {
const tempoGuide = tempo.toLowerCase().includes("lent")
? "Rythme lent et contemplatif"
: tempo.toLowerCase().includes("rapide")
? "Rythme énergique et dynamique"
: `Tempo ${tempo.toLowerCase()}.`;
styleElements.push(tempoGuide);
}
if (instruments && instruments.length > 0) {
styleElements.push(`Instruments principaux: ${instruments.join(", ")}`);
}
if (voice) {
styleElements.push(`Style vocal: ${voice}`);
}
return {
styleGuide: styleElements.join(" "),
emotionGuide: "",
};
}
/**
* Fonction principale de génération de musique
*/
exports.generateMusic = onCall(
{ region: "europe-west1" },
async ({ data = {} }) => {
try {
const {
title = "",
lyrics = [],
genres = [],
voice = "",
instruments = [],
tempo = "",
audience = "",
projectContext = "",
} = data;
const { styleGuide, emotionGuide } = buildGuides({
genres,
voice,
instruments,
tempo,
});
const taggedLyrics = lyricsToTaggedPrompt(lyrics);
const safeTitle = clampLen(title, 80);
// Construire les informations de contexte pour le style (sans les paroles)
const contextInfo = buildSunoPrompt({
title: safeTitle,
audience,
projectContext,
emotionGuide,
styleGuide,
taggedLyrics: "", // Ne pas inclure les paroles dans le contexte
});
// Le style combine les informations de genre ET le contexte
const baseStyle = buildStyle({ genres, voice, instruments, tempo });
const enhancedStyle = contextInfo
? `${baseStyle}. ${contextInfo}`
: baseStyle;
const payload = {
customMode: true,
model: SUNO_MODEL,
title: safeTitle,
style: clampLen(enhancedStyle, 600),
instrumental: SUNO_INSTRUMENTAL,
callBackUrl: SUNO_CALLBACK_URL || "",
};
// Les paroles vont dans le prompt, pas les instructions de style
if (!SUNO_INSTRUMENTAL && taggedLyrics) {
payload.prompt = clampLen(taggedLyrics, 3000);
}
const response = await fetch(`${SUNO_API_BASE}${SUNO_API_PATH}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${SUNO_API_KEY}`,
},
body: JSON.stringify(payload),
});
const raw = await response.text();
let parsed;
try {
parsed = JSON.parse(raw);
} catch (parseError) {
console.error("❌ Erreur de parsing JSON:", parseError);
const { HttpsError } = require("firebase-functions/v2/https");
throw new HttpsError(
"internal",
`Réponse API invalide: ${raw.substring(0, 200)}...`,
);
}
if (!response.ok) {
console.error("❌ Erreur API Suno:", {
status: response.status,
statusText: response.statusText,
body: parsed || raw,
});
throw new Error(
`Erreur API Suno (${response.status}): ${
(parsed && parsed.error) || response.statusText
}`,
);
}
return {
success: true,
request: {
model: SUNO_MODEL,
instrumental: SUNO_INSTRUMENTAL,
title: safeTitle,
style: enhancedStyle,
prompt: SUNO_INSTRUMENTAL ? undefined : taggedLyrics,
},
response: parsed || { raw },
};
} catch (error) {
console.error("❌ Erreur lors de la génération de musique:", error);
throw new Error(
`Erreur lors de la génération de musique: ${error.message}`,
);
}
},
);
/**
* Fonction pour suivre le statut d'une génération de musique Suno
*/
exports.getSunoStatus = onCall(
{ region: "europe-west1" },
async ({ data = {} }) => {
try {
const { taskId } = data;
if (!taskId) {
throw new Error("TaskId manquant");
}
logger.info("🔍 Vérification du statut Suno", { taskId });
const response = await fetch(
`${SUNO_API_BASE}${SUNO_STATUS_PATH}?taskId=${taskId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${SUNO_API_KEY}`,
},
},
);
const raw = await response.text();
let parsed;
try {
parsed = JSON.parse(raw);
} catch (parseError) {
console.error("❌ Erreur de parsing JSON:", parseError);
throw new Error(`Réponse API invalide: ${raw.substring(0, 200)}...`);
}
if (!response.ok) {
console.error("❌ Erreur API Suno Status:", {
status: response.status,
statusText: response.statusText,
body: parsed || raw,
});
// Gestion spéciale pour les taskId de test
if (response.status === 404 && taskId.includes("test")) {
return {
success: true,
taskId,
data: {
status: "not_found",
message: "TaskId de test - tâche non trouvée dans l'API Suno",
isTestId: true,
},
};
}
throw new Error(
`Erreur API Suno Status (${response.status}): ${
(parsed && parsed.error) || response.statusText
}`,
);
}
logger.info("✅ Statut Suno récupéré", {
taskId,
status: parsed && parsed.status,
});
return {
success: true,
taskId,
data: parsed,
};
} catch (error) {
console.error("❌ Erreur lors de la vérification du statut:", error);
throw new Error(
`Erreur lors de la vérification du statut: ${error.message}`,
);
}
},
);
/**
* Cloud Function pour recevoir les callbacks de l'API Suno
* Cette fonction est appelée par l'API Suno lorsque la génération
* de musique est terminée
*/
exports.sunoCallback = onRequest(
{
region: "europe-west1",
cors: true,
methods: ["POST"],
},
async (req, res) => {
try {
logger.info("🎵 Callback Suno reçu", { data: req.body });
// Vérifier que c'est une requête POST
if (req.method !== "POST") {
logger.warn("⚠️ Méthode non autorisée:", req.method);
return res.status(405).json({ error: "Méthode non autorisée" });
}
// Extraire les données du callback
const {
id,
status,
audio_url: audioUrl,
video_url: videoUrl,
image_url: imageUrl,
lyric,
title,
tags,
duration,
error_message: errorMessage,
} = req.body;
// Vérifier que l'ID est présent
if (!id) {
logger.error("❌ ID manquant dans le callback");
return res.status(400).json({ error: "ID manquant" });
}
// Chercher le document correspondant dans Firestore
const musicRef = db.collection("music").doc(id);
const musicDoc = await musicRef.get();
if (!musicDoc.exists) {
logger.error("❌ Document non trouvé pour l'ID:", id);
return res.status(404).json({ error: "Document non trouvé" });
}
// Préparer les données de mise à jour
const updateData = {
status: status,
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
};
// Ajouter les URLs si disponibles
if (audioUrl) {
updateData.audioUrl = audioUrl;
}
if (videoUrl) {
updateData.videoUrl = videoUrl;
}
if (imageUrl) {
updateData.imageUrl = imageUrl;
}
if (lyric) {
updateData.lyrics = lyric;
}
if (title) {
updateData.title = title;
}
if (tags) {
updateData.style = tags;
}
if (duration) {
updateData.duration = duration;
}
if (errorMessage) {
updateData.errorMessage = errorMessage;
}
// Mettre à jour le document dans Firestore
await musicRef.update(updateData);
logger.info("✅ Document mis à jour avec succès", {
id: id,
status: status,
});
// Répondre avec succès
res.status(200).json({
success: true,
message: "Callback traité avec succès",
id: id,
status: status,
});
} catch (error) {
logger.error("❌ Erreur lors du traitement du callback:", error);
res.status(500).json({
error: "Erreur interne du serveur",
message: error.message,
});
}
},
);
-163
View File
@@ -1,163 +0,0 @@
const functions = require("firebase-functions");
const { refsList } = require("..");
const { onCall } = require("firebase-functions/v2/https");
const isStripeProd = false;
const stripe = require("stripe")(isStripeProd ? "sk_live_..." : "sk_test_...");
const baseURL = isStripeProd ? "https://.../" : "https://.../";
exports.createCheckoutSession = onCall(async ({ auth = {}, data = {} }) => {
try {
const { userID, productList = [] } = data || {};
console.log("productList", productList);
if (!userID) {
throw new Error("userID is required");
}
const currentUserRef = refsList.users.doc(userID);
const userData = (await currentUserRef.get())?.data() || null;
let stripeCustomerID;
const { firstName } = userData || {};
let updatedCustomerData = {
name: firstName,
email: auth.token.email,
};
if (userData?.stripeCustomerID) {
stripeCustomerID = userData.stripeCustomerID;
} else {
const customer = await stripe.customers.create({
...updatedCustomerData,
});
stripeCustomerID = customer.id;
await currentUserRef.update({ stripeCustomerID });
}
await stripe.customers.update(stripeCustomerID, updatedCustomerData);
let line_items = productList.map((product) => ({
price: product.priceID,
quantity: product.quantity,
}));
const hasSubscription = productList.some((product) => product.isRenewable);
let checkoutObject = {
line_items: line_items,
mode: hasSubscription ? "subscription" : "payment", // Use "subscription" to support both one-time and subscription items
ui_mode: "embedded",
return_url: `${baseURL}explore`,
customer: stripeCustomerID,
invoice_creation: {
enabled: true,
},
};
const session = await stripe.checkout.sessions.create(checkoutObject);
return session;
} catch (e) {
console.log(e);
throw new functions.https.HttpsError("permission-denied", e.message);
}
});
exports.getPremiumStatus = onCall(async ({ auth = {}, data = {} }) => {
// Vérifiez l'authentification de l'utilisateur
if (!auth) {
throw new functions.https.HttpsError(
"unauthenticated",
"The function must be called while authenticated."
);
}
const uid = auth.uid;
try {
// Récupérer l'ID client Stripe depuis Firestore (supposons que vous l'avez stocké précédemment)
const userDoc = await refsList.users.doc(uid).get();
const stripeCustomerID = userDoc.data()?.stripeCustomerID || null;
if (!stripeCustomerID) {
throw new functions.https.HttpsError(
"not-found",
"Stripe customer ID not found."
);
}
// Récupérer les abonnements de l'utilisateur
const subscriptions = await stripe.subscriptions.list({
customer: stripeCustomerID,
status: "all",
});
// Récupérer les achats de produits (Invoices)
const invoices = await stripe.invoices.list({
customer: stripeCustomerID,
});
console.log("subscriptions", subscriptions);
console.log("invoices", invoices);
return {
subscriptions: subscriptions.data,
invoices: invoices.data,
};
} catch (error) {
console.error("Error fetching Stripe details:", error);
return {
subscriptions: [],
invoices: [],
};
}
});
exports.createStripeCustomerPortalSession = onCall(
async ({ auth = {}, data = {} }) => {
// Vérifiez l'authentification de l'utilisateur
if (!auth) {
throw new functions.https.HttpsError(
"unauthenticated",
"The function must be called while authenticated."
);
}
const uid = auth.uid;
try {
// Récupérer l'ID client Stripe depuis Firestore (supposons que vous l'avez stocké précédemment)
const userDoc = await refsList.users.doc(uid).get();
const stripeCustomerID = userDoc.data().stripeCustomerID;
if (!stripeCustomerID) {
throw new functions.https.HttpsError(
"not-found",
"Stripe customer ID not found."
);
}
// Créer une session de portail client
const session = await stripe.billingPortal.sessions.create({
customer: stripeCustomerID,
return_url: baseURL, // Remplacez par l'URL de retour de votre application
});
return {
url: session.url,
};
} catch (error) {
console.error("Error creating Stripe customer portal session:", error);
throw new functions.https.HttpsError(
"internal",
"Unable to create Stripe customer portal session."
);
}
}
);
-66
View File
@@ -1,66 +0,0 @@
const admin = require("firebase-admin");
const { onCall, HttpsError } = require("firebase-functions/v2/https");
const { onDocumentWritten } = require("firebase-functions/v2/firestore");
const { refsList } = require("..");
exports.userUpdateListener = onDocumentWritten(
"users/{userID}",
async (userSnap) => {
try {
const { userID } = userSnap.params;
const currentData = userSnap?.data?.after?.data() || null;
const previousData = userSnap?.data?.before?.data() || null;
if (!currentData) {
await admin.auth().deleteUser(userID);
}
if (!previousData) {
} else if (!!previousData && !!currentData) {
const { email = null } = currentData;
try {
if (previousData?.email && previousData?.email !== email && email) {
await admin.auth().updateUser(userID, {
email,
});
}
} catch (e) {
console.log(e);
}
}
} catch (e) {
console.log(e);
}
}
);
exports.deleteAccount = onCall(async ({ auth = {}, data = {} }) => {
try {
if (!auth.uid) {
throw new HttpsError(
"unauthenticated",
"Vous devez être connecté pour effectuer cette action."
);
}
const userDoc = await refsList.users.doc(auth.uid).get();
if (!userDoc.exists) {
throw new HttpsError("not-found", "L'utilisateur n'existe pas.");
}
await admin.auth().deleteUser(auth.uid);
await userDoc.ref.delete();
return { success: true };
} catch (error) {
console.error(error);
throw new HttpsError(
"internal",
"Une erreur s'est produite lors de la suppression de votre compte."
);
}
});
-154
View File
@@ -1,154 +0,0 @@
const { Timestamp } = require("firebase-admin/firestore");
const admin = require("firebase-admin");
const React = require("react");
const { refsList, genkitInstance } = require("../..");
exports.getCompanyDataFromUserID = async ({ userID = null }) => {
try {
if (!userID) {
throw new Error("No userID provided");
}
const companySnapshot =
(await refsList.companies.where("mainOwnerID", "==", userID).get())
.docs?.[0] || null;
if (!companySnapshot) {
throw new Error("Company not found");
}
const companyData =
{ ...companySnapshot.data(), companyID: companySnapshot.id } || null;
return companyData;
} catch (error) {
console.log(error);
throw error;
}
};
const logNewAction = async ({
description = "",
userID = null,
isSuccess = true,
}) => {
try {
if (!description?.length) {
throw new Error("Missing required fields");
}
await refsList.logs.add({
isSuccess,
description,
userID,
timestamp: Timestamp.now(),
});
} catch (error) {
console.log(error);
throw error;
}
};
exports.logNewAction = logNewAction;
// Fonction pour générer et uploader le PDF
exports.generateDocument = async ({ template, data, filePath }) => {
try {
// Générer le PDF en buffer
// Import dynamique de Quote en ESM
const { pdf } = await import("@react-pdf/renderer");
// Générer le PDF en buffer
const buffer = await pdf(
React.createElement(template, {
...data,
})
).toBuffer();
// Uploader le PDF dans Firebase Storage
const bucket = admin.storage().bucket("gs://starsclick.appspot.com/");
const file = bucket.file(filePath);
console.log(`Uploading PDF to Firebase Storage as ${filePath}...`);
const chunks = [];
buffer.on("data", (chunk) => chunks.push(chunk));
buffer.on("end", async () => {
const pdfBuffer = Buffer.concat(chunks);
await file.save(pdfBuffer, {
metadata: {
contentType: "application/pdf",
},
});
});
// Générer une URL signée
const expiresAt = Date.now() + 1000 * 60 * 60 * 24 * 7; // Expire dans 7 jours
const [url] = await file.getSignedUrl({
action: "read",
expires: expiresAt,
});
console.log(`PDF uploaded to Firebase Storage as ${filePath}.`);
console.log(`PDF URL: ${url}`);
return { downloadURL: url };
} catch (error) {
console.error("Error generating or uploading PDF:", error);
throw error;
}
};
exports.generateAI = async ({ prompt = "", schema, config = {} }) => {
if (prompt?.length < 1) {
throw new Error(
"Vous devez spécifier un prompt pour effectuer cette action."
);
}
let attempts = 0;
let result;
while (attempts < 3) {
try {
const { output } = await genkitInstance.generate({
system:
"Voici un prompt, le contenu généré doit toujours être en français: ",
prompt,
config: {
...config,
},
output: { schema },
});
console.log("Résultat de la génération par IA:", output);
result = output;
await logNewAction({
description: `Génération par IA réussie`,
isSuccess: true,
});
return result;
} catch (error) {
attempts += 1;
console.log(`Tentative ${attempts} échouée:`, error);
if (attempts >= 3) {
await logNewAction({
description: `Génération par IA échouée après plusieurs tentatives`,
isSuccess: false,
error,
});
throw new Error(
"Erreur lors de la génération par IA après plusieurs tentatives, veuillez réessayer."
);
}
}
}
};
-2
View File
@@ -1,2 +0,0 @@
Web / API server started at 127.0.0.1:4000
Web / API server started at ::1:4000
+1 -1
View File
@@ -13,7 +13,7 @@ install! 'cocoapods',
prepare_react_native_project! prepare_react_native_project!
target 'minuitstarter' do target 'musicland' do
use_expo_modules! use_expo_modules!
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1' if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
+3964
View File
File diff suppressed because it is too large Load Diff
-477
View File
@@ -1,477 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
96905EF65AED1B983A6B3ABC /* libPods-minuitstarter.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-minuitstarter.a */; };
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
86E87E7EF63547E884C7EE41 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 233FD88934724953B0CAFD13 /* GoogleService-Info.plist */; };
724C02E629504D58B1E3E1C4 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB4CCD694E13411AA978E1F4 /* noop-file.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
13B07F961A680F5B00A75B9A /* minuitstarter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = minuitstarter.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = minuitstarter/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = minuitstarter/AppDelegate.mm; sourceTree = "<group>"; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = minuitstarter/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = minuitstarter/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = minuitstarter/main.m; sourceTree = "<group>"; };
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-minuitstarter.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-minuitstarter.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6C2E3173556A471DD304B334 /* Pods-minuitstarter.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-minuitstarter.debug.xcconfig"; path = "Target Support Files/Pods-minuitstarter/Pods-minuitstarter.debug.xcconfig"; sourceTree = "<group>"; };
7A4D352CD337FB3A3BF06240 /* Pods-minuitstarter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-minuitstarter.release.xcconfig"; path = "Target Support Files/Pods-minuitstarter/Pods-minuitstarter.release.xcconfig"; sourceTree = "<group>"; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = minuitstarter/SplashScreen.storyboard; sourceTree = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-minuitstarter/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
233FD88934724953B0CAFD13 /* GoogleService-Info.plist */ = {isa = PBXFileReference; name = "GoogleService-Info.plist"; path = "minuitstarter/GoogleService-Info.plist"; sourceTree = "<group>"; fileEncoding = 4; lastKnownFileType = text.plist.xml; explicitFileType = undefined; includeInIndex = 0; };
EB4CCD694E13411AA978E1F4 /* noop-file.swift */ = {isa = PBXFileReference; name = "noop-file.swift"; path = "minuitstarter/noop-file.swift"; sourceTree = "<group>"; fileEncoding = 4; lastKnownFileType = sourcecode.swift; explicitFileType = undefined; includeInIndex = 0; };
900CF7F80C634C0FB32C45F6 /* minuitstarter-Bridging-Header.h */ = {isa = PBXFileReference; name = "minuitstarter-Bridging-Header.h"; path = "minuitstarter/minuitstarter-Bridging-Header.h"; sourceTree = "<group>"; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
96905EF65AED1B983A6B3ABC /* libPods-minuitstarter.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
13B07FAE1A68108700A75B9A /* minuitstarter */ = {
isa = PBXGroup;
children = (
BB2F792B24A3F905000567C9 /* Supporting */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.mm */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB71A68108700A75B9A /* main.m */,
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
233FD88934724953B0CAFD13 /* GoogleService-Info.plist */,
EB4CCD694E13411AA978E1F4 /* noop-file.swift */,
900CF7F80C634C0FB32C45F6 /* minuitstarter-Bridging-Header.h */,
);
name = minuitstarter;
sourceTree = "<group>";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-minuitstarter.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
);
name = Libraries;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* minuitstarter */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
D65327D7A22EEC0BE12398D9 /* Pods */,
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* minuitstarter.app */,
);
name = Products;
sourceTree = "<group>";
};
92DBD88DE9BF7D494EA9DA96 /* minuitstarter */ = {
isa = PBXGroup;
children = (
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */,
);
name = minuitstarter;
sourceTree = "<group>";
};
BB2F792B24A3F905000567C9 /* Supporting */ = {
isa = PBXGroup;
children = (
BB2F792C24A3F905000567C9 /* Expo.plist */,
);
name = Supporting;
path = minuitstarter/Supporting;
sourceTree = "<group>";
};
D65327D7A22EEC0BE12398D9 /* Pods */ = {
isa = PBXGroup;
children = (
6C2E3173556A471DD304B334 /* Pods-minuitstarter.debug.xcconfig */,
7A4D352CD337FB3A3BF06240 /* Pods-minuitstarter.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = {
isa = PBXGroup;
children = (
92DBD88DE9BF7D494EA9DA96 /* minuitstarter */,
);
name = ExpoModulesProviders;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
13B07F861A680F5B00A75B9A /* minuitstarter */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "minuitstarter" */;
buildPhases = (
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = minuitstarter;
productName = minuitstarter;
productReference = 13B07F961A680F5B00A75B9A /* minuitstarter.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1130;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1250;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "minuitstarter" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* minuitstarter */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
86E87E7EF63547E884C7EE41 /* GoogleService-Info.plist in Resources */,
5B79BC161B624D5A9880D7AA /* minuitstarter-Bridging-Header.h in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Bundle React Native code and images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
};
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-minuitstarter-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-minuitstarter/Pods-minuitstarter-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-minuitstarter/Pods-minuitstarter-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
724C02E629504D58B1E3E1C4 /* noop-file.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-minuitstarter.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"FB_SONARKIT_ENABLED=1",
);
INFOPLIST_FILE = minuitstarter/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.minuit.starter";
PRODUCT_NAME = "minuitstarter";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
DEVELOPMENT_TEAM = W2QZ9CTMYJ;
TARGETED_DEVICE_FAMILY = "1,2";
SWIFT_OBJC_BRIDGING_HEADER = minuitstarter/minuitstarter-Bridging-Header.h;
CODE_SIGN_ENTITLEMENTS = minuitstarter/minuitstarter.entitlements;
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-minuitstarter.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = minuitstarter/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.minuit.starter";
PRODUCT_NAME = "minuitstarter";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
DEVELOPMENT_TEAM = W2QZ9CTMYJ;
TARGETED_DEVICE_FAMILY = "1,2";
SWIFT_OBJC_BRIDGING_HEADER = minuitstarter/minuitstarter-Bridging-Header.h;
CODE_SIGN_ENTITLEMENTS = minuitstarter/minuitstarter.entitlements;
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = "\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = "\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "minuitstarter" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
13B07F951A680F5B00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "minuitstarter" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
File diff suppressed because one or more lines are too long
@@ -15,9 +15,9 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A" BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "minuitstarter.app" BuildableName = "musicland.app"
BlueprintName = "minuitstarter" BlueprintName = "musicland"
ReferencedContainer = "container:minuitstarter.xcodeproj"> ReferencedContainer = "container:musicland.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
@@ -33,9 +33,9 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "00E356ED1AD99517003FC87E" BlueprintIdentifier = "00E356ED1AD99517003FC87E"
BuildableName = "minuitstarterTests.xctest" BuildableName = "musiclandTests.xctest"
BlueprintName = "minuitstarterTests" BlueprintName = "musiclandTests"
ReferencedContainer = "container:minuitstarter.xcodeproj"> ReferencedContainer = "container:musicland.xcodeproj">
</BuildableReference> </BuildableReference>
</TestableReference> </TestableReference>
</Testables> </Testables>
@@ -55,9 +55,9 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A" BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "minuitstarter.app" BuildableName = "musicland.app"
BlueprintName = "minuitstarter" BlueprintName = "musicland"
ReferencedContainer = "container:minuitstarter.xcodeproj"> ReferencedContainer = "container:musicland.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
</LaunchAction> </LaunchAction>
@@ -72,9 +72,9 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A" BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "minuitstarter.app" BuildableName = "musicland.app"
BlueprintName = "minuitstarter" BlueprintName = "musicland"
ReferencedContainer = "container:minuitstarter.xcodeproj"> ReferencedContainer = "container:musicland.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
</ProfileAction> </ProfileAction>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:musicland.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

Before

Width:  |  Height:  |  Size: 663 KiB

After

Width:  |  Height:  |  Size: 663 KiB

@@ -10,13 +10,7 @@
}, },
"color-space": "srgb" "color-space": "srgb"
}, },
"idiom": "universal", "idiom": "universal"
"appearances": [
{
"appearance": "luminosity",
"value": "light"
}
]
} }
], ],
"info": { "info": {
@@ -2,34 +2,16 @@
"images": [ "images": [
{ {
"idiom": "universal", "idiom": "universal",
"appearances": [
{
"appearance": "luminosity",
"value": "light"
}
],
"filename": "image.png", "filename": "image.png",
"scale": "1x" "scale": "1x"
}, },
{ {
"idiom": "universal", "idiom": "universal",
"appearances": [
{
"appearance": "luminosity",
"value": "light"
}
],
"filename": "image@2x.png", "filename": "image@2x.png",
"scale": "2x" "scale": "2x"
}, },
{ {
"idiom": "universal", "idiom": "universal",
"appearances": [
{
"appearance": "luminosity",
"value": "light"
}
],
"filename": "image@3x.png", "filename": "image@3x.png",
"scale": "3x" "scale": "3x"
} }

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>minuit.starter</string> <string>musicland</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@@ -27,7 +27,7 @@
<dict> <dict>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>minuit</string> <string>musicland</string>
<string>com.minuit.starter</string> <string>com.minuit.starter</string>
</array> </array>
</dict> </dict>
+50
View File
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
<string>0A2A.1</string>
<string>3B52.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
<string>1C8F.1</string>
<string>C56D.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
<string>85F4.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>
@@ -28,7 +28,6 @@
<constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="d6a0be88096b36fb132659aa90203d39139deda9"/> <constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="d6a0be88096b36fb132659aa90203d39139deda9"/>
</constraints> </constraints>
<color key="backgroundColor" name="SplashScreenBackground"/> <color key="backgroundColor" name="SplashScreenBackground"/>
<color key="backgroundColor" name="SplashScreenBackground"/>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -41,8 +40,5 @@
<namedColor name="SplashScreenBackground"> <namedColor name="SplashScreenBackground">
<color alpha="1.000" blue="0.0784313725490196" green="0.0470588235294118" red="0.0588235294117647" customColorSpace="sRGB" colorSpace="custom"/> <color alpha="1.000" blue="0.0784313725490196" green="0.0470588235294118" red="0.0588235294117647" customColorSpace="sRGB" colorSpace="custom"/>
</namedColor> </namedColor>
<namedColor name="SplashScreenBackground">
<color alpha="1.000" blue="0.0784313725490196" green="0.0470588235294118" red="0.0588235294117647" customColorSpace="sRGB" colorSpace="custom"/>
</namedColor>
</resources> </resources>
</document> </document>
+30 -25
View File
@@ -6,8 +6,8 @@
"android": "expo run:android", "android": "expo run:android",
"ios": "expo run:ios", "ios": "expo run:ios",
"web": "expo start --web", "web": "expo start --web",
"postinstall": "patch-package", "xcode": "open ios/musicland.xcworkspace",
"clean": "node cleanProject.js", "clean": "npx expo prebuild --clean",
"build:web": "expo export -p web", "build:web": "expo export -p web",
"build:desktop:dev": "npx pake-cli http://localhost:8081 --name minuitdev --iter-copy-file --icon ./assets/desktopIcon.icns", "build:desktop:dev": "npx pake-cli http://localhost:8081 --name minuitdev --iter-copy-file --icon ./assets/desktopIcon.icns",
"build:desktop:prod": "yarn build:web && npx pake-cli ./dist/index.html --name minuit --iter-copy-file --icon ./assets/desktopIcon.icns" "build:desktop:prod": "yarn build:web && npx pake-cli ./dist/index.html --name minuit --iter-copy-file --icon ./assets/desktopIcon.icns"
@@ -16,7 +16,7 @@
"@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"@expo-google-fonts/inter": "^0.4.1", "@expo-google-fonts/inter": "^0.4.1",
"@expo/config-plugins": "~9.0.0", "@expo/config-plugins": "~9.0.0",
"@expo/metro-runtime": "~4.0.0", "@expo/metro-runtime": "~4.0.1",
"@expo/react-native-action-sheet": "^4.0.1", "@expo/react-native-action-sheet": "^4.0.1",
"@gorhom/bottom-sheet": "^4", "@gorhom/bottom-sheet": "^4",
"@gorhom/portal": "^1.0.14", "@gorhom/portal": "^1.0.14",
@@ -38,25 +38,26 @@
"@stripe/react-stripe-js": "^2.5.0", "@stripe/react-stripe-js": "^2.5.0",
"@stripe/stripe-js": "^3.0.0", "@stripe/stripe-js": "^3.0.0",
"deprecated-react-native-prop-types": "^3.0.1", "deprecated-react-native-prop-types": "^3.0.1",
"expo": "~52.0.17", "expo": "~52.0.47",
"expo-av": "~15.0.1", "expo-av": "~15.0.2",
"expo-blur": "~14.0.1", "expo-blur": "~14.0.3",
"expo-build-properties": "~0.13.1", "expo-build-properties": "~0.13.3",
"expo-constants": "~17.0.3", "expo-constants": "~17.0.8",
"expo-dev-client": "~5.0.5", "expo-dev-client": "~5.0.20",
"expo-document-picker": "~13.0.1", "expo-device": "~7.0.3",
"expo-font": "~13.0.1", "expo-document-picker": "~13.0.3",
"expo-haptics": "~14.0.0", "expo-font": "~13.0.4",
"expo-image": "~2.0.3", "expo-haptics": "~14.0.1",
"expo-image-picker": "~16.0.3", "expo-image": "~2.0.7",
"expo-insights": "~0.8.1", "expo-image-picker": "~16.0.6",
"expo-linear-gradient": "~14.0.1", "expo-insights": "~0.8.2",
"expo-linking": "~7.0.3", "expo-linear-gradient": "~14.0.2",
"expo-location": "~18.0.3", "expo-linking": "~7.0.5",
"expo-speech": "~13.0.0", "expo-location": "~18.0.10",
"expo-splash-screen": "~0.29.16", "expo-speech": "~13.0.1",
"expo-status-bar": "~2.0.0", "expo-splash-screen": "~0.29.24",
"expo-system-ui": "~4.0.5", "expo-status-bar": "~2.0.1",
"expo-system-ui": "~4.0.9",
"firebase": "^9.20.0", "firebase": "^9.20.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
@@ -64,7 +65,7 @@
"patch-package": "^6.5.0", "patch-package": "^6.5.0",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-native": "0.76.5", "react-native": "0.76.9",
"react-native-compressor": "^1.8.24", "react-native-compressor": "^1.8.24",
"react-native-country-picker-modal": "^2.0.0", "react-native-country-picker-modal": "^2.0.0",
"react-native-dialog": "^9.3.0", "react-native-dialog": "^9.3.0",
@@ -92,17 +93,21 @@
"@babel/core": "^7.20.5", "@babel/core": "^7.20.5",
"@babel/parser": "^7.25.6", "@babel/parser": "^7.25.6",
"@babel/traverse": "^7.25.6", "@babel/traverse": "^7.25.6",
"eslint": "^8.57.0",
"eslint-config-expo": "~8.0.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"imagemin": "^9.0.0", "imagemin": "^9.0.0",
"imagemin-gifsicle": "^7.0.0", "imagemin-gifsicle": "^7.0.0",
"imagemin-jpegtran": "^7.0.0", "imagemin-jpegtran": "^7.0.0",
"imagemin-optipng": "^8.0.0", "imagemin-optipng": "^8.0.0",
"imagemin-svgo": "^11.0.1", "imagemin-svgo": "^11.0.1",
"prettier": "^3.2.5",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"resolutions": { "resolutions": {
"firebase": "^9.20.0", "firebase": "^9.20.0",
"react-error-overlay": "6.0.9", "react-error-overlay": "6.0.9"
"use-force-update": "1.0.8"
}, },
"private": true "private": true
} }
@@ -1,13 +0,0 @@
diff --git a/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m b/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m
index c139440..45a97dd 100644
--- a/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m
+++ b/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m
@@ -41,7 +40,7 @@ - (void)setTimeZoneName:(NSString *)timeZoneName {
YGNodeMarkDirty(self.yogaNode);
}
-static YGSize RNDateTimePickerShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
+static YGSize RNDateTimePickerShadowViewMeasure(YGNodeConstRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
{
RNDateTimePickerShadowView *shadowPickerView = (__bridge RNDateTimePickerShadowView *)YGNodeGetContext(node);
-13
View File
@@ -1,13 +0,0 @@
diff --git a/node_modules/react-async-hook/package.json b/node_modules/react-async-hook/package.json
index d881745..3ba7d1d 100644
--- a/node_modules/react-async-hook/package.json
+++ b/node_modules/react-async-hook/package.json
@@ -31,7 +31,7 @@
"npm": ">=5"
},
"main": "dist/index.js",
- "module": "react-async-hook.esm.js",
+ "module": "dist/react-async-hook.esm.js",
"typings": "dist/index.d.ts",
"files": [
"dist"
+50
View File
@@ -0,0 +1,50 @@
# Intégration de la fonction callback dans music.js
## Objectif
Centraliser toute la logique liée à l'API Suno dans un seul fichier pour une meilleure organisation du code.
## Changements effectués
### ✅ Déplacement de la fonction callback
- [x] Ajout des imports nécessaires dans `music.js` (onRequest, admin, logger)
- [x] Initialisation de Firestore dans `music.js`
- [x] Intégration complète de la fonction `sunoCallback` dans `music.js`
- [x] Suppression du fichier `callback.js` devenu obsolète
### ✅ Mise à jour des exports
- [x] Modification de `index.js` pour exporter `sunoCallback` depuis `music.js`
- [x] Correction de la syntaxe d'export pour éviter les erreurs de parsing
## Structure finale
### Fichier `music.js`
- Fonction `generateMusic` : génération de musique via l'API Suno
- Fonction `sunoCallback` : réception des callbacks de l'API Suno
- Toutes les fonctions utilitaires (buildStyle, clampLen, etc.)
- Configuration globale de l'API Suno
### Fichier `index.js`
- Export de `music` (contient generateMusic)
- Export de `sunoCallback` (fonction callback)
- Export de `lyrics`
## Avantages
1. **Centralisation** : Toute la logique Suno dans un seul fichier
2. **Cohérence** : Partage des constantes et utilitaires
3. **Maintenance** : Plus facile de maintenir et déboguer
4. **Organisation** : Structure plus claire du projet
## URL de callback
L'URL reste la même :
```
https://us-central1-musicland-app.cloudfunctions.net/sunoCallback
```
## Prochaines étapes
1. Déployer les fonctions : `firebase deploy --only functions`
2. Tester la génération de musique
3. Vérifier la réception des callbacks
4. Contrôler les mises à jour Firestore
+43
View File
@@ -0,0 +1,43 @@
# Plan de correction de l'API Suno
## Problème identifié
- [ ] ❌ L'API Suno retournait une erreur 404 à cause d'une URL incorrecte
- [ ] ❌ Les paramètres de la requête n'étaient pas conformes à la documentation officielle
- [ ] ❌ L'authentification utilisait un mauvais format de header
## Solutions appliquées
### 1. Correction de l'URL de l'API
- [x] ✅ Changé `https://api.sunoaiapi.com` vers `https://api.sunoapi.org`
- [x] ✅ Changé l'endpoint `/api/custom_generate` vers `/api/v1/generate`
### 2. Correction des paramètres de la requête
- [x] ✅ Changé `custom_mode` vers `customMode` (camelCase)
- [x] ✅ Changé `tags` vers `style` selon la documentation
- [x] ✅ Changé `callback_url` vers `callBackUrl` (camelCase)
### 3. Correction de l'authentification
- [x] ✅ Changé `X-API-Key` vers `Authorization: Bearer` selon la documentation
### 4. Ajustement du modèle
- [x] ✅ Changé `V4_5PLUS` vers `V4_5` (modèle supporté)
## Tests à effectuer
- [ ] 🧪 Tester la génération de musique avec paroles
- [ ] 🧪 Tester la génération de musique instrumentale
- [ ] 🧪 Vérifier la réponse de l'API et le format des données retournées
- [ ] 🧪 Tester avec différents styles musicaux
## Documentation de référence
- 📚 [Documentation officielle Suno API](https://docs.sunoapi.org/suno-api/generate-music)
- 📚 URL de l'API: `https://api.sunoapi.org/api/v1/generate`
- 📚 Authentification: `Authorization: Bearer <token>`
## Fichiers modifiés
- ✏️ `/functions/src/music.js` - Correction de l'URL, des paramètres et de l'authentification
## Notes importantes
- ⚠️ L'API Suno a une limite de concurrence: 20 requêtes toutes les 10 secondes
- ⚠️ Chaque requête génère exactement 2 chansons
- ⚠️ URL de streaming disponible en 30-40 secondes
- ⚠️ URL de téléchargement prête en 2-3 minutes
+124
View File
@@ -0,0 +1,124 @@
# Plan d'implémentation - Fonction Callback Suno
## Objectif
Créer une cloud function qui servira de callback pour l'API Suno afin de recevoir les notifications lorsque la génération de musique est terminée.
## Problèmes identifiés
- [ ] Besoin d'une URL de callback pour l'API Suno
- [ ] Gestion des notifications de fin de génération
- [ ] Mise à jour automatique du statut dans Firestore
- [ ] Traitement des erreurs de callback
## Solutions appliquées
### ✅ 1. Création de la fonction callback
- [x] Créé `/functions/src/callback.js`
- [x] Fonction `sunoCallback` avec méthode POST uniquement
- [x] Validation des données reçues
- [x] Gestion des erreurs avec logs émojis
### ✅ 2. Traitement des données callback
- [x] Extraction des paramètres de l'API Suno :
- `id` : Identifiant de la génération
- `status` : Statut de la génération
- `audio_url` : URL du fichier audio
- `video_url` : URL du fichier vidéo
- `image_url` : URL de l'image de couverture
- `lyric` : Paroles générées
- `title` : Titre de la chanson
- `tags` : Style musical
- `duration` : Durée de la chanson
- `error_message` : Message d'erreur si échec
### ✅ 3. Mise à jour Firestore
- [x] Recherche du document correspondant par ID
- [x] Mise à jour des champs :
- `status` : Nouveau statut
- `audioUrl` : URL audio si disponible
- `videoUrl` : URL vidéo si disponible
- `imageUrl` : URL image si disponible
- `lyrics` : Paroles si disponibles
- `title` : Titre si disponible
- `style` : Style si disponible
- `duration` : Durée si disponible
- `errorMessage` : Message d'erreur si échec
- `updatedAt` : Timestamp de mise à jour
### ✅ 4. Configuration de l'URL callback
- [x] Mis à jour `SUNO_CALLBACK_URL` dans `music.js`
- [x] URL : `https://us-central1-musicland-app.cloudfunctions.net/sunoCallback`
- [x] Intégration dans le payload de génération
### ✅ 5. Export de la fonction
- [x] Ajouté l'export dans `functions/index.js`
- [x] Fonction accessible via Firebase Functions
## Structure de la fonction callback
```javascript
exports.sunoCallback = onRequest({
cors: true,
methods: ["POST"],
}, async (req, res) => {
// Validation de la méthode
// Extraction des données
// Recherche du document Firestore
// Mise à jour des données
// Réponse de succès/erreur
});
```
## Gestion des erreurs
- ✅ Validation de la méthode HTTP (POST uniquement)
- ✅ Vérification de la présence de l'ID
- ✅ Vérification de l'existence du document Firestore
- ✅ Gestion des erreurs de mise à jour
- ✅ Logs avec émojis pour faciliter le debugging
## Réponses de la fonction
### Succès (200)
```json
{
"success": true,
"message": "Callback traité avec succès",
"id": "generation_id",
"status": "completed"
}
```
### Erreurs
- **405** : Méthode non autorisée
- **400** : ID manquant
- **404** : Document non trouvé
- **500** : Erreur interne du serveur
## Tests à effectuer
- [ ] Tester la fonction avec un payload de test
- [ ] Vérifier la mise à jour Firestore
- [ ] Tester les cas d'erreur
- [ ] Vérifier les logs
- [ ] Tester l'intégration avec l'API Suno
## Déploiement
- [ ] Déployer les functions : `firebase deploy --only functions`
- [ ] Vérifier l'URL de la fonction déployée
- [ ] Tester la fonction en production
## Notes importantes
- La fonction utilise `onRequest` pour accepter les callbacks HTTP
- CORS activé pour permettre les appels externes
- Seules les requêtes POST sont acceptées
- Les logs utilisent des émojis pour faciliter le debugging
- La fonction met à jour automatiquement le timestamp `updatedAt`
## Fichiers modifiés
-`/functions/src/callback.js` (créé)
-`/functions/src/music.js` (SUNO_CALLBACK_URL mis à jour)
-`/functions/index.js` (export ajouté)
- ✅ `/plans/suno-callback-implementation.md` (ce fichier)
+147
View File
@@ -0,0 +1,147 @@
# Plan - Suivi du statut de génération Suno
## Objectif
Permettre de suivre la progression d'une génération de musique Suno en utilisant le taskId retourné par l'API.
## Problème identifié
- ✅ L'API Suno retourne un taskId après la génération
- ✅ Pas de moyen de suivre la progression de la génération
- ✅ Besoin d'une fonction pour interroger le statut
## Solution implémentée
### ✅ 1. Ajout de la constante API
- [x] Ajouté `SUNO_STATUS_PATH = "/api/v1/query"` dans `music.js`
- [x] Endpoint pour interroger le statut d'une tâche
### ✅ 2. Création de la fonction getSunoStatus
- [x] Nouvelle fonction Cloud Function `getSunoStatus`
- [x] Prend en paramètre le `taskId`
- [x] Interroge l'API Suno pour récupérer le statut
- [x] Retourne les données de progression
### ✅ 3. Export de la fonction
- [x] Ajouté l'export dans `index.js`
- [x] Fonction accessible via `firebase.functions().httpsCallable('getSunoStatus')`
## Structure de la fonction
```javascript
exports.getSunoStatus = onCall({}, async ({ data = {} }) => {
const { taskId } = data;
// Validation du taskId
// Appel à l'API Suno
// Parsing de la réponse
// Retour des données
});
```
## Utilisation côté client
```javascript
// Après avoir généré la musique et récupéré le taskId
const checkStatus = async (taskId) => {
try {
const result = await firebase
.functions()
.httpsCallable('getSunoStatus')({ taskId });
console.log('Statut:', result.data);
return result.data;
} catch (error) {
console.error('Erreur:', error);
}
};
// Polling pour suivre la progression
const pollStatus = async (taskId) => {
const interval = setInterval(async () => {
const status = await checkStatus(taskId);
if (status?.data?.status === 'completed') {
clearInterval(interval);
console.log('🎵 Génération terminée!');
}
}, 5000); // Vérifier toutes les 5 secondes
};
```
## Réponse de la fonction
### Succès
```json
{
"success": true,
"taskId": "aa6920a5a84317c40688b6c4e457c630",
"data": {
"status": "processing", // ou "completed", "failed"
"progress": 75,
"audio_url": "https://...",
"video_url": "https://...",
"image_url": "https://..."
}
}
```
### Erreur
```json
{
"error": "Erreur lors de la vérification du statut: TaskId manquant"
}
```
## Statuts possibles
- `pending` : En attente de traitement
- `processing` : En cours de génération
- `completed` : Génération terminée avec succès
- `failed` : Échec de la génération
- `cancelled` : Génération annulée
## Gestion des erreurs
- ✅ Validation du taskId obligatoire
- ✅ Gestion des erreurs de réseau
- ✅ Parsing sécurisé de la réponse JSON
- ✅ Logs avec émojis pour le debugging
- ✅ Messages d'erreur explicites
## Prochaines étapes
- [ ] Déployer les fonctions : `firebase deploy --only functions`
- [ ] Tester la fonction avec un taskId réel
- [ ] Implémenter le polling côté client
- [ ] Ajouter une interface utilisateur pour afficher la progression
- [ ] Gérer les cas d'erreur côté client
## Exemple d'intégration complète
```javascript
// 1. Générer la musique
const generateResult = await firebase
.functions()
.httpsCallable('music-generateMusic')(musicData);
const taskId = generateResult.data.response.data.taskId;
// 2. Suivre la progression
if (taskId) {
pollStatus(taskId);
} else {
console.error('❌ TaskId manquant');
}
```
## Notes importantes
- ⚠️ Ne pas interroger l'API trop fréquemment (max toutes les 5 secondes)
- ⚠️ Implémenter un timeout pour éviter les boucles infinies
- ⚠️ Gérer les cas où le taskId n'existe plus
- ⚠️ La génération peut prendre 2-3 minutes selon la complexité
## Fichiers modifiés
-`/functions/src/music.js` - Ajout de getSunoStatus
-`/functions/index.js` - Export de la fonction
- ✅ `/plans/suno-status-tracking.md` - Ce fichier de documentation
+10 -9
View File
@@ -11,12 +11,13 @@ import "firebase/compat/functions";
import "firebase/compat/firestore"; import "firebase/compat/firestore";
export const firebaseConfig = { export const firebaseConfig = {
apiKey: "AIzaSyDrD0yGi4U_5zxLjJmypnA7QmKXVeEkhyc", apiKey: "AIzaSyCuHJHdwVN_F-VmUG4Hd7bGiMRqj6rPlLo",
authDomain: "minuitstarter.firebaseapp.com", authDomain: "musicland-d33f9.firebaseapp.com",
projectId: "minuitstarter", projectId: "musicland-d33f9",
storageBucket: "minuitstarter.appspot.com", storageBucket: "musicland-d33f9.firebasestorage.app",
messagingSenderId: "918923582918", messagingSenderId: "305598753437",
appId: "1:918923582918:web:c2ff55f8e19881f249a6c7", appId: "1:305598753437:web:9930f10af59e5cf7f28274",
measurementId: "G-RV4MS1SVTT",
}; };
if (!firebase?.apps?.filter(({ name_ }) => name_ === "[DEFAULT]").length) { if (!firebase?.apps?.filter(({ name_ }) => name_ === "[DEFAULT]").length) {
@@ -26,9 +27,9 @@ if (!firebase?.apps?.filter(({ name_ }) => name_ === "[DEFAULT]").length) {
persistence: getReactNativePersistence(AsyncStorage), persistence: getReactNativePersistence(AsyncStorage),
}); });
// if (__DEV__) { if (__DEV__) {
// firebase.functions().useEmulator("localhost", 5001); firebase.functions().useEmulator("192.168.68.98", 5001);
// } }
console.log("Firebase init"); console.log("Firebase init");
} }
+96
View File
@@ -6,8 +6,90 @@ import GradientButton from "../../components/GradientButton";
import { navigate } from "../../navigation/NavigationService"; import { navigate } from "../../navigation/NavigationService";
import { Routes } from "../../navigation"; import { Routes } from "../../navigation";
import { gutters } from "../../styles"; import { gutters } from "../../styles";
import firebase from "../../config/firebase";
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import { responsiveHeight } from "react-native-responsive-dimensions";
const Studio = () => { const Studio = () => {
const { setIsLoading } = useMinuit();
async function generateMusic() {
try {
await setIsLoading(true);
const { data } = await firebase
.functions()
.httpsCallable("music-generateMusic")({
lyrics: [
{
lyrics:
"Sous le ciel de minuit, une idée prend son envol\nUn éclat dans le noir, comme un nouveau symbole\nDes startups qui rêvent, des projets plein d'ardeur\nMinuit est là pour elles, avec cœur et saveur\nDes écrans qui s'allument, des lignes de code en fête\nL'équipe est au taquet, pas de place pour la défaite\nLe design est notre guide, l'humain notre priorité\nPour bâtir le futur, avec créativité",
type: "couplet",
},
{
lyrics:
"Minuit, oh Minuit, on avance ensemble vers l'infini\nNotre passion digitale, c'est notre plus beau défi\nDes idées qui fusent, l'esprit d'équipe au rendez-vous\nPour que chaque projet, brille de mille feux, c'est tout\nLa joie dans nos cœurs, l'énergie à revendre\nMinuit, c'est la flamme qui nous fait bien comprendre\nQue l'innovation, c'est la clé de demain\nAvec vous, à vos côtés, main dans la main !",
type: "refrain",
},
{
lyrics:
"Du prototype agile, au financement final\nOn vous accompagne, c'est notre idéal\nApplications mobiles, sur mesure et parfaites\nL'optimisation constante, pour des routes bien nettes\nL'accompagnement précis, le sourire sur les visages\nC'est ça notre ADN, notre plus beau message\nLa nuit ne fait que commencer, le travail est un plaisir\nEnsemble, nous allons loin, pour un grand avenir",
type: "couplet",
},
{
lyrics:
"Minuit, oh Minuit, on avance ensemble vers l'infini\nNotre passion digitale, c'est notre plus beau défi\nDes idées qui fusent, l'esprit d'équipe au rendez-vous\nPour que chaque projet, brille de mille feux, c'est tout\nLa joie dans nos cœurs, l'énergie à revendre\nMinuit, c'est la flamme qui nous fait bien comprendre\nQue l'innovation, c'est la clé de demain\nAvec vous, à vos côtés, main dans la main !",
type: "refrain",
},
],
title: "Viens avec nous chez Minuit",
genres: [
"Pop : Musique commerciale destinée au grand public, accrocheuse et mélodique. Domine les charts internationaux avec des artistes ultra-médiatisés.",
"Soul : Voix puissantes, émotion, style afro-américain. Influence pop et le R&B. Mélodieuse, émotionnelle et expressive dérivée du gospel et du R&B. authenticité émotionnelle.",
],
voice:
"Un chœur gospel pour donner une dimension spirituelle et émotionnelle à la chanson.",
instruments: [
"Piano classique",
"Synthétiseur",
"Guitare acoustique",
"Violon",
],
tempo: "Normal",
});
console.log("data", data);
} catch (e) {
console.log(e);
} finally {
await setIsLoading(false);
}
}
async function testSunoStatus() {
try {
await setIsLoading(true);
const { data } = await firebase
.functions()
.httpsCallable("music-getSunoStatus")({
taskId: "1b04b24a9b95a46125a73f25144b74c2",
});
console.log("🎵 Status data:", data);
if (data.success) {
console.log("✅ Fonction getSunoStatus fonctionne correctement");
if (data.data.isTestId) {
console.log("️ TaskId de test détecté - réponse simulée");
}
}
} catch (e) {
console.log("❌ Erreur getSunoStatus:", e.message || e);
if (e.message && e.message.includes("404")) {
console.log("️ Erreur 404 normale pour un taskId de test");
}
} finally {
await setIsLoading(false);
}
}
return ( return (
<Page <Page
headerType="NONE" headerType="NONE"
@@ -18,6 +100,20 @@ const Studio = () => {
}} }}
> >
<View style={{ flex: 1, justifyContent: "flex-end" }}> <View style={{ flex: 1, justifyContent: "flex-end" }}>
<GradientButton
title="Generate Minuit.Music"
onPress={() => generateMusic()}
containerStyle={{
marginBottom: responsiveHeight(2),
}}
/>
<GradientButton
title="Test Suno Status"
onPress={() => testSunoStatus()}
containerStyle={{
marginBottom: responsiveHeight(2),
}}
/>
<GradientButton <GradientButton
title="Commencer" title="Commencer"
onPress={() => navigate(Routes.Compose)} onPress={() => navigate(Routes.Compose)}
+38
View File
@@ -5,8 +5,39 @@ import GradientButton from "../../components/GradientButton";
import { gutters } from "../../styles"; import { gutters } from "../../styles";
import { navigate } from "../../navigation/NavigationService"; import { navigate } from "../../navigation/NavigationService";
import { Routes } from "../../navigation"; import { Routes } from "../../navigation";
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import firebase from "../../config/firebase";
import { responsiveHeight } from "react-native-responsive-dimensions";
const Writing = () => { const Writing = () => {
const { setIsLoading } = useMinuit();
async function generateTestLyrics() {
try {
await setIsLoading(true);
const { data } = await firebase
.functions()
.httpsCallable("lyrics-generateLyrics")({
objective:
"Célébrer lagence Minuit et mettre en avant son expertise digitale, son esprit d’équipe et sa créativité.",
context:
"Lagence Minuit accompagne les startups et entreprises innovantes dans la création de produits digitaux, du prototype à la version de financement, jusqu’à loptimisation et la mise à l’échelle. Spécialisée dans le développement sur-mesure dapplications mobiles, elle valorise lhumain, le design et laccompagnement personnalisé. Esprit nocturne, équipe passionnée.",
emotion:
"La Joie : expose un bonheur profond, l'émerveillement, la gratitude, satisfaction intense, énergie positive.",
style: "Upbeat : Pour une ambiance joyeuse et rythmée.",
audience:
"L’équipe Minuit et ses clients fidèles, startups ambitieuses et partenaires visionnaires.",
structure: ["couplet", "refrain", "couplet", "refrain"],
rhymes: "Avec rimes",
});
console.log("data", data);
} catch (e) {
console.log(e);
} finally {
await setIsLoading(false);
}
}
return ( return (
<Page <Page
headerType="NONE" headerType="NONE"
@@ -16,6 +47,13 @@ const Writing = () => {
}} }}
> >
<View style={{ flex: 1, justifyContent: "flex-end" }}> <View style={{ flex: 1, justifyContent: "flex-end" }}>
<GradientButton
title="Generate Minuit.lyrics"
onPress={() => generateTestLyrics()}
containerStyle={{
marginBottom: responsiveHeight(2),
}}
/>
<GradientButton <GradientButton
title="Commencer" title="Commencer"
onPress={() => navigate(Routes.WritingLyrics)} onPress={() => navigate(Routes.WritingLyrics)}
@@ -1,18 +0,0 @@
{
"applinks": {
"apps": [],
"details": [
{
"appID": "MW8MX2T3ZL.com.minuit.starter",
"paths": [
"/tasks/*"
]
}
]
},
"webcredentials": {
"apps": [
"MW8MX2T3ZL.com.minuit.starter"
]
}
}
-130
View File
@@ -1,130 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
/>
<!-- SEO Meta Tags -->
<meta
name="description"
content="Gérez vos projets de développement efficacement avec minuit.starter"
/>
<meta
name="keywords"
content="minuit.agency, développement, gestion de projets, minuit, bordeaux"
/>
<meta name="author" content="minuit.agency" />
<!-- Apple iTunes Meta Tag -->
<meta name="apple-itunes-app" content="app-id=1661696886" />
<title>minuit.starter - Suivi de projet</title>
<style>
@import url("https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
::-webkit-scrollbar {
display: none;
}
html,
body,
#root {
width: 100%;
-webkit-overflow-scrolling: touch;
margin: 0px;
padding: 0px;
min-height: 100%;
overflow: -moz-scrollbars-none;
scrollbar-width: none;
}
#root {
flex-shrink: 0;
flex-basis: auto;
flex-grow: 1;
display: flex;
flex: 1;
}
input:focus,
textarea:focus,
select:focus,
input:-internal-autofill-selected {
outline: none;
}
input[type="text" i] {
outline-width: 0;
}
html {
scroll-behavior: smooth;
-webkit-text-size-adjust: 100%;
height: calc(100% + env(safe-area-inset-top));
}
body {
display: flex;
overflow-y: auto;
overscroll-behavior-y: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-ms-overflow-style: scrollbar;
}
</style>
</head>
<body>
<noscript>
<form
action=""
style="
background-color: #fff;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
"
>
<div
style="
font-size: 18px;
font-family: Helvetica, sans-serif;
line-height: 24px;
margin: 10%;
width: 80%;
"
>
<p>Oh no! It looks like JavaScript is not enabled in your browser.</p>
<p style="margin: 20px 0">
<button
type="submit"
style="
background-color: #4630eb;
border-radius: 100px;
border: none;
box-shadow: none;
color: #fff;
cursor: pointer;
font-weight: bold;
line-height: 20px;
padding: 6px 16px;
"
>
Reload
</button>
</p>
</div>
</form>
</noscript>
<div id="root"></div>
</body>
</html>
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+2385 -441
View File
File diff suppressed because it is too large Load Diff