diff --git a/.gitignore b/.gitignore index 954fc66..3ffd089 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,9 @@ yarn-error.* # local env files .env*.local +.env # typescript *.tsbuildinfo + +/src/generated/prisma diff --git a/App.tsx b/App.tsx index 8bf740a..a3adcdd 100644 --- a/App.tsx +++ b/App.tsx @@ -1,13 +1,15 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Ionicons } from '@expo/vector-icons'; import { StatusBar } from 'expo-status-bar'; -import { Platform } from 'react-native'; +import { Platform, View, ActivityIndicator } from 'react-native'; import WebWrapper from './src/components/WebWrapper'; +import NetworkTestScreen from './src/components/NetworkTestScreen'; // Import screens +import SignInScreen from './src/screens/SignInScreen'; import OnboardingScreen from './src/screens/OnboardingScreen'; import DiscoveryScreen from './src/screens/DiscoveryScreen'; import MatchesScreen from './src/screens/MatchesScreen'; @@ -17,87 +19,202 @@ import ProfileScreen from './src/screens/ProfileScreen'; const Tab = createBottomTabNavigator(); const queryClient = new QueryClient(); +type AuthState = 'loading' | 'signed-out' | 'needs-onboarding' | 'signed-in'; + +interface User { + id: string; + walletAddress: string; + name: string; + gender: 'male' | 'female'; + age: number; + bio: string; + photos: string[]; + preferredTipAmount: number; + ghostedCount: number; + ghostedByCount: number; + matchCount: number; + createdAt: Date; + updatedAt: Date; +} + export default function App() { + const [authState, setAuthState] = useState('loading'); + const [currentUser, setCurrentUser] = useState(null); + const [onboardingWallet, setOnboardingWallet] = useState(''); + const [showNetworkTest, setShowNetworkTest] = useState(false); + + // Check for existing session on app launch + useEffect(() => { + const checkAuthState = async () => { + try { + // Show network test on mobile platforms first + if (Platform.OS !== 'web') { + setShowNetworkTest(true); + return; + } + + // For demo purposes, start with signed-out state + // In a real app, you'd check for stored session/wallet data + setAuthState('signed-out'); + } catch (error) { + console.error('Error checking auth state:', error); + setAuthState('signed-out'); + } + }; + + checkAuthState(); + }, []); + + const handleSignInSuccess = (user: User) => { + setCurrentUser(user); + setAuthState('signed-in'); + }; + + const handleNeedsOnboarding = (walletAddress: string) => { + setOnboardingWallet(walletAddress); + setAuthState('needs-onboarding'); + }; + + const handleOnboardingComplete = (user: User) => { + setCurrentUser(user); + setAuthState('signed-in'); + }; + + const handleSignOut = () => { + setCurrentUser(null); + setOnboardingWallet(''); + setAuthState('signed-out'); + }; + + const handleNetworkTestClose = () => { + setShowNetworkTest(false); + setAuthState('signed-out'); + }; + + // Show network test modal on mobile + if (showNetworkTest) { + return ( + + + + ); + } + + const renderAuthenticationFlow = () => { + if (authState === 'loading') { + return ( + + + + ); + } + + if (authState === 'signed-out') { + return ( + + ); + } + + if (authState === 'needs-onboarding') { + return ( + + ); + } + + return null; + }; + + const renderMainApp = () => ( + + + ({ + tabBarIcon: ({ focused, color, size }) => { + let iconName: keyof typeof Ionicons.glyphMap; + + if (route.name === 'Discovery') { + iconName = focused ? 'heart' : 'heart-outline'; + } else if (route.name === 'Matches') { + iconName = focused ? 'people' : 'people-outline'; + } else if (route.name === 'Wallet') { + iconName = focused ? 'wallet' : 'wallet-outline'; + } else if (route.name === 'Profile') { + iconName = focused ? 'person' : 'person-outline'; + } else { + iconName = 'help-outline'; + } + + return ; + }, + tabBarActiveTintColor: '#00F90C', + tabBarInactiveTintColor: '#A0A0A0', + tabBarStyle: { + backgroundColor: '#1A1A1A', + borderTopColor: '#333333', + borderTopWidth: 1, + paddingBottom: 5, + paddingTop: 5, + height: 60, + }, + headerStyle: { + backgroundColor: '#0A0A0A', + borderBottomColor: '#333333', + borderBottomWidth: 1, + }, + headerTitleStyle: { + color: '#FFFFFF', + fontWeight: 'bold', + fontFamily: 'Inter', + }, + headerTintColor: '#00F90C', + })} + > + + + + + {() => } + + + + ); + const appContent = ( - - - ({ - tabBarIcon: ({ focused, color, size }) => { - let iconName: keyof typeof Ionicons.glyphMap; - - if (route.name === 'Discovery') { - iconName = focused ? 'heart' : 'heart-outline'; - } else if (route.name === 'Matches') { - iconName = focused ? 'people' : 'people-outline'; - } else if (route.name === 'Wallet') { - iconName = focused ? 'wallet' : 'wallet-outline'; - } else if (route.name === 'Profile') { - iconName = focused ? 'person' : 'person-outline'; - } else { - iconName = 'help-outline'; - } - - return ; - }, - tabBarActiveTintColor: '#00F90C', - tabBarInactiveTintColor: '#A0A0A0', - tabBarStyle: { - backgroundColor: '#1A1A1A', - borderTopColor: '#333333', - borderTopWidth: 1, - paddingBottom: 5, - paddingTop: 5, - height: 60, - }, - headerStyle: { - backgroundColor: '#0A0A0A', - borderBottomColor: '#333333', - borderBottomWidth: 1, - }, - headerTitleStyle: { - color: '#FFFFFF', - fontWeight: 'bold', - fontFamily: 'Inter', - }, - headerTintColor: '#00F90C', - })} - > - - - - - - + {authState === 'signed-in' ? renderMainApp() : renderAuthenticationFlow()} ); diff --git a/package-lock.json b/package-lock.json index e6f5a29..dcd5db0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,18 +10,24 @@ "dependencies": { "@expo/metro-runtime": "~5.0.4", "@expo/vector-icons": "^14.1.0", + "@prisma/client": "^6.13.0", "@react-navigation/bottom-tabs": "^7.4.5", "@react-navigation/native": "^7.1.17", "@react-navigation/stack": "^7.4.5", "@solana/spl-token": "^0.4.13", "@solana/web3.js": "^1.98.4", "@tanstack/react-query": "^5.84.1", + "@types/bcryptjs": "^2.4.6", + "@types/pg": "^8.15.5", + "bcryptjs": "^3.0.2", "buffer": "^6.0.3", "crypto-browserify": "^3.12.1", "expo": "~53.0.20", "expo-status-bar": "~2.2.3", "expo-web-browser": "~14.2.0", "nativewind": "^4.1.23", + "pg": "^8.16.3", + "prisma": "^6.13.0", "react": "19.0.0", "react-dom": "19.0.0", "react-native": "0.79.5", @@ -32,7 +38,8 @@ "react-native-screens": "~4.11.1", "react-native-web": "^0.20.0", "stream-browserify": "^3.0.0", - "tailwindcss": "^3.4.17" + "tailwindcss": "^3.4.17", + "ts-node": "^10.9.2" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -40,6 +47,7 @@ "@types/react": "~19.0.10", "@types/react-dom": "~19.0.0", "autoprefixer": "^10.4.21", + "concurrently": "^9.2.0", "postcss": "^8.5.6", "typescript": "~5.8.3" } @@ -1544,6 +1552,28 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -2518,6 +2548,85 @@ "node": ">=14" } }, + "node_modules/@prisma/client": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.13.0.tgz", + "integrity": "sha512-8m2+I3dQovkV8CkDMluiwEV1TxV9EXdT6xaCz39O6jYw7mkf5gwfmi+cL4LJsEPwz5tG7sreBwkRpEMJedGYUQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.13.0.tgz", + "integrity": "sha512-OYMM+pcrvj/NqNWCGESSxVG3O7kX6oWuGyvufTUNnDw740KIQvNyA4v0eILgkpuwsKIDU36beZCkUtIt0naTog==", + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.16.12", + "read-package-up": "11.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.13.0.tgz", + "integrity": "sha512-um+9pfKJW0ihmM83id9FXGi5qEbVJ0Vxi1Gm0xpYsjwUBnw6s2LdPBbrsG9QXRX46K4CLWCTNvskXBup4i9hlw==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.13.0.tgz", + "integrity": "sha512-D+1B79LFvtWA0KTt8ALekQ6A/glB9w10ETknH5Y9g1k2NYYQOQy93ffiuqLn3Pl6IPJG3EsK/YMROKEaq8KBrA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.13.0", + "@prisma/engines-version": "6.13.0-35.361e86d0ea4987e9f53a565309b3eed797a6bcbd", + "@prisma/fetch-engine": "6.13.0", + "@prisma/get-platform": "6.13.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.13.0-35.361e86d0ea4987e9f53a565309b3eed797a6bcbd", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.13.0-35.361e86d0ea4987e9f53a565309b3eed797a6bcbd.tgz", + "integrity": "sha512-MpPyKSzBX7P/ZY9odp9TSegnS/yH3CSbchQE9f0yBg3l2QyN59I6vGXcoYcqKC9VTniS1s18AMmhyr1OWavjHg==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.13.0.tgz", + "integrity": "sha512-grmmq+4FeFKmaaytA8Ozc2+Tf3BC8xn/DVJos6LL022mfRlMZYjT3hZM0/xG7+5fO95zFG9CkDUs0m1S2rXs5Q==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.13.0", + "@prisma/engines-version": "6.13.0-35.361e86d0ea4987e9f53a565309b3eed797a6bcbd", + "@prisma/get-platform": "6.13.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.13.0.tgz", + "integrity": "sha512-Nii2pX50fY4QKKxQwm7/vvqT6Ku8yYJLZAFX4e2vzHwRdMqjugcOG5hOSLjxqoXb0cvOspV70TOhMzrw8kqAnw==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.13.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.79.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.5.tgz", @@ -3239,6 +3348,12 @@ } } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -3284,6 +3399,30 @@ "node": ">=10.13.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3325,6 +3464,12 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "license": "MIT" + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -3548,6 +3693,23 @@ "@types/node": "*" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "license": "MIT" + }, + "node_modules/@types/pg": { + "version": "8.15.5", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz", + "integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -3933,6 +4095,18 @@ "acorn": "^8.14.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -4520,6 +4694,15 @@ "dev": true, "license": "MIT" }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -4979,6 +5162,83 @@ "node": ">= 0.8" } }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/c12/node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -5252,6 +5512,15 @@ "node": ">= 0.10" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -5517,6 +5786,54 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concurrently": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz", + "integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", @@ -5557,6 +5874,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -5791,6 +6117,12 @@ "sha.js": "^2.4.8" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, "node_modules/cross-fetch": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", @@ -6306,6 +6638,15 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -6357,6 +6698,12 @@ "node": ">=8" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, "node_modules/del": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", @@ -6517,6 +6864,12 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -6552,6 +6905,15 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -6734,6 +7096,16 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.16.12", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz", + "integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.197", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.197.tgz", @@ -7638,6 +8010,12 @@ "node": ">= 0.8" } }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, "node_modules/eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", @@ -7646,6 +8024,28 @@ "node": "> 0.1.90" } }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7864,6 +8264,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/find-yarn-workspace-root": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", @@ -8117,6 +8529,23 @@ "node": ">=6" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -8748,6 +9177,18 @@ "node": ">=0.8.19" } }, + "node_modules/index-to-position": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", + "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -9927,6 +10368,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -10717,6 +11164,12 @@ "is-stream": "^1.0.1" } }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -10761,6 +11214,32 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -10852,6 +11331,25 @@ "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", "license": "MIT" }, + "node_modules/nypm": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz", + "integrity": "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.2.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, "node_modules/ob1": { "version": "0.82.5", "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.82.5.tgz", @@ -10902,6 +11400,12 @@ "dev": true, "license": "MIT" }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -11290,6 +11794,12 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, "node_modules/pbkdf2": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", @@ -11338,6 +11848,101 @@ "inherits": "^2.0.1" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -11466,6 +12071,17 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz", + "integrity": "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -12164,6 +12780,45 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -12213,6 +12868,31 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prisma": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.13.0.tgz", + "integrity": "sha512-dfzORf0AbcEyyzxuv2lEwG8g+WRGF/qDQTpHf/6JoHsyF5MyzCEZwClVaEmw3WXcobgadosOboKUgQU0kFs9kw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.13.0", + "@prisma/engines": "6.13.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/proc-log": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", @@ -12322,6 +13002,22 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qrcode-terminal": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz", @@ -12465,6 +13161,16 @@ "rc": "cli.js" } }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", @@ -12941,6 +13647,83 @@ "pify": "^2.3.0" } }, + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-package-up/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13383,6 +14166,16 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -14047,6 +14840,38 @@ "node": ">=0.10.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "license": "CC0-1.0" + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -14088,6 +14913,15 @@ "node": ">=6" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -14870,6 +15704,12 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -14923,12 +15763,71 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -15076,6 +15975,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -15221,6 +16132,22 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/validate-npm-package-name": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", @@ -16001,6 +16928,15 @@ "node": ">=8.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -16087,6 +17023,15 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 6b9e0bb..f5f251e 100644 --- a/package.json +++ b/package.json @@ -9,23 +9,35 @@ "web": "expo start --web", "dev": "expo start --web", "build": "expo export --platform web", - "preview": "expo start --web --no-dev --minify" + "preview": "expo start --web --no-dev --minify", + "seed": "node scripts/seed.js", + "db:push": "npx prisma db push", + "db:generate": "npx prisma generate", + "dev:full": "concurrently \"cd server && npm start\" \"npm start\"", + "server": "cd server && node index.js", + "start:full": "start-full-stack.bat" }, "dependencies": { "@expo/metro-runtime": "~5.0.4", "@expo/vector-icons": "^14.1.0", + "@prisma/client": "^6.13.0", "@react-navigation/bottom-tabs": "^7.4.5", "@react-navigation/native": "^7.1.17", "@react-navigation/stack": "^7.4.5", "@solana/spl-token": "^0.4.13", "@solana/web3.js": "^1.98.4", "@tanstack/react-query": "^5.84.1", + "@types/bcryptjs": "^2.4.6", + "@types/pg": "^8.15.5", + "bcryptjs": "^3.0.2", "buffer": "^6.0.3", "crypto-browserify": "^3.12.1", "expo": "~53.0.20", "expo-status-bar": "~2.2.3", "expo-web-browser": "~14.2.0", "nativewind": "^4.1.23", + "pg": "^8.16.3", + "prisma": "^6.13.0", "react": "19.0.0", "react-dom": "19.0.0", "react-native": "0.79.5", @@ -36,7 +48,8 @@ "react-native-screens": "~4.11.1", "react-native-web": "^0.20.0", "stream-browserify": "^3.0.0", - "tailwindcss": "^3.4.17" + "tailwindcss": "^3.4.17", + "ts-node": "^10.9.2" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -44,6 +57,7 @@ "@types/react": "~19.0.10", "@types/react-dom": "~19.0.0", "autoprefixer": "^10.4.21", + "concurrently": "^9.2.0", "postcss": "^8.5.6", "typescript": "~5.8.3" }, diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..ce4d2dd --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,132 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" + output = "../src/generated/prisma" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id String @id @default(cuid()) + walletAddress String @unique + gender Gender + name String + age Int + bio String + photos String[] + preferredTipAmount Int @default(3) // 1, 2, 3, 4, or 5 USDC + ghostedCount Int @default(0) + ghostedByCount Int @default(0) + matchCount Int @default(0) + isOnline Boolean @default(false) + lastActive DateTime @default(now()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relationships + sentMatches Match[] @relation("SenderMatches") + receivedMatches Match[] @relation("ReceiverMatches") + transactions Transaction[] + + @@map("users") +} + +model Match { + id String @id @default(cuid()) + senderId String + receiverId String + tipAmount Int + status MatchStatus @default(PENDING) + transactionHash String + dateScheduled DateTime? + dateConfirmed Boolean @default(false) + createdAt DateTime @default(now()) + expiresAt DateTime + ghostedAt DateTime? + acceptedAt DateTime? + rejectedAt DateTime? + + // Relationships + sender User @relation("SenderMatches", fields: [senderId], references: [id]) + receiver User @relation("ReceiverMatches", fields: [receiverId], references: [id]) + transactions Transaction[] + + @@unique([senderId, receiverId]) + @@map("matches") +} + +model Transaction { + id String @id @default(cuid()) + walletAddress String + type TransactionType + amount Int // Amount in USDC cents (multiply by 100) + transactionHash String + matchId String? + timestamp DateTime @default(now()) + status TransactionStatus @default(PENDING) + + // Relationships + user User @relation(fields: [walletAddress], references: [walletAddress]) + match Match? @relation(fields: [matchId], references: [id]) + + @@map("transactions") +} + +model EscrowTransaction { + id String @id @default(cuid()) + matchId String @unique + senderAddress String + receiverAddress String + amount Int // Amount in USDC cents + escrowAddress String // Temporary wallet holding the funds + status EscrowStatus @default(HELD) + createdAt DateTime @default(now()) + releasedAt DateTime? + refundedAt DateTime? + + @@map("escrow_transactions") +} + +enum Gender { + MALE + FEMALE +} + +enum MatchStatus { + PENDING + ACCEPTED + REJECTED + GHOSTED + REFUNDED + EXPIRED + DATE_CONFIRMED +} + +enum TransactionStatus { + PENDING + CONFIRMED + FAILED +} + +enum TransactionType { + TIP_SENT + TIP_RECEIVED + REFUND + GHOST_FORFEIT + GHOSTED + DATE_PAYMENT +} + +enum EscrowStatus { + HELD + RELEASED + REFUNDED +} diff --git a/scripts/get-ip.js b/scripts/get-ip.js new file mode 100644 index 0000000..1fb3454 --- /dev/null +++ b/scripts/get-ip.js @@ -0,0 +1,30 @@ +const os = require('os'); + +// Function to get local IP address +function getLocalIP() { + const interfaces = os.networkInterfaces(); + + for (const name in interfaces) { + const iface = interfaces[name]; + + for (const alias of iface) { + if (alias.family === 'IPv4' && !alias.internal) { + // Skip Docker and virtual interfaces + if (!name.toLowerCase().includes('docker') && + !name.toLowerCase().includes('vbox') && + !name.toLowerCase().includes('vmware')) { + return alias.address; + } + } + } + } + + return 'localhost'; +} + +const localIP = getLocalIP(); +console.log(`Your local IP address is: ${localIP}`); +console.log(`Update this in src/constants/network.ts if using Expo Go on mobile`); +console.log(`API will be available at: http://${localIP}:3001`); + +module.exports = { getLocalIP }; diff --git a/scripts/seed.js b/scripts/seed.js new file mode 100644 index 0000000..36a3fdd --- /dev/null +++ b/scripts/seed.js @@ -0,0 +1,109 @@ +const { PrismaClient } = require('../src/generated/prisma'); + +const prisma = new PrismaClient(); + +async function seedDatabase() { + try { + console.log('🌱 Starting database seed...'); + + // Check if users already exist + const existingUsers = await prisma.user.count(); + if (existingUsers > 0) { + console.log('Database already seeded'); + return; + } + + // Create sample sugar babies (females) + const sugarBabies = [ + { + walletAddress: 'demo-wallet-1', + gender: 'FEMALE', + name: 'Sophia', + age: 26, + bio: 'Adventure seeker & coffee enthusiast ā˜•ļø Love hiking, photography, and spontaneous road trips. Looking for someone to explore life\'s beautiful moments with! 🌟', + photos: ['https://images.unsplash.com/photo-1494790108755-2616b612b786?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 3, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-2', + gender: 'FEMALE', + name: 'Emma', + age: 24, + bio: 'Yoga instructor & wellness advocate šŸ§˜ā€ā™€ļø Passionate about healthy living, meditation, and creating meaningful connections. Let\'s grow together! ✨', + photos: ['https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 5, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-3', + gender: 'FEMALE', + name: 'Isabella', + age: 27, + bio: 'Creative soul & art lover šŸŽØ Dog mom to the cutest golden retriever. Love museums, indie music, and deep conversations over wine šŸ·', + photos: ['https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 2, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-4', + gender: 'FEMALE', + name: 'Olivia', + age: 25, + bio: 'Tech enthusiast & fitness lover šŸ’Ŗ Startup founder by day, gym rat by night. Looking for someone who shares my passion for innovation and health! šŸš€', + photos: ['https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 4, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-5', + gender: 'FEMALE', + name: 'Ava', + age: 23, + bio: 'Foodie & travel blogger šŸŒ Always on the hunt for the best restaurants and hidden gems. Let\'s create memories over amazing food! šŸ•āœˆļø', + photos: ['https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 3, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-6', + gender: 'FEMALE', + name: 'Mia', + age: 28, + bio: 'Bookworm & nature lover šŸ“ššŸŒæ Hiking trails, cozy cafes, and deep conversations are my happy places. Looking for someone to share adventures with! šŸ”ļø', + photos: ['https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 2, + isOnline: true, + }, + ]; + + // Create demo sugar daddy + const sugarDaddy = { + walletAddress: 'demo-sugar-daddy-1', + gender: 'MALE', + name: 'Alexander', + age: 35, + bio: 'Successful entrepreneur looking for meaningful connections. Love fine dining, travel, and spoiling the right person. Let\'s create beautiful memories together! šŸ’Ž', + photos: ['https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 5, + isOnline: true, + }; + + // Create all users + await prisma.user.createMany({ + data: [...sugarBabies, sugarDaddy], + }); + + console.log(`āœ… Successfully seeded ${sugarBabies.length + 1} users to the database!`); + + } catch (error) { + console.error('āŒ Error seeding database:', error); + throw error; + } finally { + await prisma.$disconnect(); + } +} + +seedDatabase() + .then(() => process.exit(0)) + .catch(() => process.exit(1)); diff --git a/scripts/seed.ts b/scripts/seed.ts new file mode 100644 index 0000000..388d1fb --- /dev/null +++ b/scripts/seed.ts @@ -0,0 +1,15 @@ +import { databaseService } from '../src/services/database'; + +async function seedDatabase() { + try { + console.log('🌱 Starting database seed...'); + await databaseService.seedDatabase(); + console.log('āœ… Database seeded successfully!'); + process.exit(0); + } catch (error) { + console.error('āŒ Error seeding database:', error); + process.exit(1); + } +} + +seedDatabase(); diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..9445cbc --- /dev/null +++ b/server/index.js @@ -0,0 +1,817 @@ +const express = require('express'); +const cors = require('cors'); +const { PrismaClient } = require('@prisma/client'); +const bcrypt = require('bcryptjs'); +const os = require('os'); +require('dotenv').config({ path: '../.env' }); + +const app = express(); +const prisma = new PrismaClient(); + +// Function to get local IP address +function getLocalIP() { + const interfaces = os.networkInterfaces(); + + for (const name in interfaces) { + const iface = interfaces[name]; + + for (const alias of iface) { + if (alias.family === 'IPv4' && !alias.internal) { + // Skip Docker and virtual interfaces + if (!name.toLowerCase().includes('docker') && + !name.toLowerCase().includes('vbox') && + !name.toLowerCase().includes('vmware')) { + return alias.address; + } + } + } + } + + return 'localhost'; +} + +// Middleware +app.use(cors({ + origin: [ + 'http://localhost:8081', + 'http://localhost:8082', // Added for React Native on port 8082 + 'http://localhost:3000', + `http://${getLocalIP()}:8081`, // Dynamic local IP + `http://${getLocalIP()}:8082`, // Dynamic local IP for port 8082 + /^http:\/\/192\.168\.\d+\.\d+:808[12]$/, // Allow any 192.168.x.x:8081 or 8082 + /^http:\/\/10\.0\.\d+\.\d+:808[12]$/, // Allow any 10.0.x.x:8081 or 8082 + /^http:\/\/172\.16\.\d+\.\d+:808[12]$/, // Allow any 172.16.x.x:8081 or 8082 + /^http:\/\/172\.25\.\d+\.\d+:808[12]$/, // Allow any 172.25.x.x:8081 or 8082 + ], + credentials: true +})); +app.use(express.json()); + +// Health check +app.get('/health', (req, res) => { + res.json({ status: 'ok', message: 'Solmate API is running' }); +}); + +// Authentication routes +app.post('/api/auth/signin', async (req, res) => { + try { + const { walletAddress } = req.body; + + if (!walletAddress) { + return res.status(400).json({ error: 'Wallet address is required' }); + } + + const user = await prisma.user.findUnique({ + where: { walletAddress }, + }); + + if (!user) { + return res.json({ user: null, needsOnboarding: true }); + } + + // Update last active + await prisma.user.update({ + where: { id: user.id }, + data: { lastActive: new Date() }, + }); + + res.json({ + user: { + ...user, + gender: user.gender.toLowerCase() + }, + needsOnboarding: false + }); + } catch (error) { + console.error('Sign in error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.post('/api/auth/onboard', async (req, res) => { + try { + const { walletAddress, name, age, gender, bio, photos, preferredTipAmount } = req.body; + + if (!walletAddress || !name || !age || !gender) { + return res.status(400).json({ error: 'Missing required fields' }); + } + + const user = await prisma.user.create({ + data: { + walletAddress, + name, + age, + gender: gender.toUpperCase(), + bio: bio || '', + photos: photos || [], + preferredTipAmount: preferredTipAmount || 3, + isOnline: true, + lastActive: new Date(), + }, + }); + + res.json({ + user: { + ...user, + gender: user.gender.toLowerCase() + } + }); + } catch (error) { + console.error('Onboard error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// User routes +app.get('/api/users/discovery/:walletAddress', async (req, res) => { + try { + const { walletAddress } = req.params; + const limit = parseInt(req.query.limit) || 10; + + const currentUser = await prisma.user.findUnique({ + where: { walletAddress }, + }); + + if (!currentUser) { + return res.status(404).json({ error: 'User not found' }); + } + + // Get users already matched or swiped + const existingMatches = await prisma.match.findMany({ + where: { + OR: [ + { senderId: currentUser.id }, + { receiverId: currentUser.id }, + ], + }, + select: { senderId: true, receiverId: true }, + }); + + const excludedUserIds = new Set([currentUser.id]); + existingMatches.forEach(match => { + excludedUserIds.add(match.senderId); + excludedUserIds.add(match.receiverId); + }); + + const users = await prisma.user.findMany({ + where: { + id: { notIn: Array.from(excludedUserIds) }, + gender: currentUser.gender === 'MALE' ? 'FEMALE' : 'MALE', + isOnline: true, + }, + orderBy: { + lastActive: 'desc', + }, + take: limit, + }); + + res.json(users.map(user => ({ + ...user, + gender: user.gender.toLowerCase() + }))); + } catch (error) { + console.error('Discovery error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Match routes +app.post('/api/matches', async (req, res) => { + try { + const { senderWallet, receiverWallet, tipAmount, transactionHash } = req.body; + + if (!senderWallet || !receiverWallet || !tipAmount || !transactionHash) { + return res.status(400).json({ error: 'Missing required fields' }); + } + + const sender = await prisma.user.findUnique({ + where: { walletAddress: senderWallet }, + }); + const receiver = await prisma.user.findUnique({ + where: { walletAddress: receiverWallet }, + }); + + if (!sender || !receiver) { + return res.status(404).json({ error: 'User not found' }); + } + + // Check if a match already exists between these users + const existingMatch = await prisma.match.findFirst({ + where: { + OR: [ + { + senderId: sender.id, + receiverId: receiver.id, + }, + { + senderId: receiver.id, + receiverId: sender.id, + }, + ], + }, + include: { + sender: true, + receiver: true, + }, + }); + + if (existingMatch) { + return res.status(409).json({ + error: 'Match already exists between these users', + existingMatch + }); + } + + const expiresAt = new Date(); + expiresAt.setHours(expiresAt.getHours() + 24); + + const match = await prisma.match.create({ + data: { + senderId: sender.id, + receiverId: receiver.id, + tipAmount, + transactionHash, + expiresAt, + status: 'PENDING', + }, + include: { + sender: true, + receiver: true, + }, + }); + + // Create transaction record + await prisma.transaction.create({ + data: { + walletAddress: senderWallet, + type: 'TIP_SENT', + amount: tipAmount * 100, + transactionHash, + matchId: match.id, + status: 'CONFIRMED', + }, + }); + + res.json(match); + } catch (error) { + console.error('Create match error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.get('/api/matches/:walletAddress', async (req, res) => { + try { + const { walletAddress } = req.params; + + const user = await prisma.user.findUnique({ + where: { walletAddress }, + }); + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + const matches = await prisma.match.findMany({ + where: { + OR: [ + { senderId: user.id }, + { receiverId: user.id }, + ], + }, + include: { + sender: true, + receiver: true, + transactions: true, + }, + orderBy: { + createdAt: 'desc', + }, + }); + + res.json(matches); + } catch (error) { + console.error('Get matches error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.patch('/api/matches/:matchId/accept', async (req, res) => { + try { + const { matchId } = req.params; + + const match = await prisma.match.update({ + where: { id: matchId }, + data: { + status: 'ACCEPTED', + acceptedAt: new Date(), + }, + include: { + sender: true, + receiver: true, + }, + }); + + // Update match counts + await Promise.all([ + prisma.user.update({ + where: { id: match.senderId }, + data: { matchCount: { increment: 1 } }, + }), + prisma.user.update({ + where: { id: match.receiverId }, + data: { matchCount: { increment: 1 } }, + }), + ]); + + res.json(match); + } catch (error) { + console.error('Accept match error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.patch('/api/matches/:matchId/reject', async (req, res) => { + try { + const { matchId } = req.params; + + const match = await prisma.match.update({ + where: { id: matchId }, + data: { + status: 'REJECTED', + rejectedAt: new Date(), + }, + include: { + sender: true, + receiver: true, + }, + }); + + res.json(match); + } catch (error) { + console.error('Reject match error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Seed route +app.post('/api/seed', async (req, res) => { + try { + // Clear existing data + await prisma.transaction.deleteMany(); + await prisma.match.deleteMany(); + await prisma.user.deleteMany(); + + // Create users with more varied and realistic data + const users = await prisma.user.createMany({ + data: [ + // Sugar Daddies + { + walletAddress: 'demo-sugar-daddy-1', + name: 'Alex', + age: 35, + gender: 'MALE', + bio: 'Tech entrepreneur & crypto enthusiast šŸš€ Building the future, one investment at a time', + photos: ['https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 5, + isOnline: true, + lastActive: new Date(), + matchCount: 0, + ghostedCount: 0, + ghostedByCount: 0, + }, + { + walletAddress: 'demo-sugar-daddy-2', + name: 'Marcus', + age: 42, + gender: 'MALE', + bio: 'Investment banker & wine collector šŸ· Love fine dining and meaningful conversations', + photos: ['https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 8, + isOnline: true, + lastActive: new Date(Date.now() - 30 * 60 * 1000), // 30 mins ago + matchCount: 0, + ghostedCount: 1, + ghostedByCount: 0, + }, + { + walletAddress: 'demo-sugar-daddy-3', + name: 'David', + age: 38, + gender: 'MALE', + bio: 'Real estate mogul šŸ¢ Passionate about art, travel, and spoiling the right person', + photos: ['https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 10, + isOnline: false, + lastActive: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago + matchCount: 0, + ghostedCount: 0, + ghostedByCount: 2, + }, + + // Sugar Babies + { + walletAddress: 'demo-sugar-baby-1', + name: 'Sophia', + age: 26, + gender: 'FEMALE', + bio: 'Adventure seeker & coffee enthusiast ā˜•ļø Med student by day, explorer by heart', + photos: ['https://images.unsplash.com/photo-1494790108755-2616b612b786?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 3, + isOnline: true, + lastActive: new Date(), + matchCount: 0, + ghostedCount: 0, + ghostedByCount: 0, + }, + { + walletAddress: 'demo-sugar-baby-2', + name: 'Emma', + age: 24, + gender: 'FEMALE', + bio: 'Yoga instructor & wellness advocate šŸ§˜ā€ā™€ļø Seeking someone who values mindfulness and growth', + photos: ['https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 5, + isOnline: true, + lastActive: new Date(Date.now() - 15 * 60 * 1000), // 15 mins ago + matchCount: 0, + ghostedCount: 0, + ghostedByCount: 1, + }, + { + walletAddress: 'demo-sugar-baby-3', + name: 'Isabella', + age: 27, + gender: 'FEMALE', + bio: 'Creative soul & art lover šŸŽØ Graphic designer looking for inspiration and genuine connection', + photos: ['https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 2, + isOnline: true, + lastActive: new Date(Date.now() - 5 * 60 * 1000), // 5 mins ago + matchCount: 0, + ghostedCount: 2, + ghostedByCount: 0, + }, + { + walletAddress: 'demo-sugar-baby-4', + name: 'Olivia', + age: 25, + gender: 'FEMALE', + bio: 'Tech enthusiast & fitness lover šŸ’Ŗ Software engineer who loves hiking and trying new cuisines', + photos: ['https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 4, + isOnline: true, + lastActive: new Date(), + matchCount: 0, + ghostedCount: 0, + ghostedByCount: 0, + }, + { + walletAddress: 'demo-sugar-baby-5', + name: 'Ava', + age: 23, + gender: 'FEMALE', + bio: 'Fashion designer & world traveler āœˆļø Currently building my own brand while studying in Paris', + photos: ['https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 3, + isOnline: false, + lastActive: new Date(Date.now() - 4 * 60 * 60 * 1000), // 4 hours ago + matchCount: 0, + ghostedCount: 1, + ghostedByCount: 0, + }, + { + walletAddress: 'demo-sugar-baby-6', + name: 'Mia', + age: 28, + gender: 'FEMALE', + bio: 'Chef & foodie extraordinaire šŸ‘Øā€šŸ³ Opening my own restaurant soon, love sharing culinary adventures', + photos: ['https://images.unsplash.com/photo-1529626455594-4ff0802cfb7e?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 4, + isOnline: true, + lastActive: new Date(Date.now() - 1 * 60 * 60 * 1000), // 1 hour ago + matchCount: 0, + ghostedCount: 0, + ghostedByCount: 1, + }, + { + walletAddress: 'demo-sugar-baby-7', + name: 'Charlotte', + age: 22, + gender: 'FEMALE', + bio: 'Psychology student & aspiring therapist šŸ’ Fascinated by human connections and deep conversations', + photos: ['https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 2, + isOnline: true, + lastActive: new Date(), + matchCount: 0, + ghostedCount: 0, + ghostedByCount: 0, + }, + { + walletAddress: 'demo-sugar-baby-8', + name: 'Luna', + age: 26, + gender: 'FEMALE', + bio: 'Professional dancer & choreographer šŸ’ƒ Bringing art to life through movement and expression', + photos: ['https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 6, + isOnline: false, + lastActive: new Date(Date.now() - 6 * 60 * 60 * 1000), // 6 hours ago + matchCount: 0, + ghostedCount: 0, + ghostedByCount: 0, + }, + ], + }); + + // Get created users to create comprehensive matches and transactions + const createdUsers = await prisma.user.findMany(); + const alex = createdUsers.find(u => u.walletAddress === 'demo-sugar-daddy-1'); + const marcus = createdUsers.find(u => u.walletAddress === 'demo-sugar-daddy-2'); + const david = createdUsers.find(u => u.walletAddress === 'demo-sugar-daddy-3'); + + const sophia = createdUsers.find(u => u.walletAddress === 'demo-sugar-baby-1'); + const emma = createdUsers.find(u => u.walletAddress === 'demo-sugar-baby-2'); + const isabella = createdUsers.find(u => u.walletAddress === 'demo-sugar-baby-3'); + const olivia = createdUsers.find(u => u.walletAddress === 'demo-sugar-baby-4'); + const ava = createdUsers.find(u => u.walletAddress === 'demo-sugar-baby-5'); + const mia = createdUsers.find(u => u.walletAddress === 'demo-sugar-baby-6'); + const charlotte = createdUsers.find(u => u.walletAddress === 'demo-sugar-baby-7'); + const luna = createdUsers.find(u => u.walletAddress === 'demo-sugar-baby-8'); + + const matches = []; + const transactions = []; + + if (alex && marcus && david && sophia && emma && isabella && olivia && ava && mia && charlotte && luna) { + // Alex's matches + // 1. Accepted match with Sophia (most recent success) + const match1 = await prisma.match.create({ + data: { + senderId: alex.id, + receiverId: sophia.id, + tipAmount: 5, + transactionHash: '0x1a2b3c4d5e6f7890abcdef1234567890alexsophia', + status: 'ACCEPTED', + acceptedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000), // 2 days ago + expiresAt: new Date(Date.now() + 22 * 60 * 60 * 1000), // 22 hours from now + } + }); + matches.push(match1); + + // Transaction for Alex -> Sophia + transactions.push({ + walletAddress: alex.walletAddress, + type: 'TIP_SENT', + amount: 500, // $5 in cents + transactionHash: '0x1a2b3c4d5e6f7890abcdef1234567890alexsophia', + matchId: match1.id, + status: 'CONFIRMED', + }); + + // 2. Pending match with Emma + const match2 = await prisma.match.create({ + data: { + senderId: alex.id, + receiverId: emma.id, + tipAmount: 3, + transactionHash: '0x2b3c4d5e6f7890abcdef1234567890alexemma', + status: 'PENDING', + expiresAt: new Date(Date.now() + 20 * 60 * 60 * 1000), // 20 hours from now + createdAt: new Date(Date.now() - 6 * 60 * 60 * 1000), // 6 hours ago + } + }); + matches.push(match2); + + transactions.push({ + walletAddress: alex.walletAddress, + type: 'TIP_SENT', + amount: 300, + transactionHash: '0x2b3c4d5e6f7890abcdef1234567890alexemma', + matchId: match2.id, + status: 'CONFIRMED', + }); + + // 3. Rejected match with Isabella + const match3 = await prisma.match.create({ + data: { + senderId: alex.id, + receiverId: isabella.id, + tipAmount: 2, + transactionHash: '0x3c4d5e6f7890abcdef1234567890alexisabella', + status: 'REJECTED', + rejectedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000), // 1 day ago + expiresAt: new Date(Date.now() + 15 * 60 * 60 * 1000), // 15 hours from now + createdAt: new Date(Date.now() - 25 * 60 * 60 * 1000), // 25 hours ago + } + }); + matches.push(match3); + + transactions.push({ + walletAddress: alex.walletAddress, + type: 'TIP_SENT', + amount: 200, + transactionHash: '0x3c4d5e6f7890abcdef1234567890alexisabella', + matchId: match3.id, + status: 'CONFIRMED', + }); + + // Marcus's matches + // 1. Accepted match with Olivia + const match4 = await prisma.match.create({ + data: { + senderId: marcus.id, + receiverId: olivia.id, + tipAmount: 8, + transactionHash: '0x4d5e6f7890abcdef1234567890marcusolivia', + status: 'ACCEPTED', + acceptedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000), // 3 days ago + expiresAt: new Date(Date.now() + 21 * 60 * 60 * 1000), // 21 hours from now + createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000), // 3 days ago + } + }); + matches.push(match4); + + transactions.push({ + walletAddress: marcus.walletAddress, + type: 'TIP_SENT', + amount: 800, + transactionHash: '0x4d5e6f7890abcdef1234567890marcusolivia', + matchId: match4.id, + status: 'CONFIRMED', + }); + + // 2. Ghosted match with Ava + const match5 = await prisma.match.create({ + data: { + senderId: marcus.id, + receiverId: ava.id, + tipAmount: 6, + transactionHash: '0x5e6f7890abcdef1234567890marcusava', + status: 'GHOSTED', + expiresAt: new Date(Date.now() - 12 * 60 * 60 * 1000), // Expired 12 hours ago + createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000), // 5 days ago + } + }); + matches.push(match5); + + transactions.push({ + walletAddress: marcus.walletAddress, + type: 'TIP_SENT', + amount: 600, + transactionHash: '0x5e6f7890abcdef1234567890marcusava', + matchId: match5.id, + status: 'CONFIRMED', + }); + + // David's matches + // 1. Pending match with Charlotte + const match6 = await prisma.match.create({ + data: { + senderId: david.id, + receiverId: charlotte.id, + tipAmount: 10, + transactionHash: '0x6f7890abcdef1234567890davidcharlotte', + status: 'PENDING', + expiresAt: new Date(Date.now() + 18 * 60 * 60 * 1000), // 18 hours from now + createdAt: new Date(Date.now() - 4 * 60 * 60 * 1000), // 4 hours ago + } + }); + matches.push(match6); + + transactions.push({ + walletAddress: david.walletAddress, + type: 'TIP_SENT', + amount: 1000, + transactionHash: '0x6f7890abcdef1234567890davidcharlotte', + matchId: match6.id, + status: 'CONFIRMED', + }); + + // 2. Rejected by Luna + const match7 = await prisma.match.create({ + data: { + senderId: david.id, + receiverId: luna.id, + tipAmount: 12, + transactionHash: '0x7890abcdef1234567890davidluna', + status: 'REJECTED', + rejectedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000), // 2 days ago + expiresAt: new Date(Date.now() + 10 * 60 * 60 * 1000), // 10 hours from now + createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000), // 2 days ago + } + }); + matches.push(match7); + + transactions.push({ + walletAddress: david.walletAddress, + type: 'TIP_SENT', + amount: 1200, + transactionHash: '0x7890abcdef1234567890davidluna', + matchId: match7.id, + status: 'CONFIRMED', + }); + + // 3. Ghosted by Mia + const match8 = await prisma.match.create({ + data: { + senderId: david.id, + receiverId: mia.id, + tipAmount: 8, + transactionHash: '0x890abcdef1234567890davidmia', + status: 'GHOSTED', + expiresAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // Expired 2 hours ago + createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago + } + }); + matches.push(match8); + + transactions.push({ + walletAddress: david.walletAddress, + type: 'TIP_SENT', + amount: 800, + transactionHash: '0x890abcdef1234567890davidmia', + matchId: match8.id, + status: 'CONFIRMED', + }); + + // Create all transactions + for (const txData of transactions) { + await prisma.transaction.create({ data: txData }); + } + + // Update match counts (only count accepted matches) + await prisma.user.update({ + where: { id: alex.id }, + data: { matchCount: 1 } // Sophia + }); + + await prisma.user.update({ + where: { id: sophia.id }, + data: { matchCount: 1 } // Alex + }); + + await prisma.user.update({ + where: { id: marcus.id }, + data: { matchCount: 1, ghostedCount: 1 } // Olivia accepted, Ava ghosted him + }); + + await prisma.user.update({ + where: { id: olivia.id }, + data: { matchCount: 1 } // Marcus + }); + + await prisma.user.update({ + where: { id: david.id }, + data: { ghostedByCount: 2 } // Luna rejected, Mia ghosted + }); + + await prisma.user.update({ + where: { id: isabella.id }, + data: { ghostedCount: 2 } // She has rejected others + }); + + await prisma.user.update({ + where: { id: emma.id }, + data: { ghostedByCount: 1 } // Someone ghosted her + }); + + await prisma.user.update({ + where: { id: ava.id }, + data: { ghostedCount: 1 } // She ghosted Marcus + }); + + await prisma.user.update({ + where: { id: mia.id }, + data: { ghostedByCount: 1 } // She ghosted David + }); + } + + res.json({ + message: 'Database seeded successfully with comprehensive demo data', + userCount: users.count, + matchCount: matches.length, + transactionCount: transactions.length + }); + } catch (error) { + console.error('Seed error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +const PORT = process.env.PORT || 3001; +const LOCAL_IP = getLocalIP(); + +app.listen(PORT, '0.0.0.0', () => { + console.log(`šŸš€ Solmate API server running on port ${PORT}`); + console.log(`šŸ“ Local: http://localhost:${PORT}`); + console.log(`šŸ“ Network: http://${LOCAL_IP}:${PORT}`); + console.log(`šŸ“± For Expo Go, use: ${LOCAL_IP}`); + console.log(`āš™ļø Update LOCAL_IP in src/constants/network.ts to: ${LOCAL_IP}`); +}); + +process.on('SIGINT', async () => { + await prisma.$disconnect(); + process.exit(0); +}); + +module.exports = app; diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..d1e26f6 --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,1881 @@ +{ + "name": "solmate-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "solmate-api", + "version": "1.0.0", + "dependencies": { + "@prisma/client": "^6.13.0", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2" + }, + "devDependencies": { + "nodemon": "^3.1.7", + "prisma": "^6.13.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@prisma/client": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.13.0.tgz", + "integrity": "sha512-8m2+I3dQovkV8CkDMluiwEV1TxV9EXdT6xaCz39O6jYw7mkf5gwfmi+cL4LJsEPwz5tG7sreBwkRpEMJedGYUQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.13.0.tgz", + "integrity": "sha512-OYMM+pcrvj/NqNWCGESSxVG3O7kX6oWuGyvufTUNnDw740KIQvNyA4v0eILgkpuwsKIDU36beZCkUtIt0naTog==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.16.12", + "read-package-up": "11.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.13.0.tgz", + "integrity": "sha512-um+9pfKJW0ihmM83id9FXGi5qEbVJ0Vxi1Gm0xpYsjwUBnw6s2LdPBbrsG9QXRX46K4CLWCTNvskXBup4i9hlw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.13.0.tgz", + "integrity": "sha512-D+1B79LFvtWA0KTt8ALekQ6A/glB9w10ETknH5Y9g1k2NYYQOQy93ffiuqLn3Pl6IPJG3EsK/YMROKEaq8KBrA==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.13.0", + "@prisma/engines-version": "6.13.0-35.361e86d0ea4987e9f53a565309b3eed797a6bcbd", + "@prisma/fetch-engine": "6.13.0", + "@prisma/get-platform": "6.13.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.13.0-35.361e86d0ea4987e9f53a565309b3eed797a6bcbd", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.13.0-35.361e86d0ea4987e9f53a565309b3eed797a6bcbd.tgz", + "integrity": "sha512-MpPyKSzBX7P/ZY9odp9TSegnS/yH3CSbchQE9f0yBg3l2QyN59I6vGXcoYcqKC9VTniS1s18AMmhyr1OWavjHg==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.13.0.tgz", + "integrity": "sha512-grmmq+4FeFKmaaytA8Ozc2+Tf3BC8xn/DVJos6LL022mfRlMZYjT3hZM0/xG7+5fO95zFG9CkDUs0m1S2rXs5Q==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.13.0", + "@prisma/engines-version": "6.13.0-35.361e86d0ea4987e9f53a565309b3eed797a6bcbd", + "@prisma/get-platform": "6.13.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.13.0.tgz", + "integrity": "sha512-Nii2pX50fY4QKKxQwm7/vvqT6Ku8yYJLZAFX4e2vzHwRdMqjugcOG5hOSLjxqoXb0cvOspV70TOhMzrw8kqAnw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.13.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/effect": { + "version": "3.16.12", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz", + "integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/index-to-position": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", + "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "devOptional": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nypm": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz", + "integrity": "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.2.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz", + "integrity": "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/prisma": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.13.0.tgz", + "integrity": "sha512-dfzORf0AbcEyyzxuv2lEwG8g+WRGF/qDQTpHf/6JoHsyF5MyzCEZwClVaEmw3WXcobgadosOboKUgQU0kFs9kw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.13.0", + "@prisma/engines": "6.13.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "devOptional": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "devOptional": true, + "license": "CC0-1.0" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "devOptional": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..93c5b4e --- /dev/null +++ b/server/package.json @@ -0,0 +1,21 @@ +{ + "name": "solmate-api", + "version": "1.0.0", + "description": "API server for Solmate dating app", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "nodemon index.js" + }, + "dependencies": { + "@prisma/client": "^6.13.0", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2" + }, + "devDependencies": { + "nodemon": "^3.1.7", + "prisma": "^6.13.0" + } +} diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma new file mode 100644 index 0000000..dea28ed --- /dev/null +++ b/server/prisma/schema.prisma @@ -0,0 +1,131 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id String @id @default(cuid()) + walletAddress String @unique + gender Gender + name String + age Int + bio String + photos String[] + preferredTipAmount Int @default(3) // 1, 2, 3, 4, or 5 USDC + ghostedCount Int @default(0) + ghostedByCount Int @default(0) + matchCount Int @default(0) + isOnline Boolean @default(false) + lastActive DateTime @default(now()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relationships + sentMatches Match[] @relation("SenderMatches") + receivedMatches Match[] @relation("ReceiverMatches") + transactions Transaction[] + + @@map("users") +} + +model Match { + id String @id @default(cuid()) + senderId String + receiverId String + tipAmount Int + status MatchStatus @default(PENDING) + transactionHash String + dateScheduled DateTime? + dateConfirmed Boolean @default(false) + createdAt DateTime @default(now()) + expiresAt DateTime + ghostedAt DateTime? + acceptedAt DateTime? + rejectedAt DateTime? + + // Relationships + sender User @relation("SenderMatches", fields: [senderId], references: [id]) + receiver User @relation("ReceiverMatches", fields: [receiverId], references: [id]) + transactions Transaction[] + + @@unique([senderId, receiverId]) + @@map("matches") +} + +model Transaction { + id String @id @default(cuid()) + walletAddress String + type TransactionType + amount Int // Amount in USDC cents (multiply by 100) + transactionHash String + matchId String? + timestamp DateTime @default(now()) + status TransactionStatus @default(PENDING) + + // Relationships + user User @relation(fields: [walletAddress], references: [walletAddress]) + match Match? @relation(fields: [matchId], references: [id]) + + @@map("transactions") +} + +model EscrowTransaction { + id String @id @default(cuid()) + matchId String @unique + senderAddress String + receiverAddress String + amount Int // Amount in USDC cents + escrowAddress String // Temporary wallet holding the funds + status EscrowStatus @default(HELD) + createdAt DateTime @default(now()) + releasedAt DateTime? + refundedAt DateTime? + + @@map("escrow_transactions") +} + +enum Gender { + MALE + FEMALE +} + +enum MatchStatus { + PENDING + ACCEPTED + REJECTED + GHOSTED + REFUNDED + EXPIRED + DATE_CONFIRMED +} + +enum TransactionStatus { + PENDING + CONFIRMED + FAILED +} + +enum TransactionType { + TIP_SENT + TIP_RECEIVED + REFUND + GHOST_FORFEIT + GHOSTED + DATE_PAYMENT +} + +enum EscrowStatus { + HELD + RELEASED + REFUNDED +} diff --git a/src/components/MatchCard.tsx b/src/components/MatchCard.tsx index 586ecb8..a14db2f 100644 --- a/src/components/MatchCard.tsx +++ b/src/components/MatchCard.tsx @@ -142,14 +142,14 @@ const MatchCard: React.FC = ({ user, onTip, onSwipe }) => { onPress={() => onSwipe('left')} style={styles.actionButton} > - + onSwipe('right')} style={styles.likeButton} > - + @@ -160,7 +160,7 @@ const MatchCard: React.FC = ({ user, onTip, onSwipe }) => { const styles = StyleSheet.create({ card: { width: width - 32, - height: height * 0.75, + height: height * 0.8, // Increased from 0.75 for more space backgroundColor: '#1A1A1A', borderRadius: 24, overflow: 'hidden', @@ -175,11 +175,14 @@ const styles = StyleSheet.create({ }, imageContainer: { position: 'relative', - height: 384, + height: height * 0.45, // Dynamic height based on screen, was fixed 384px + minHeight: 300, // Ensure minimum height + maxHeight: 500, // Prevent excessive height on large screens }, backgroundImage: { width: '100%', height: '100%', + resizeMode: 'cover', // Ensure proper scaling without cropping faces }, gradientOverlay: { position: 'absolute', @@ -187,7 +190,7 @@ const styles = StyleSheet.create({ left: 0, right: 0, bottom: 0, - backgroundColor: 'rgba(0, 0, 0, 0.8)', + backgroundColor: 'rgba(0, 0, 0, 0.4)', // Reduced opacity from 0.8 to 0.4 }, ghostTag: { position: 'absolute', @@ -238,11 +241,12 @@ const styles = StyleSheet.create({ fontWeight: '600', }, userInfo: { - padding: 24, + padding: 20, // Reduced from 24 backgroundColor: '#1A1A1A', + flex: 1, // Allow flexible sizing }, bioContainer: { - marginBottom: 16, + marginBottom: 12, // Reduced from 16 }, userName: { fontSize: 24, @@ -256,13 +260,13 @@ const styles = StyleSheet.create({ lineHeight: 24, }, tipContainer: { - marginBottom: 24, + marginBottom: 16, // Reduced from 24 }, tipLabel: { color: '#9CA3AF', - fontSize: 14, + fontSize: 13, // Slightly smaller fontWeight: '500', - marginBottom: 12, + marginBottom: 8, // Reduced from 12 textAlign: 'center', }, tipButtons: { @@ -294,12 +298,13 @@ const styles = StyleSheet.create({ actionButtons: { flexDirection: 'row', justifyContent: 'center', - gap: 24, + gap: 20, // Reduced from 24 + marginTop: 4, // Add some top margin }, actionButton: { - width: 64, - height: 64, - borderRadius: 32, + width: 56, // Reduced from 64 + height: 56, // Reduced from 64 + borderRadius: 28, // Adjusted for new size backgroundColor: 'rgba(255, 255, 255, 0.1)', alignItems: 'center', justifyContent: 'center', @@ -315,9 +320,9 @@ const styles = StyleSheet.create({ elevation: 6, }, likeButton: { - width: 64, - height: 64, - borderRadius: 32, + width: 56, // Reduced from 64 + height: 56, // Reduced from 64 + borderRadius: 28, // Adjusted for new size backgroundColor: '#00F90C', alignItems: 'center', justifyContent: 'center', diff --git a/src/components/NetworkTestScreen.tsx b/src/components/NetworkTestScreen.tsx new file mode 100644 index 0000000..67954d2 --- /dev/null +++ b/src/components/NetworkTestScreen.tsx @@ -0,0 +1,253 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Alert, Platform } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { NetworkConfig } from '../constants/network'; + +const NetworkTestScreen: React.FC<{ onClose: () => void }> = ({ onClose }) => { + const [connectionStatus, setConnectionStatus] = useState<'testing' | 'success' | 'error'>('testing'); + const [errorMessage, setErrorMessage] = useState(''); + const [apiUrl, setApiUrl] = useState(''); + + useEffect(() => { + testConnection(); + }, []); + + const testConnection = async () => { + setConnectionStatus('testing'); + const healthUrl = NetworkConfig.getHealthUrl(); + const baseUrl = NetworkConfig.getApiBaseUrl(); + setApiUrl(baseUrl); + + try { + console.log(`Testing connection to: ${healthUrl}`); + + const response = await fetch(healthUrl, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + }); + + if (response.ok) { + const data = await response.json(); + console.log('Connection successful:', data); + setConnectionStatus('success'); + } else { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + } catch (error) { + console.error('Connection test failed:', error); + setConnectionStatus('error'); + setErrorMessage(error instanceof Error ? error.message : 'Unknown error'); + } + }; + + const getStatusColor = () => { + switch (connectionStatus) { + case 'testing': return '#F59E0B'; + case 'success': return '#00F90C'; + case 'error': return '#FF6B6B'; + } + }; + + const getStatusIcon = () => { + switch (connectionStatus) { + case 'testing': return 'sync'; + case 'success': return 'checkmark-circle'; + case 'error': return 'alert-circle'; + } + }; + + const getStatusText = () => { + switch (connectionStatus) { + case 'testing': return 'Testing connection...'; + case 'success': return 'Connected successfully!'; + case 'error': return 'Connection failed'; + } + }; + + const showNetworkHelp = () => { + const helpMessage = Platform.OS === 'web' + ? 'You are running on web. Make sure the API server is running on localhost:3001' + : `You are running on ${Platform.OS}. Make sure: +1. Your API server is running on your computer +2. Your phone and computer are on the same WiFi network +3. Update the LOCAL_IP in src/constants/network.ts to your computer's IP address + +Current API URL: ${apiUrl} + +To find your IP address: +• Windows: Run 'ipconfig' in Command Prompt +• Mac/Linux: Run 'ifconfig' in Terminal +• Look for your WiFi adapter's IP address`; + + Alert.alert('Network Setup Help', helpMessage); + }; + + return ( + + + + Network Connection Test + + + + + + + + + {getStatusText()} + + + {connectionStatus === 'error' && ( + + {errorMessage} + + )} + + + API URL: {apiUrl} + + + Platform: {Platform.OS} + + + + + + + Test Again + + + + + Help + + + + {connectionStatus === 'success' && ( + + Continue to App + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + justifyContent: 'center', + alignItems: 'center', + padding: 20, + }, + modal: { + backgroundColor: '#1A1A1A', + borderRadius: 20, + padding: 24, + width: '100%', + maxWidth: 400, + borderWidth: 1, + borderColor: '#374151', + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 24, + }, + title: { + color: '#FFFFFF', + fontSize: 20, + fontWeight: 'bold', + }, + closeButton: { + padding: 4, + }, + statusContainer: { + alignItems: 'center', + marginBottom: 24, + }, + statusText: { + fontSize: 18, + fontWeight: '600', + marginTop: 16, + marginBottom: 8, + }, + errorText: { + color: '#FF6B6B', + fontSize: 14, + textAlign: 'center', + marginBottom: 16, + }, + urlText: { + color: '#9CA3AF', + fontSize: 12, + textAlign: 'center', + marginTop: 8, + fontFamily: 'monospace', + }, + platformText: { + color: '#6B7280', + fontSize: 12, + textAlign: 'center', + marginTop: 4, + }, + actions: { + flexDirection: 'row', + justifyContent: 'space-around', + marginBottom: 16, + }, + retryButton: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#00F90C', + paddingHorizontal: 20, + paddingVertical: 12, + borderRadius: 12, + }, + retryButtonText: { + color: '#000000', + fontWeight: '600', + marginLeft: 8, + }, + helpButton: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'transparent', + paddingHorizontal: 20, + paddingVertical: 12, + borderRadius: 12, + borderWidth: 1, + borderColor: '#00F90C', + }, + helpButtonText: { + color: '#00F90C', + fontWeight: '600', + marginLeft: 8, + }, + continueButton: { + backgroundColor: '#00F90C', + paddingVertical: 16, + borderRadius: 12, + alignItems: 'center', + }, + continueButtonText: { + color: '#000000', + fontSize: 16, + fontWeight: 'bold', + }, + spinning: { + // Add spinning animation if needed + }, +}); + +export default NetworkTestScreen; diff --git a/src/components/WebWrapper.tsx b/src/components/WebWrapper.tsx index b033e30..1920f0c 100644 --- a/src/components/WebWrapper.tsx +++ b/src/components/WebWrapper.tsx @@ -1,13 +1,22 @@ import React from 'react'; -import { View } from 'react-native'; +import { View, ViewStyle } from 'react-native'; interface WebWrapperProps { children: React.ReactNode; } const WebWrapper: React.FC = ({ children }) => { + const webStyle: ViewStyle = { + flex: 1, + width: '100%' as any, + height: '100%' as any, + backgroundColor: '#0A0A0A', + minHeight: '100vh' as any, + overflow: 'auto' as any + }; + return ( - + {children} ); diff --git a/src/constants/network.ts b/src/constants/network.ts new file mode 100644 index 0000000..5cba798 --- /dev/null +++ b/src/constants/network.ts @@ -0,0 +1,43 @@ +import { Platform } from 'react-native'; + +// Network configuration for different environments +export const NetworkConfig = { + // API server port + PORT: 3001, + + // Get the appropriate API base URL based on platform + getApiBaseUrl(): string { + if (Platform.OS === 'web') { + return `http://localhost:${this.PORT}/api`; + } + + // For mobile devices (Expo Go), we need to use the computer's local IP + // This should be updated to match your computer's IP address + const LOCAL_IP = this.getLocalIP(); + return `http://${LOCAL_IP}:${this.PORT}/api`; + }, + + // Get local IP address (you can override this manually if needed) + getLocalIP(): string { + // Common local IP ranges - you might need to update this based on your network + // You can find your IP by running `ipconfig` (Windows) or `ifconfig` (Mac/Linux) + + // Update this with your actual local IP address from the server output + return '172.25.128.1'; // Updated from server output + }, + + // Health check URL + getHealthUrl(): string { + if (Platform.OS === 'web') { + return `http://localhost:${this.PORT}/health`; + } + return `http://${this.getLocalIP()}:${this.PORT}/health`; + } +}; + +// Export for use in development +export const DEV_CONFIG = { + // Set this to your computer's local IP address when using Expo Go + LOCAL_IP: '172.25.128.1', // Updated from server output + PORT: 3001, +}; diff --git a/src/index.css b/src/index.css index fef43e1..b2009b0 100644 --- a/src/index.css +++ b/src/index.css @@ -22,10 +22,18 @@ html, body, #root { /* Web-specific overrides for React Native Web */ .web-container { - max-width: 480px; - margin: 0 auto; - height: 100vh; - overflow: hidden; + width: 100%; + min-height: 100vh; + height: auto; + overflow-y: auto; + overflow-x: hidden; +} + +/* Remove max-width constraint for better full-screen experience */ +@media (min-width: 481px) { + .web-container { + width: 100%; + } } /* Custom scrollbar for web */ diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts new file mode 100644 index 0000000..7b3e2d9 --- /dev/null +++ b/src/lib/prisma.ts @@ -0,0 +1,11 @@ +import { PrismaClient } from '../generated/prisma'; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined; +}; + +export const prisma = globalForPrisma.prisma ?? new PrismaClient({ + log: ['query', 'error', 'warn'], +}); + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma; diff --git a/src/screens/DiscoveryScreen.tsx b/src/screens/DiscoveryScreen.tsx index 68bf126..a400c47 100644 --- a/src/screens/DiscoveryScreen.tsx +++ b/src/screens/DiscoveryScreen.tsx @@ -14,107 +14,36 @@ import { useQuery } from '@tanstack/react-query'; import MatchCard from '../components/MatchCard'; import { User, Match } from '../types'; import { solanaService } from '../services/solana'; +import { apiService } from '../services/api'; const { width, height } = Dimensions.get('window'); -// Enhanced mock data with high-quality images and better profiles -const MOCK_USERS: User[] = [ - { - id: '1', - walletAddress: 'mock-wallet-1', - gender: 'female', - name: 'Sophia', - age: 26, - bio: 'Adventure seeker & coffee enthusiast ā˜•ļø Love hiking, photography, and spontaneous road trips. Looking for someone to explore life\'s beautiful moments with! 🌟', - photos: ['https://images.unsplash.com/photo-1494790108755-2616b612b786?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 3, - ghostedCount: 0, - ghostedByCount: 0, - matchCount: 12, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '2', - walletAddress: 'mock-wallet-2', - gender: 'female', - name: 'Emma', - age: 24, - bio: 'Yoga instructor & wellness advocate šŸ§˜ā€ā™€ļø Passionate about healthy living, meditation, and creating meaningful connections. Let\'s grow together! ✨', - photos: ['https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 5, - ghostedCount: 1, - ghostedByCount: 0, - matchCount: 18, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '3', - walletAddress: 'mock-wallet-3', - gender: 'female', - name: 'Isabella', - age: 27, - bio: 'Creative soul & art lover šŸŽØ Dog mom to the cutest golden retriever. Love museums, indie music, and deep conversations over wine šŸ·', - photos: ['https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 2, - ghostedCount: 0, - ghostedByCount: 0, - matchCount: 8, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '4', - walletAddress: 'mock-wallet-4', - gender: 'female', - name: 'Olivia', - age: 25, - bio: 'Tech enthusiast & fitness lover šŸ’Ŗ Startup founder by day, gym rat by night. Looking for someone who shares my passion for innovation and health! šŸš€', - photos: ['https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 4, - ghostedCount: 0, - ghostedByCount: 1, - matchCount: 15, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '5', - walletAddress: 'mock-wallet-5', - gender: 'female', - name: 'Ava', - age: 23, - bio: 'Foodie & travel blogger šŸŒ Always on the hunt for the best restaurants and hidden gems. Let\'s create memories over amazing food! šŸ•āœˆļø', - photos: ['https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 3, - ghostedCount: 2, - ghostedByCount: 0, - matchCount: 22, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '6', - walletAddress: 'mock-wallet-6', - gender: 'female', - name: 'Mia', - age: 28, - bio: 'Bookworm & nature lover šŸ“ššŸŒæ Hiking trails, cozy cafes, and deep conversations are my happy places. Looking for someone to share adventures with! šŸ”ļø', - photos: ['https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 2, - ghostedCount: 0, - ghostedByCount: 0, - matchCount: 9, - createdAt: new Date(), - updatedAt: new Date(), - }, -]; - const DiscoveryScreen: React.FC = () => { - const [users, setUsers] = useState(MOCK_USERS); + const [users, setUsers] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); const [walletData, setWalletData] = useState(null); + + // For demo purposes, we'll use the sugar daddy wallet + const currentUserWallet = 'demo-sugar-daddy-1'; + + // Fetch discovery users from database + const { data: discoveryUsers, isLoading, refetch } = useQuery({ + queryKey: ['discovery-users', currentUserWallet], + queryFn: () => apiService.getDiscoveryUsers(currentUserWallet, 10), + enabled: !!currentUserWallet, + }); + + // Update local state when data changes + useEffect(() => { + if (discoveryUsers) { + // Transform database users to match the expected User type + const transformedUsers = discoveryUsers.map(dbUser => ({ + ...dbUser, + gender: dbUser.gender.toLowerCase() as 'male' | 'female', // Convert MALE/FEMALE to male/female + })); + setUsers(transformedUsers); + } + }, [discoveryUsers]); const { data: wallet } = useQuery({ queryKey: ['wallet'], @@ -124,54 +53,110 @@ const DiscoveryScreen: React.FC = () => { useEffect(() => { if (wallet) { setWalletData(wallet); + } else { + // Initialize with mock wallet data for demo + setWalletData({ + address: currentUserWallet, + balance: 2.5, + usdcBalance: 150.0, + isConnected: true, + }); } - }, [wallet]); + }, [wallet, currentUserWallet]); const handleTip = async (amount: number) => { - if (!walletData?.isConnected) { - // Silently handle wallet not connected - no popup + // Ensure wallet is connected or use mock data + const activeWallet = walletData || { + address: currentUserWallet, + balance: 2.5, + usdcBalance: 150.0, + isConnected: true, + }; + + if (!activeWallet?.isConnected) { console.log('Wallet not connected'); return; } - const hasSufficientBalance = await solanaService.hasSufficientUSDC( - walletData.address, - amount - ); + try { + const hasSufficientBalance = await solanaService.hasSufficientUSDC( + activeWallet.address, + amount + ); - if (!hasSufficientBalance) { - // Silently handle insufficient balance - no popup - console.log('Insufficient USDC balance'); - return; - } + if (!hasSufficientBalance) { + console.log('Insufficient USDC balance'); + return; + } + + // Get the current user we're swiping on + const currentUser = users[currentIndex]; + if (!currentUser) return; - try { const transaction = await solanaService.sendTip( - walletData.address, - 'recipient-wallet', + activeWallet.address, + currentUser.walletAddress, amount ); if (transaction) { - // Silently handle successful tip - no popup - console.log(`Tip sent: $${amount} USDC`); + // Create match in database + try { + await apiService.createMatch( + currentUserWallet, + currentUser.walletAddress, + amount, + transaction.transactionHash + ); + + console.log(`Tip sent: $${amount} USDC to ${currentUser.name}`); + Alert.alert( + 'Tip Sent! šŸ’°', + `You sent $${amount} USDC to ${currentUser.name}. They'll be notified of your interest!` + ); + + // Refresh discovery users to exclude the newly matched user + refetch(); + } catch (dbError: any) { + console.error('Database error:', dbError); + + // Handle duplicate match error gracefully + if (dbError.message?.includes('Match already exists')) { + console.log(`Match already exists with ${currentUser.name}`); + // Still refresh the list to ensure UI consistency + refetch(); + } else { + Alert.alert( + 'Error', + 'Failed to create match. Please try again.' + ); + } + } } } catch (error) { - // Silently handle error - no popup - console.log('Failed to send tip:', error); + console.error('Failed to send tip:', error); } }; - const handleSwipe = (direction: 'left' | 'right') => { + const handleSwipe = async (direction: 'left' | 'right') => { if (direction === 'right') { - // Silently handle like - no popup + // Like - send a tip automatically + const currentUser = users[currentIndex]; + if (currentUser) { + await handleTip(currentUser.preferredTipAmount); + } console.log('Profile liked'); } else { - // Silently handle pass - no popup + // Pass console.log('Profile passed'); } setCurrentIndex(prev => Math.min(prev + 1, users.length - 1)); + + // If we're near the end, try to fetch more users + if (currentIndex >= users.length - 2) { + refetch(); + } }; const renderCard = (user: User, index: number) => ( @@ -277,7 +262,7 @@ const styles = StyleSheet.create({ backgroundColor: '#0A0A0A', }, header: { - paddingTop: 48, + paddingTop: 20, // Reduced from 48 since no navigation header paddingBottom: 16, paddingHorizontal: 24, backgroundColor: '#0A0A0A', diff --git a/src/screens/MatchesScreen.tsx b/src/screens/MatchesScreen.tsx index a778192..261c51a 100644 --- a/src/screens/MatchesScreen.tsx +++ b/src/screens/MatchesScreen.tsx @@ -10,156 +10,103 @@ import { StyleSheet, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Match, User } from '../types'; import { solanaService } from '../services/solana'; +import { apiService } from '../services/api'; -// Enhanced mock matches data -const MOCK_MATCHES: Match[] = [ - { - id: 'match-1', - senderId: 'user-1', - receiverId: 'user-2', - tipAmount: 3, - status: 'pending', - transactionHash: 'tx-hash-1', - createdAt: new Date(Date.now() - 3600000), // 1 hour ago - expiresAt: new Date(Date.now() + 82800000), // 23 hours from now - }, - { - id: 'match-2', - senderId: 'user-1', - receiverId: 'user-3', - tipAmount: 5, - status: 'accepted', - transactionHash: 'tx-hash-2', - createdAt: new Date(Date.now() - 86400000), // 1 day ago - expiresAt: new Date(Date.now() - 86400000), - }, - { - id: 'match-3', - senderId: 'user-4', - receiverId: 'user-1', - tipAmount: 2, - status: 'ghosted', - transactionHash: 'tx-hash-3', - createdAt: new Date(Date.now() - 172800000), // 2 days ago - expiresAt: new Date(Date.now() - 172800000), - ghostedAt: new Date(Date.now() - 86400000), - }, - { - id: 'match-4', - senderId: 'user-5', - receiverId: 'user-1', - tipAmount: 4, - status: 'pending', - transactionHash: 'tx-hash-4', - createdAt: new Date(Date.now() - 7200000), // 2 hours ago - expiresAt: new Date(Date.now() + 79200000), // 22 hours from now - }, -]; +const MatchesScreen: React.FC = () => { + // For demo purposes, we'll use the sugar daddy wallet + const currentUserWallet = 'demo-sugar-daddy-1'; + const queryClient = useQueryClient(); -const MOCK_USERS: { [key: string]: User } = { - 'user-2': { - id: 'user-2', - walletAddress: 'mock-wallet-2', - gender: 'female', - name: 'Sophia', - age: 26, - bio: 'Adventure seeker & coffee enthusiast ā˜•ļø', - photos: ['https://images.unsplash.com/photo-1494790108755-2616b612b786?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 3, - ghostedCount: 0, - ghostedByCount: 0, - matchCount: 12, - createdAt: new Date(), - updatedAt: new Date(), - }, - 'user-3': { - id: 'user-3', - walletAddress: 'mock-wallet-3', - gender: 'female', - name: 'Emma', - age: 24, - bio: 'Yoga instructor & wellness advocate šŸ§˜ā€ā™€ļø', - photos: ['https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 5, - ghostedCount: 1, - ghostedByCount: 0, - matchCount: 18, - createdAt: new Date(), - updatedAt: new Date(), - }, - 'user-4': { - id: 'user-4', - walletAddress: 'mock-wallet-4', - gender: 'female', - name: 'Isabella', - age: 27, - bio: 'Creative soul & art lover šŸŽØ', - photos: ['https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 2, - ghostedCount: 0, - ghostedByCount: 0, - matchCount: 8, - createdAt: new Date(), - updatedAt: new Date(), - }, - 'user-5': { - id: 'user-5', - walletAddress: 'mock-wallet-5', - gender: 'female', - name: 'Olivia', - age: 25, - bio: 'Tech enthusiast & fitness lover šŸ’Ŗ', - photos: ['https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&h=600&fit=crop&crop=face&auto=format'], - preferredTipAmount: 4, - ghostedCount: 0, - ghostedByCount: 1, - matchCount: 15, - createdAt: new Date(), - updatedAt: new Date(), - }, -}; + const { data: rawMatches = [], isLoading } = useQuery({ + queryKey: ['matches', currentUserWallet], + queryFn: () => apiService.getUserMatches(currentUserWallet), + enabled: !!currentUserWallet, + }); -const MatchesScreen: React.FC = () => { - const { data: matches = MOCK_MATCHES } = useQuery({ - queryKey: ['matches'], - queryFn: () => Promise.resolve(MOCK_MATCHES), + // Transform database matches to app format + const matches = React.useMemo(() => { + return rawMatches.map((dbMatch: any) => ({ + ...dbMatch, + status: dbMatch.status.toLowerCase() as 'pending' | 'accepted' | 'rejected' | 'ghosted' | 'refunded', + })); + }, [rawMatches]); + + const acceptMatchMutation = useMutation({ + mutationFn: async (matchId: string) => { + return await apiService.acceptMatch(matchId); + }, + onSuccess: (match: any) => { + queryClient.invalidateQueries({ queryKey: ['matches', currentUserWallet] }); + const userName = match.sender?.walletAddress === currentUserWallet ? match.receiver?.name : match.sender?.name; + Alert.alert('Match Accepted! šŸ’–', `You and ${userName} are now matched!`); + }, }); - const getMatchUser = (match: Match): User | null => { - const userId = match.senderId === 'user-1' ? match.receiverId : match.senderId; - return MOCK_USERS[userId] || null; + const rejectMatchMutation = useMutation({ + mutationFn: async (matchId: string) => { + return await apiService.rejectMatch(matchId); + }, + onSuccess: (match: any) => { + queryClient.invalidateQueries({ queryKey: ['matches', currentUserWallet] }); + const userName = match.sender?.walletAddress === currentUserWallet ? match.receiver?.name : match.sender?.name; + Alert.alert('Match Rejected', `${userName} will be refunded.`); + }, + }); + + const getMatchUser = (match: any): User | null => { + // Return the other user (not the current user) + if (match.sender?.walletAddress === currentUserWallet) { + return match.receiver ? { + ...match.receiver, + gender: match.receiver.gender.toLowerCase() as 'male' | 'female' + } : null; + } else { + return match.sender ? { + ...match.sender, + gender: match.sender.gender.toLowerCase() as 'male' | 'female' + } : null; + } }; const getMatchStatus = (status: string) => { switch (status) { - case 'pending': + case 'PENDING': return { text: 'Pending Response', color: '#F59E0B', icon: 'time-outline' }; - case 'accepted': + case 'ACCEPTED': return { text: 'Match Accepted!', color: '#00F90C', icon: 'checkmark-circle-outline' }; - case 'ghosted': + case 'GHOSTED': return { text: 'Ghosted', color: '#8B5CF6', icon: 'close-circle-outline' }; - case 'expired': + case 'REJECTED': + return { text: 'Rejected', color: '#FF6B6B', icon: 'close-circle-outline' }; + case 'EXPIRED': return { text: 'Expired', color: '#6B7280', icon: 'alert-circle-outline' }; default: return { text: 'Unknown', color: '#6B7280', icon: 'help-circle-outline' }; } }; - const formatTimeAgo = (timestamp: Date) => { + const formatTimeAgo = (timestamp: Date | string) => { const now = new Date(); - const diffInHours = Math.floor((now.getTime() - timestamp.getTime()) / (1000 * 60 * 60)); + const dateObject = typeof timestamp === 'string' ? new Date(timestamp) : timestamp; + + // Check if the date is valid + if (isNaN(dateObject.getTime())) { + return 'Recently'; + } + + const diffInHours = Math.floor((now.getTime() - dateObject.getTime()) / (1000 * 60 * 60)); if (diffInHours < 1) return 'Just now'; if (diffInHours < 24) return `${diffInHours}h ago`; const diffInDays = Math.floor(diffInHours / 24); if (diffInDays < 7) return `${diffInDays}d ago`; - return timestamp.toLocaleDateString(); + return dateObject.toLocaleDateString(); }; - const handleMatchAction = (match: Match, action: 'accept' | 'reject') => { + const handleMatchAction = (match: any, action: 'accept' | 'reject') => { const user = getMatchUser(match); if (!user) return; @@ -171,9 +118,7 @@ const MatchesScreen: React.FC = () => { { text: 'Cancel', style: 'cancel' }, { text: 'Accept', - onPress: () => { - Alert.alert('Match Accepted!', `You and ${user.name} are now matched!`); - }, + onPress: () => acceptMatchMutation.mutate(match.id), }, ] ); @@ -186,9 +131,7 @@ const MatchesScreen: React.FC = () => { { text: 'Reject', style: 'destructive', - onPress: () => { - Alert.alert('Match Rejected', `${user.name} will be refunded.`); - }, + onPress: () => rejectMatchMutation.mutate(match.id), }, ] ); @@ -201,7 +144,9 @@ const MatchesScreen: React.FC = () => { if (!user) return null; - const isIncoming = match.receiverId === 'user-1'; + // Check if this is an incoming match (the current user is the receiver) + const currentUser = rawMatches.find(m => m.id === match.id); + const isIncoming = currentUser?.receiver?.walletAddress === currentUserWallet; return ( @@ -326,7 +271,7 @@ const styles = StyleSheet.create({ backgroundColor: '#0A0A0A', }, header: { - paddingTop: 48, + paddingTop: 20, // Reduced from 48 since no navigation header paddingBottom: 16, paddingHorizontal: 24, backgroundColor: '#0A0A0A', diff --git a/src/screens/OnboardingScreen.tsx b/src/screens/OnboardingScreen.tsx index 465dffc..f75bb7e 100644 --- a/src/screens/OnboardingScreen.tsx +++ b/src/screens/OnboardingScreen.tsx @@ -6,26 +6,35 @@ import { ScrollView, Alert, Dimensions, + TextInput, + StyleSheet, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, - withTiming, interpolate, } from 'react-native-reanimated'; +import { useMutation } from '@tanstack/react-query'; import { TIP_AMOUNTS } from '../constants'; import { OnboardingData } from '../types'; +import { apiService } from '../services/api'; +import { solanaService } from '../services/solana'; -const { width, height } = Dimensions.get('window'); +const { width } = Dimensions.get('window'); -const OnboardingScreen: React.FC = () => { +interface OnboardingScreenProps { + walletAddress: string; + onComplete: (user: any) => void; +} + +const OnboardingScreen: React.FC = ({ walletAddress, onComplete }) => { const [currentStep, setCurrentStep] = useState(0); const [onboardingData, setOnboardingData] = useState({ gender: 'male', - tipAmount: 2, - photos: [], + tipAmount: 3, + photos: ['https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=600&fit=crop&crop=face&auto=format'], bio: '', name: '', age: 25, @@ -34,14 +43,39 @@ const OnboardingScreen: React.FC = () => { // Animation values const genderAnimation = useSharedValue(0); const tipAnimation = useSharedValue(0); - const walletAnimation = useSharedValue(0); + const profileAnimation = useSharedValue(0); const steps = [ { title: 'Choose Your Gender', subtitle: 'This helps us match you with the right people' }, { title: 'Select Tip Amount', subtitle: 'Choose how much you want to tip for matches' }, - { title: 'Connect Wallet', subtitle: 'Connect your Solana wallet to start tipping' }, + { title: 'Complete Profile', subtitle: 'Tell us a bit about yourself' }, ]; + const createUserMutation = useMutation({ + mutationFn: async (userData: typeof onboardingData) => { + return await apiService.onboardUser({ + walletAddress, + gender: userData.gender, + name: userData.name, + age: userData.age, + bio: userData.bio, + photos: userData.photos, + preferredTipAmount: userData.tipAmount, + }); + }, + onSuccess: (user) => { + Alert.alert( + 'Welcome to Solmate! šŸŽ‰', + 'Your profile has been created successfully. Start swiping to find amazing matches!', + [{ text: 'Let\'s Go!', onPress: () => onComplete(user) }] + ); + }, + onError: (error) => { + console.error('Error creating user:', error); + Alert.alert('Error', 'Failed to create profile. Please try again.'); + }, + }); + const handleGenderSelect = (gender: 'male' | 'female') => { setOnboardingData(prev => ({ ...prev, gender })); genderAnimation.value = withSpring(1); @@ -49,7 +83,7 @@ const OnboardingScreen: React.FC = () => { setTimeout(() => { setCurrentStep(1); tipAnimation.value = withSpring(1); - }, 500); + }, 300); }; const handleTipSelect = (amount: number) => { @@ -58,49 +92,43 @@ const OnboardingScreen: React.FC = () => { setTimeout(() => { setCurrentStep(2); - walletAnimation.value = withSpring(1); - }, 500); + profileAnimation.value = withSpring(1); + }, 300); }; - const handleWalletConnect = async () => { - try { - // This would integrate with actual wallet providers - Alert.alert('Wallet Connected', 'Your Solana wallet has been connected successfully!'); - - // Navigate to main app - // navigation.navigate('Discovery'); - } catch (error) { - Alert.alert('Error', 'Failed to connect wallet. Please try again.'); + const handleCompleteProfile = () => { + if (!onboardingData.name.trim() || !onboardingData.bio.trim()) { + Alert.alert('Missing Information', 'Please fill in your name and bio.'); + return; + } + + if (onboardingData.age < 18 || onboardingData.age > 100) { + Alert.alert('Invalid Age', 'Please enter a valid age between 18 and 100.'); + return; } + + createUserMutation.mutate(onboardingData); }; const renderGenderSelection = () => ( ({ - opacity: interpolate(genderAnimation.value, [0, 1], [0, 1]), - transform: [{ scale: interpolate(genderAnimation.value, [0, 1], [0.8, 1]) }], + opacity: interpolate(genderAnimation.value, [0, 1], [1, 1]), + transform: [{ scale: interpolate(genderAnimation.value, [0, 1], [1, 1]) }], })), ]} > - - I am a... - + I am a... - + handleGenderSelect('male')} - className={`w-32 h-32 rounded-2xl justify-center items-center border-2 ${ - onboardingData.gender === 'male' - ? 'border-primary bg-primary/20' - : 'border-gray-600 bg-background-secondary' - }`} + style={[ + styles.genderButton, + onboardingData.gender === 'male' && styles.genderButtonActive + ]} > { color={onboardingData.gender === 'male' ? '#00F90C' : '#A0A0A0'} /> - Man + Sugar Daddy handleGenderSelect('female')} - className={`w-32 h-32 rounded-2xl justify-center items-center border-2 ${ - onboardingData.gender === 'female' - ? 'border-primary bg-primary/20' - : 'border-gray-600 bg-background-secondary' - }`} + style={[ + styles.genderButton, + onboardingData.gender === 'female' && styles.genderButtonActive + ]} > { color={onboardingData.gender === 'female' ? '#00F90C' : '#A0A0A0'} /> - Woman + Sugar Baby @@ -144,144 +173,308 @@ const OnboardingScreen: React.FC = () => { const renderTipSelection = () => ( ({ - opacity: interpolate(tipAnimation.value, [0, 1], [0, 1]), - transform: [{ scale: interpolate(tipAnimation.value, [0, 1], [0.8, 1]) }], + opacity: interpolate(tipAnimation.value, [0, 1], [1, 1]), })), ]} > - - Choose Tip Amount + Choose Tip Amount + + This is the amount you'll {onboardingData.gender === 'male' ? 'pay' : 'receive'} for matches - + {TIP_AMOUNTS.map((tip) => ( handleTipSelect(tip.value)} - className={`p-6 rounded-2xl border-2 ${ - onboardingData.tipAmount === tip.value - ? 'border-primary bg-primary/20 animate-glow' - : 'border-gray-600 bg-background-secondary' - }`} + style={[ + styles.tipButton, + onboardingData.tipAmount === tip.value && styles.tipButtonActive + ]} > - - - - {tip.label} - - - {tip.description} - - - + + + {tip.label} + + + {tip.description} + + ))} ); - const renderWalletConnection = () => ( + const renderProfileCompletion = () => ( ({ - opacity: interpolate(walletAnimation.value, [0, 1], [0, 1]), - transform: [{ scale: interpolate(walletAnimation.value, [0, 1], [0.8, 1]) }], + opacity: interpolate(profileAnimation.value, [0, 1], [1, 1]), })), ]} > - - Connect Your Wallet - + Complete Your Profile + + + + Name + setOnboardingData(prev => ({ ...prev, name: text }))} + style={styles.textInput} + placeholder="Enter your name" + placeholderTextColor="#9CA3AF" + /> + - - - - Connect Phantom Wallet - + + Age + setOnboardingData(prev => ({ ...prev, age: parseInt(text) || 18 }))} + style={styles.textInput} + placeholder="Enter your age" + placeholderTextColor="#9CA3AF" + keyboardType="numeric" + /> + - - - Connect Solflare - + + About You + setOnboardingData(prev => ({ ...prev, bio: text }))} + style={[styles.textInput, styles.bioInput]} + placeholder="Tell others about yourself, your interests, what you're looking for..." + placeholderTextColor="#9CA3AF" + multiline + numberOfLines={4} + /> + - - Connect Backpack + + {createUserMutation.isPending ? 'Creating Profile...' : 'Complete Profile'} + - - - Your wallet will be used to send and receive USDC tips for matches - ); return ( - + {/* Progress Bar */} - + {steps.map((_, index) => ( ))} {/* Step Title */} - - + + {steps[currentStep]?.title} - + {steps[currentStep]?.subtitle} {/* Step Content */} - {currentStep === 0 && renderGenderSelection()} - {currentStep === 1 && renderTipSelection()} - {currentStep === 2 && renderWalletConnection()} + + {currentStep === 0 && renderGenderSelection()} + {currentStep === 1 && renderTipSelection()} + {currentStep === 2 && renderProfileCompletion()} + ); }; +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#0A0A0A', + }, + progressContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + paddingTop: 48, + paddingBottom: 16, + paddingHorizontal: 40, + }, + progressBar: { + height: 4, + borderRadius: 2, + backgroundColor: '#374151', + marginHorizontal: 4, + }, + progressBarActive: { + backgroundColor: '#00F90C', + }, + headerContainer: { + paddingHorizontal: 24, + paddingBottom: 32, + }, + headerTitle: { + fontSize: 28, + fontWeight: 'bold', + color: '#FFFFFF', + textAlign: 'center', + marginBottom: 8, + }, + headerSubtitle: { + fontSize: 16, + color: '#9CA3AF', + textAlign: 'center', + }, + stepContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 24, + }, + stepTitle: { + fontSize: 32, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 32, + textAlign: 'center', + }, + stepSubtitle: { + fontSize: 16, + color: '#9CA3AF', + textAlign: 'center', + marginBottom: 32, + }, + genderContainer: { + flexDirection: 'row', + gap: 24, + }, + genderButton: { + width: 140, + height: 140, + borderRadius: 20, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#1A1A1A', + borderWidth: 2, + borderColor: '#374151', + }, + genderButtonActive: { + borderColor: '#00F90C', + backgroundColor: 'rgba(0, 249, 12, 0.1)', + }, + genderText: { + fontSize: 16, + fontWeight: '600', + color: '#9CA3AF', + marginTop: 12, + textAlign: 'center', + }, + genderTextActive: { + color: '#00F90C', + }, + tipContainer: { + width: '100%', + }, + tipButton: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#1A1A1A', + borderWidth: 2, + borderColor: '#374151', + borderRadius: 16, + padding: 20, + marginBottom: 16, + }, + tipButtonActive: { + borderColor: '#00F90C', + backgroundColor: 'rgba(0, 249, 12, 0.1)', + }, + tipContent: { + flex: 1, + }, + tipAmount: { + fontSize: 24, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 4, + }, + tipAmountActive: { + color: '#00F90C', + }, + tipDescription: { + fontSize: 14, + color: '#9CA3AF', + }, + tipDescriptionActive: { + color: '#00F90C', + }, + profileForm: { + width: '100%', + }, + inputContainer: { + marginBottom: 20, + }, + inputLabel: { + fontSize: 16, + fontWeight: '600', + color: '#FFFFFF', + marginBottom: 8, + }, + textInput: { + backgroundColor: '#1A1A1A', + borderWidth: 1, + borderColor: '#374151', + borderRadius: 12, + paddingVertical: 16, + paddingHorizontal: 16, + color: '#FFFFFF', + fontSize: 16, + }, + bioInput: { + height: 100, + textAlignVertical: 'top', + }, + completeButton: { + backgroundColor: '#00F90C', + borderRadius: 16, + paddingVertical: 16, + alignItems: 'center', + marginTop: 20, + }, + completeButtonText: { + color: '#000000', + fontSize: 18, + fontWeight: 'bold', + }, +}); + export default OnboardingScreen; \ No newline at end of file diff --git a/src/screens/ProfileScreen.tsx b/src/screens/ProfileScreen.tsx index 5aa81ac..fa42bca 100644 --- a/src/screens/ProfileScreen.tsx +++ b/src/screens/ProfileScreen.tsx @@ -34,14 +34,26 @@ const MOCK_USER: User = { updatedAt: new Date(), }; -const ProfileScreen: React.FC = () => { +interface ProfileScreenProps { + currentUser: User | null; + onSignOut: () => void; +} + +const ProfileScreen: React.FC = ({ currentUser, onSignOut }) => { const [isEditing, setIsEditing] = useState(false); - const [user, setUser] = useState(MOCK_USER); + const [user, setUser] = useState(currentUser || MOCK_USER); const [notificationsEnabled, setNotificationsEnabled] = useState(true); const [locationEnabled, setLocationEnabled] = useState(true); const [privacyEnabled, setPrivacyEnabled] = useState(false); const [showPhotoModal, setShowPhotoModal] = useState(false); + // Update local state when currentUser changes + React.useEffect(() => { + if (currentUser) { + setUser(currentUser); + } + }, [currentUser]); + const { data: wallet } = useQuery({ queryKey: ['wallet'], queryFn: () => solanaService.connectWallet(), @@ -67,9 +79,7 @@ const ProfileScreen: React.FC = () => { { text: 'Logout', style: 'destructive', - onPress: () => { - Alert.alert('Logged Out', 'You have been logged out successfully'); - }, + onPress: onSignOut, }, ] ); @@ -94,6 +104,14 @@ const ProfileScreen: React.FC = () => { const renderProfileHeader = () => ( + {/* Add logout button */} + + + + void; + onNeedsOnboarding: (walletAddress: string) => void; +} + +const SignInScreen: React.FC = ({ onSignInSuccess, onNeedsOnboarding }) => { + const [walletAddress, setWalletAddress] = useState(''); + const [isConnectingWallet, setIsConnectingWallet] = useState(false); + const queryClient = useQueryClient(); + + // Connect Wallet Mutation + const connectWalletMutation = useMutation({ + mutationFn: async () => { + const wallet = await solanaService.connectWallet(); + return wallet; + }, + onSuccess: async (walletData) => { + if (walletData?.isConnected && walletData.address) { + setWalletAddress(walletData.address); + await handleSignIn(walletData.address); + } + }, + onError: (error) => { + console.error('Wallet connection failed:', error); + Alert.alert('Connection Error', 'Failed to connect wallet. Please try again.'); + }, + }); + + // Sign In Mutation + const signInMutation = useMutation({ + mutationFn: async (address: string) => { + return await apiService.signInUser(address); + }, + onSuccess: ({ user, needsOnboarding }) => { + if (needsOnboarding) { + onNeedsOnboarding(walletAddress); + } else if (user) { + onSignInSuccess(user); + } + }, + onError: (error) => { + console.error('Sign in failed:', error); + Alert.alert('Sign In Error', 'Failed to sign in. Please try again.'); + }, + }); + + const handleSignIn = async (address: string) => { + if (!address.trim()) { + Alert.alert('Invalid Input', 'Please enter a valid wallet address.'); + return; + } + + signInMutation.mutate(address); + }; + + const handleConnectWallet = () => { + setIsConnectingWallet(true); + connectWalletMutation.mutate(); + }; + + const handleManualSignIn = () => { + handleSignIn(walletAddress); + }; + + // Demo accounts for easy testing + const demoAccounts = [ + { name: 'Sugar Daddy Demo', wallet: 'demo-sugar-daddy-1' }, + { name: 'Sophia (Sugar Baby)', wallet: 'demo-sugar-baby-1' }, + { name: 'Emma (Sugar Baby)', wallet: 'demo-sugar-baby-2' }, + ]; + + const handleDemoLogin = (wallet: string) => { + setWalletAddress(wallet); + handleSignIn(wallet); + }; + + return ( + + + + + {/* Header */} + + šŸ’Ž Solmate + Where Sugar Meets Solana + + Connect with amazing people through secure, blockchain-powered dating + + + + {/* Sign In Options */} + + Sign In + + {/* Wallet Connect Button */} + + + + {connectWalletMutation.isPending ? 'Connecting...' : 'Connect Solana Wallet'} + + + + + + or + + + + {/* Manual Wallet Address Input */} + + Wallet Address + + + + + + {signInMutation.isPending ? 'Signing In...' : 'Sign In'} + + + + {/* Demo Accounts Section */} + + šŸŽÆ Quick Demo Accounts + Try the app with pre-made profiles + + {demoAccounts.map((account, index) => ( + handleDemoLogin(account.wallet)} + style={styles.demoButton} + disabled={signInMutation.isPending} + > + + {account.name} + + + ))} + + + {/* New User */} + + New to Solmate? + + Connect your wallet and we'll guide you through creating your profile! + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#0A0A0A', + }, + scrollContainer: { + flexGrow: 1, + justifyContent: 'center', + paddingHorizontal: 24, + paddingVertical: 40, + }, + header: { + alignItems: 'center', + marginBottom: 40, + }, + logo: { + fontSize: 48, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 8, + }, + tagline: { + fontSize: 20, + color: '#00F90C', + fontWeight: '600', + marginBottom: 12, + }, + description: { + fontSize: 16, + color: '#9CA3AF', + textAlign: 'center', + lineHeight: 24, + maxWidth: 300, + }, + signInContainer: { + width: '100%', + }, + sectionTitle: { + fontSize: 24, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 24, + textAlign: 'center', + }, + connectWalletButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#00F90C', + borderRadius: 16, + paddingVertical: 16, + paddingHorizontal: 24, + marginBottom: 20, + }, + connectWalletButtonText: { + color: '#000000', + fontSize: 18, + fontWeight: 'bold', + marginLeft: 12, + }, + divider: { + flexDirection: 'row', + alignItems: 'center', + marginVertical: 20, + }, + dividerLine: { + flex: 1, + height: 1, + backgroundColor: '#374151', + }, + dividerText: { + color: '#6B7280', + paddingHorizontal: 16, + fontSize: 14, + }, + inputContainer: { + marginBottom: 20, + }, + inputLabel: { + color: '#FFFFFF', + fontSize: 16, + fontWeight: '600', + marginBottom: 8, + }, + textInput: { + backgroundColor: '#1A1A1A', + borderWidth: 1, + borderColor: '#374151', + borderRadius: 12, + paddingVertical: 16, + paddingHorizontal: 16, + color: '#FFFFFF', + fontSize: 16, + }, + signInButton: { + backgroundColor: '#374151', + borderRadius: 16, + paddingVertical: 16, + paddingHorizontal: 24, + alignItems: 'center', + marginBottom: 30, + }, + signInButtonText: { + color: '#FFFFFF', + fontSize: 18, + fontWeight: 'bold', + }, + demoSection: { + marginBottom: 30, + }, + demoTitle: { + fontSize: 18, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 4, + textAlign: 'center', + }, + demoSubtitle: { + fontSize: 14, + color: '#9CA3AF', + textAlign: 'center', + marginBottom: 16, + }, + demoButton: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#1A1A1A', + borderWidth: 1, + borderColor: '#374151', + borderRadius: 12, + paddingVertical: 14, + paddingHorizontal: 16, + marginBottom: 8, + }, + demoButtonText: { + flex: 1, + color: '#FFFFFF', + fontSize: 16, + fontWeight: '500', + marginLeft: 12, + }, + newUserSection: { + alignItems: 'center', + paddingTop: 20, + borderTopWidth: 1, + borderTopColor: '#374151', + }, + newUserText: { + color: '#FFFFFF', + fontSize: 16, + fontWeight: '600', + marginBottom: 4, + }, + newUserSubtext: { + color: '#9CA3AF', + fontSize: 14, + textAlign: 'center', + lineHeight: 20, + }, +}); + +export default SignInScreen; diff --git a/src/services/api.ts b/src/services/api.ts new file mode 100644 index 0000000..141e903 --- /dev/null +++ b/src/services/api.ts @@ -0,0 +1,105 @@ +import { Platform } from 'react-native'; +import { NetworkConfig } from '../constants/network'; + +const API_BASE_URL = NetworkConfig.getApiBaseUrl(); + +class ApiService { + private async request(endpoint: string, options?: RequestInit): Promise { + const url = `${API_BASE_URL}${endpoint}`; + + try { + console.log(`API Request: ${url}`); + + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + ...options, + }); + + if (!response.ok) { + const error = await response.json().catch(() => ({ error: 'Network error' })); + throw new Error(error.error || `HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + console.log(`API Response for ${endpoint}:`, data); + return data; + } catch (error) { + console.error(`API request failed for ${endpoint}:`, error); + throw error; + } + } + + // Authentication + async signInUser(walletAddress: string) { + return this.request<{ user: any; needsOnboarding: boolean }>('/auth/signin', { + method: 'POST', + body: JSON.stringify({ walletAddress }), + }); + } + + async onboardUser(data: { + walletAddress: string; + name: string; + age: number; + gender: string; + bio: string; + photos: string[]; + preferredTipAmount: number; + }) { + return this.request<{ user: any }>('/auth/onboard', { + method: 'POST', + body: JSON.stringify(data), + }); + } + + // Discovery + async getDiscoveryUsers(walletAddress: string, limit: number = 10) { + return this.request(`/users/discovery/${walletAddress}?limit=${limit}`); + } + + // Matches + async createMatch( + senderWallet: string, + receiverWallet: string, + tipAmount: number, + transactionHash: string + ) { + return this.request('/matches', { + method: 'POST', + body: JSON.stringify({ + senderWallet, + receiverWallet, + tipAmount, + transactionHash, + }), + }); + } + + async getUserMatches(walletAddress: string) { + return this.request(`/matches/${walletAddress}`); + } + + async acceptMatch(matchId: string) { + return this.request(`/matches/${matchId}/accept`, { + method: 'PATCH', + }); + } + + async rejectMatch(matchId: string) { + return this.request(`/matches/${matchId}/reject`, { + method: 'PATCH', + }); + } + + // Utility + async seedDatabase() { + return this.request<{ message: string; userCount: number }>('/seed', { + method: 'POST', + }); + } +} + +export const apiService = new ApiService(); diff --git a/src/services/database.ts b/src/services/database.ts new file mode 100644 index 0000000..6fe3516 --- /dev/null +++ b/src/services/database.ts @@ -0,0 +1,381 @@ +import { prisma } from '../lib/prisma'; +import { User, Match, Transaction, Gender, MatchStatus, TransactionType } from '../generated/prisma'; +import bcrypt from 'bcryptjs'; + +export interface CreateUserInput { + walletAddress: string; + gender: 'MALE' | 'FEMALE'; + name: string; + age: number; + bio: string; + photos: string[]; + preferredTipAmount?: number; +} + +export interface SignInInput { + walletAddress: string; + password?: string; // Optional for wallet-only auth +} + +class DatabaseService { + // User Management + async createUser(data: CreateUserInput): Promise { + const user = await prisma.user.create({ + data: { + walletAddress: data.walletAddress, + gender: data.gender as Gender, + name: data.name, + age: data.age, + bio: data.bio, + photos: data.photos, + preferredTipAmount: data.preferredTipAmount || 3, + }, + }); + return user; + } + + async getUserByWallet(walletAddress: string): Promise { + return await prisma.user.findUnique({ + where: { walletAddress }, + include: { + sentMatches: true, + receivedMatches: true, + transactions: true, + }, + }); + } + + async updateUserActivity(walletAddress: string): Promise { + await prisma.user.update({ + where: { walletAddress }, + data: { + lastActive: new Date(), + isOnline: true, + }, + }); + } + + // Discovery - Get potential matches + async getDiscoveryUsers(currentUserWallet: string, limit: number = 10): Promise { + const currentUser = await this.getUserByWallet(currentUserWallet); + if (!currentUser) return []; + + // Get users we haven't matched with yet + const existingMatchUserIds = await prisma.match.findMany({ + where: { + OR: [ + { sender: { walletAddress: currentUserWallet } }, + { receiver: { walletAddress: currentUserWallet } }, + ], + }, + select: { + senderId: true, + receiverId: true, + }, + }); + + const excludedUserIds = new Set([ + currentUser.id, + ...existingMatchUserIds.flatMap(match => [match.senderId, match.receiverId]) + ]); + + return await prisma.user.findMany({ + where: { + id: { notIn: Array.from(excludedUserIds) }, + gender: currentUser.gender === 'MALE' ? 'FEMALE' : 'MALE', // Opposite gender + isOnline: true, + }, + orderBy: { + lastActive: 'desc', + }, + take: limit, + }); + } + + // Match Management + async createMatch( + senderWallet: string, + receiverWallet: string, + tipAmount: number, + transactionHash: string + ): Promise { + const sender = await this.getUserByWallet(senderWallet); + const receiver = await this.getUserByWallet(receiverWallet); + + if (!sender || !receiver) { + throw new Error('User not found'); + } + + const expiresAt = new Date(); + expiresAt.setHours(expiresAt.getHours() + 24); // 24 hour expiry + + const match = await prisma.match.create({ + data: { + senderId: sender.id, + receiverId: receiver.id, + tipAmount, + transactionHash, + expiresAt, + status: 'PENDING' as MatchStatus, + }, + include: { + sender: true, + receiver: true, + }, + }); + + // Create transaction record + await this.createTransaction({ + walletAddress: senderWallet, + type: 'TIP_SENT' as TransactionType, + amount: tipAmount * 100, // Convert to cents + transactionHash, + matchId: match.id, + }); + + return match; + } + + async getUserMatches(walletAddress: string): Promise { + const user = await this.getUserByWallet(walletAddress); + if (!user) return []; + + return await prisma.match.findMany({ + where: { + OR: [ + { senderId: user.id }, + { receiverId: user.id }, + ], + }, + include: { + sender: true, + receiver: true, + transactions: true, + }, + orderBy: { + createdAt: 'desc', + }, + }); + } + + async acceptMatch(matchId: string): Promise { + const match = await prisma.match.update({ + where: { id: matchId }, + data: { + status: 'ACCEPTED' as MatchStatus, + acceptedAt: new Date(), + }, + include: { + sender: true, + receiver: true, + }, + }); + + // Update match counts + await Promise.all([ + prisma.user.update({ + where: { id: match.senderId }, + data: { matchCount: { increment: 1 } }, + }), + prisma.user.update({ + where: { id: match.receiverId }, + data: { matchCount: { increment: 1 } }, + }), + ]); + + return match; + } + + async rejectMatch(matchId: string): Promise { + return await prisma.match.update({ + where: { id: matchId }, + data: { + status: 'REJECTED' as MatchStatus, + rejectedAt: new Date(), + }, + include: { + sender: true, + receiver: true, + }, + }); + } + + async ghostMatch(matchId: string): Promise { + const match = await prisma.match.update({ + where: { id: matchId }, + data: { + status: 'GHOSTED' as MatchStatus, + ghostedAt: new Date(), + }, + include: { + sender: true, + receiver: true, + }, + }); + + // Update ghosted counts + await Promise.all([ + prisma.user.update({ + where: { id: match.senderId }, + data: { ghostedByCount: { increment: 1 } }, + }), + prisma.user.update({ + where: { id: match.receiverId }, + data: { ghostedCount: { increment: 1 } }, + }), + ]); + + return match; + } + + // Transaction Management + async createTransaction(data: { + walletAddress: string; + type: TransactionType; + amount: number; + transactionHash: string; + matchId?: string; + }): Promise { + return await prisma.transaction.create({ + data, + }); + } + + async getUserTransactions(walletAddress: string): Promise { + return await prisma.transaction.findMany({ + where: { walletAddress }, + include: { + match: { + include: { + sender: true, + receiver: true, + }, + }, + }, + orderBy: { + timestamp: 'desc', + }, + }); + } + + // Sign in functionality + async signInUser(walletAddress: string): Promise { + let user = await this.getUserByWallet(walletAddress); + + // If user doesn't exist, we'll need them to complete onboarding + if (!user) { + return null; + } + + // Update activity + await this.updateUserActivity(walletAddress); + + return user; + } + + // Check if user exists and needs onboarding + async needsOnboarding(walletAddress: string): Promise { + const user = await this.getUserByWallet(walletAddress); + return !user; + } + + // Seed data for testing + async seedDatabase(): Promise { + // Check if users already exist + const existingUsers = await prisma.user.count(); + if (existingUsers > 0) { + console.log('Database already seeded'); + return; + } + + // Create sample sugar babies (females) + const sugarBabies = [ + { + walletAddress: 'demo-wallet-1', + gender: 'FEMALE' as Gender, + name: 'Sophia', + age: 26, + bio: 'Adventure seeker & coffee enthusiast ā˜•ļø Love hiking, photography, and spontaneous road trips. Looking for someone to explore life\'s beautiful moments with! 🌟', + photos: ['https://images.unsplash.com/photo-1494790108755-2616b612b786?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 3, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-2', + gender: 'FEMALE' as Gender, + name: 'Emma', + age: 24, + bio: 'Yoga instructor & wellness advocate šŸ§˜ā€ā™€ļø Passionate about healthy living, meditation, and creating meaningful connections. Let\'s grow together! ✨', + photos: ['https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 5, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-3', + gender: 'FEMALE' as Gender, + name: 'Isabella', + age: 27, + bio: 'Creative soul & art lover šŸŽØ Dog mom to the cutest golden retriever. Love museums, indie music, and deep conversations over wine šŸ·', + photos: ['https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 2, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-4', + gender: 'FEMALE' as Gender, + name: 'Olivia', + age: 25, + bio: 'Tech enthusiast & fitness lover šŸ’Ŗ Startup founder by day, gym rat by night. Looking for someone who shares my passion for innovation and health! šŸš€', + photos: ['https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 4, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-5', + gender: 'FEMALE' as Gender, + name: 'Ava', + age: 23, + bio: 'Foodie & travel blogger šŸŒ Always on the hunt for the best restaurants and hidden gems. Let\'s create memories over amazing food! šŸ•āœˆļø', + photos: ['https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 3, + isOnline: true, + }, + { + walletAddress: 'demo-wallet-6', + gender: 'FEMALE' as Gender, + name: 'Mia', + age: 28, + bio: 'Bookworm & nature lover šŸ“ššŸŒæ Hiking trails, cozy cafes, and deep conversations are my happy places. Looking for someone to share adventures with! šŸ”ļø', + photos: ['https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 2, + isOnline: true, + }, + ]; + + // Create demo sugar daddy + const sugarDaddy = { + walletAddress: 'demo-sugar-daddy-1', + gender: 'MALE' as Gender, + name: 'Alexander', + age: 35, + bio: 'Successful entrepreneur looking for meaningful connections. Love fine dining, travel, and spoiling the right person. Let\'s create beautiful memories together! šŸ’Ž', + photos: ['https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=600&fit=crop&crop=face&auto=format'], + preferredTipAmount: 5, + isOnline: true, + }; + + try { + // Create all users + await prisma.user.createMany({ + data: [...sugarBabies, sugarDaddy], + }); + + console.log(`āœ… Successfully seeded ${sugarBabies.length + 1} users to the database!`); + } catch (error) { + console.error('Error seeding database:', error); + throw error; + } + } +} + +export const databaseService = new DatabaseService(); diff --git a/src/services/solana.ts b/src/services/solana.ts index e49bd7f..9c6bc07 100644 --- a/src/services/solana.ts +++ b/src/services/solana.ts @@ -38,6 +38,11 @@ class SolanaService { // Get USDC balance async getUSDCBalance(walletAddress: string): Promise { try { + // For mock/demo wallet addresses, return mock balance + if (walletAddress.startsWith('demo-') || walletAddress === '11111111111111111111111111111112') { + return 100.0; // Mock USDC balance + } + const publicKey = new PublicKey(walletAddress); const tokenAccount = await getAssociatedTokenAddress( this.usdcMint, @@ -56,7 +61,8 @@ class SolanaService { return tokenAccountInfo.value.uiAmount || 0; } catch (error) { console.error('Error getting USDC balance:', error); - return 0; + // Return mock balance as fallback to prevent app crashes + return 50.0; } } diff --git a/start-full-stack.bat b/start-full-stack.bat new file mode 100644 index 0000000..6ac8ba7 --- /dev/null +++ b/start-full-stack.bat @@ -0,0 +1,30 @@ +@echo off +title Solmate Full Stack + +echo. +echo šŸš€ Starting Solmate Full Stack Application... +echo. + +echo šŸ“” Starting API Server... +start "API Server" cmd /k "cd server && node index.js" + +echo ā³ Waiting for API server to start... +timeout /t 5 > nul + +echo šŸ“± Starting React Native App... +start "React Native" cmd /k "npm start" + +echo. +echo šŸŽ‰ Both services are starting up! +echo šŸ“ API Server: http://localhost:3001 +echo šŸ“ React Native: http://localhost:8081 +echo. +echo Press any key to open the web app... +pause > nul + +start http://localhost:8081 + +echo. +echo Services are running in separate windows. +echo Close those windows to stop the services. +pause diff --git a/start-full-stack.ps1 b/start-full-stack.ps1 new file mode 100644 index 0000000..9cbee2d --- /dev/null +++ b/start-full-stack.ps1 @@ -0,0 +1,70 @@ +# PowerShell script to start both API server and React Native app + +Write-Host "šŸš€ Starting Solmate Full Stack Application..." -ForegroundColor Blue + +# Function to cleanup background processes +function Cleanup { + Write-Host "`nā¹ļø Stopping services..." -ForegroundColor Yellow + # Stop background jobs + Get-Job | Stop-Job + Get-Job | Remove-Job + exit 0 +} + +# Handle Ctrl+C +$null = Register-EngineEvent PowerShell.Exiting -Action { Cleanup } + +try { + # Start API Server + Write-Host "šŸ“” Starting API Server..." -ForegroundColor Green + + # Start server in background + Start-Job -Name "APIServer" -ScriptBlock { + Set-Location $using:PWD\server + node index.js + } | Out-Null + + # Wait for server to start + Start-Sleep -Seconds 3 + + # Check if API server is running + try { + $response = Invoke-RestMethod -Uri "http://localhost:3001/health" -TimeoutSec 5 + Write-Host "āœ… API Server running on http://localhost:3001" -ForegroundColor Green + } + catch { + Write-Host "āŒ API Server failed to start" -ForegroundColor Red + Get-Job | Stop-Job + Get-Job | Remove-Job + exit 1 + } + + # Start React Native app + Write-Host "šŸ“± Starting React Native App..." -ForegroundColor Green + Start-Job -Name "ReactNative" -ScriptBlock { + Set-Location $using:PWD + npm start + } | Out-Null + + Write-Host "šŸŽ‰ Both services are starting up!" -ForegroundColor Blue + Write-Host "šŸ“ API Server: http://localhost:3001" -ForegroundColor Yellow + Write-Host "šŸ“ React Native: http://localhost:8081" -ForegroundColor Yellow + Write-Host "šŸ“ Press Ctrl+C to stop all services" -ForegroundColor Yellow + + # Keep script running and show job output + while ($true) { + # Get job outputs + Get-Job | Receive-Job + Start-Sleep -Seconds 1 + + # Check if jobs are still running + $runningJobs = Get-Job | Where-Object { $_.State -eq "Running" } + if ($runningJobs.Count -eq 0) { + Write-Host "All services have stopped." -ForegroundColor Yellow + break + } + } +} +finally { + Cleanup +} diff --git a/start-full-stack.sh b/start-full-stack.sh new file mode 100644 index 0000000..b653d05 --- /dev/null +++ b/start-full-stack.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}šŸš€ Starting Solmate Full Stack Application...${NC}" + +# Function to cleanup background processes +cleanup() { + echo -e "\n${YELLOW}ā¹ļø Stopping services...${NC}" + # Kill all background jobs + jobs -p | xargs -r kill + exit 0 +} + +# Trap CTRL+C and cleanup +trap cleanup SIGINT + +# Start the API server +echo -e "${GREEN}šŸ“” Starting API Server...${NC}" +cd server +node index.js & +API_PID=$! + +# Wait a moment for server to start +sleep 3 + +# Check if API server is running +if curl -s http://localhost:3001/health > /dev/null; then + echo -e "${GREEN}āœ… API Server running on http://localhost:3001${NC}" +else + echo -e "${RED}āŒ API Server failed to start${NC}" + exit 1 +fi + +# Go back to main directory +cd .. + +# Start React Native app +echo -e "${GREEN}šŸ“± Starting React Native App...${NC}" +npm start & +RN_PID=$! + +echo -e "${BLUE}šŸŽ‰ Both services are starting up!${NC}" +echo -e "${YELLOW}šŸ“ API Server: http://localhost:3001${NC}" +echo -e "${YELLOW}šŸ“ React Native: http://localhost:8081${NC}" +echo -e "${YELLOW}šŸ“ Press Ctrl+C to stop all services${NC}" + +# Wait for background processes +wait