diff options
| author | Lucas Faria Mendes <lucas.fariamo08@gmail.com> | 2026-08-16 23:17:42 +0000 |
|---|---|---|
| committer | Lucas Faria Mendes <lucas.fariamo08@gmail.com> | 2026-08-16 23:17:42 +0000 |
| commit | 375482e654a01615683867e732ac9e458c7cbe74 (patch) | |
| tree | 12d1b31b8deb6bf8590bcdbc2cfb52dcc553e006 | |
| parent | 2c2dfba45357bbd205a59145ae55a2779c911ae0 (diff) | |
| download | yaba-master.tar.gz yaba-master.zip | |
Signed-off-by: Lucas Faria Mendes <lucas.fariamo08@gmail.com>
59 files changed, 2306 insertions, 14 deletions
diff --git a/.expo/dev/logs/start.log b/.expo/dev/logs/start.log index a7b78f4..965bde5 100644 --- a/.expo/dev/logs/start.log +++ b/.expo/dev/logs/start.log @@ -423,3 +423,102 @@ {"_e":"metro:bundling:started","_t":1786730404864,"id":"f","platform":"android","environment":null,"entry":"node_modules/expo-router/entry.js"} {"_e":"metro:bundling:progress","_t":1786730404865,"id":"f","progress":0,"total":1,"current":0} {"_e":"metro:bundling:done","_t":1786730404939,"id":"f","total":1,"ms":73.694709} +{"_e":"root:init","_t":1786732808125,"format":"v0-jsonl","version":"57.0.14"} +{"_e":"devserver:start","_t":1786732808138,"mode":"development","web":true,"baseUrl":"","asyncRoutes":false,"routerRoot":"../../../src/app","serverComponents":false,"serverActions":false,"serverRendering":true,"apiRoutes":false,"exporting":false} +{"_e":"metro:config","_t":1786732808338,"serverRoot":"../../..","projectRoot":"../../..","exporting":false,"flags":{"autolinkingModuleResolution":false,"serverActions":false,"serverComponents":false,"reactCompiler":true,"optimizeGraph":false,"treeshaking":false,"logbox":false}} +{"_e":"expo:dev-tools-plugin:load","_t":1786732808453,"plugins":[]} +{"_e":"metro:instantiate","_t":1786732809368,"atlas":false,"workers":4,"host":"::","port":8081} +{"_e":"metro:bundling:started","_t":1786732833607,"id":"1","platform":"android","environment":null,"entry":"node_modules/expo-router/entry.js"} +{"_e":"metro:bundling:progress","_t":1786732833607,"id":"1","progress":0,"total":1,"current":0} +{"_e":"metro:bundling:progress","_t":1786732833614,"id":"1","progress":0,"total":1,"current":0} +{"_e":"metro:bundling:progress","_t":1786732833954,"id":"1","progress":0.010000000000000002,"total":1,"current":1} +{"_e":"metro:bundling:progress","_t":1786732834056,"id":"1","progress":0.13573407202216065,"total":19,"current":7} +{"_e":"metro:bundling:progress","_t":1786732834157,"id":"1","progress":0.13573407202216065,"total":33,"current":10} +{"_e":"metro:bundling:progress","_t":1786732834258,"id":"1","progress":0.140625,"total":40,"current":15} +{"_e":"metro:bundling:progress","_t":1786732834358,"id":"1","progress":0.18367346938775508,"total":56,"current":24} +{"_e":"metro:bundling:progress","_t":1786732834458,"id":"1","progress":0.18566990547954262,"total":123,"current":53} +{"_e":"metro:bundling:progress","_t":1786732834558,"id":"1","progress":0.18566990547954262,"total":256,"current":82} +{"_e":"metro:bundling:progress","_t":1786732834659,"id":"1","progress":0.18566990547954262,"total":314,"current":108} +{"_e":"metro:bundling:progress","_t":1786732834760,"id":"1","progress":0.18566990547954262,"total":318,"current":114} +{"_e":"metro:bundling:progress","_t":1786732834863,"id":"1","progress":0.18566990547954262,"total":338,"current":135} +{"_e":"metro:bundling:progress","_t":1786732834964,"id":"1","progress":0.18566990547954262,"total":379,"current":147} +{"_e":"metro:bundling:progress","_t":1786732835064,"id":"1","progress":0.18566990547954262,"total":397,"current":170} +{"_e":"metro:bundling:progress","_t":1786732835167,"id":"1","progress":0.2431666105546671,"total":436,"current":215} +{"_e":"metro:bundling:progress","_t":1786732835267,"id":"1","progress":0.2843013817310301,"total":497,"current":265} +{"_e":"metro:bundling:progress","_t":1786732835367,"id":"1","progress":0.3070317721096012,"total":536,"current":297} +{"_e":"metro:bundling:progress","_t":1786732835496,"id":"1","progress":0.3198894851248952,"total":587,"current":332} +{"_e":"metro:bundling:progress","_t":1786732835597,"id":"1","progress":0.34995757691974294,"total":617,"current":365} +{"_e":"metro:bundling:progress","_t":1786732835696,"id":"1","progress":0.34995757691974294,"total":642,"current":372} +{"_e":"metro:bundling:progress","_t":1786732835812,"id":"1","progress":0.34995757691974294,"total":650,"current":375} +{"_e":"metro:bundling:progress","_t":1786732835913,"id":"1","progress":0.3589277742364645,"total":671,"current":402} +{"_e":"metro:bundling:progress","_t":1786732836012,"id":"1","progress":0.40021326895025405,"total":705,"current":446} +{"_e":"metro:bundling:progress","_t":1786732836112,"id":"1","progress":0.40021326895025405,"total":780,"current":486} +{"_e":"metro:bundling:progress","_t":1786732836212,"id":"1","progress":0.44222500000000003,"total":800,"current":532} +{"_e":"metro:bundling:progress","_t":1786732836317,"id":"1","progress":0.4786415069706442,"total":821,"current":568} +{"_e":"metro:bundling:progress","_t":1786732836418,"id":"1","progress":0.5000878450501899,"total":823,"current":582} +{"_e":"metro:bundling:progress","_t":1786732836522,"id":"1","progress":0.5217978573409764,"total":832,"current":601} +{"_e":"metro:bundling:progress","_t":1786732836627,"id":"1","progress":0.556369255576458,"total":854,"current":637} +{"_e":"metro:bundling:progress","_t":1786732836728,"id":"1","progress":0.556369255576458,"total":904,"current":648} +{"_e":"metro:bundling:progress","_t":1786732836829,"id":"1","progress":0.5572758811252468,"total":931,"current":695} +{"_e":"metro:bundling:progress","_t":1786732836928,"id":"1","progress":0.5678589353785657,"total":982,"current":740} +{"_e":"metro:bundling:progress","_t":1786732837028,"id":"1","progress":0.5804988662131518,"total":1029,"current":784} +{"_e":"metro:bundling:progress","_t":1786732837128,"id":"1","progress":0.6072039560328013,"total":1069,"current":833} +{"_e":"metro:bundling:progress","_t":1786732837228,"id":"1","progress":0.6072039560328013,"total":1137,"current":870} +{"_e":"metro:bundling:progress","_t":1786732837330,"id":"1","progress":0.6072039560328013,"total":1170,"current":884} +{"_e":"metro:bundling:progress","_t":1786732837437,"id":"1","progress":0.6072039560328013,"total":1186,"current":899} +{"_e":"metro:bundling:progress","_t":1786732837537,"id":"1","progress":0.6072039560328013,"total":1226,"current":923} +{"_e":"metro:bundling:progress","_t":1786732837637,"id":"1","progress":0.6072039560328013,"total":1236,"current":958} +{"_e":"metro:bundling:progress","_t":1786732837737,"id":"1","progress":0.6555775234131114,"total":1240,"current":1004} +{"_e":"metro:bundling:progress","_t":1786732837837,"id":"1","progress":0.6865306122448981,"total":1260,"current":1044} +{"_e":"metro:bundling:progress","_t":1786732837938,"id":"1","progress":0.697471755887667,"total":1286,"current":1074} +{"_e":"metro:bundling:progress","_t":1786732838038,"id":"1","progress":0.7320908897737457,"total":1316,"current":1126} +{"_e":"metro:bundling:progress","_t":1786732838138,"id":"1","progress":0.7320908897737457,"total":1338,"current":1136} +{"_e":"metro:bundling:progress","_t":1786732838238,"id":"1","progress":0.7320908897737457,"total":1371,"current":1157} +{"_e":"metro:bundling:progress","_t":1786732838339,"id":"1","progress":0.7320908897737457,"total":1394,"current":1177} +{"_e":"metro:bundling:progress","_t":1786732838439,"id":"1","progress":0.7631270408163265,"total":1400,"current":1223} +{"_e":"metro:bundling:progress","_t":1786732838540,"id":"1","progress":0.7920748944161405,"total":1418,"current":1262} +{"_e":"metro:bundling:progress","_t":1786732838642,"id":"1","progress":0.8136570225573336,"total":1429,"current":1289} +{"_e":"metro:bundling:progress","_t":1786732838750,"id":"1","progress":0.8136570225573336,"total":1513,"current":1321} +{"_e":"metro:bundling:progress","_t":1786732838850,"id":"1","progress":0.8136570225573336,"total":1519,"current":1336} +{"_e":"metro:bundling:progress","_t":1786732838952,"id":"1","progress":0.8136570225573336,"total":1539,"current":1353} +{"_e":"metro:bundling:progress","_t":1786732839066,"id":"1","progress":0.8136570225573336,"total":1572,"current":1372} +{"_e":"metro:bundling:progress","_t":1786732839166,"id":"1","progress":0.8136570225573336,"total":1573,"current":1377} +{"_e":"metro:bundling:progress","_t":1786732839266,"id":"1","progress":0.8136570225573336,"total":1573,"current":1386} +{"_e":"metro:bundling:progress","_t":1786732839366,"id":"1","progress":0.8136570225573336,"total":1579,"current":1405} +{"_e":"metro:bundling:progress","_t":1786732839467,"id":"1","progress":0.8268598142757296,"total":1599,"current":1454} +{"_e":"metro:bundling:progress","_t":1786732839566,"id":"1","progress":0.859855667371347,"total":1609,"current":1492} +{"_e":"metro:bundling:progress","_t":1786732839667,"id":"1","progress":0.8858816690765611,"total":1616,"current":1521} +{"_e":"root:init","_t":1786735236143,"format":"v0-jsonl","version":"57.0.14"} +{"_e":"devserver:start","_t":1786735236152,"mode":"development","web":true,"baseUrl":"","asyncRoutes":false,"routerRoot":"../../../src/app","serverComponents":false,"serverActions":false,"serverRendering":true,"apiRoutes":false,"exporting":false} +{"_e":"metro:config","_t":1786735236347,"serverRoot":"../../..","projectRoot":"../../..","exporting":false,"flags":{"autolinkingModuleResolution":false,"serverActions":false,"serverComponents":false,"reactCompiler":true,"optimizeGraph":false,"treeshaking":false,"logbox":false}} +{"_e":"expo:dev-tools-plugin:load","_t":1786735236441,"plugins":[]} +{"_e":"metro:instantiate","_t":1786735237108,"atlas":false,"workers":4,"host":"::","port":8081} +{"_e":"metro:bundling:started","_t":1786735269627,"id":"1","platform":"android","environment":null,"entry":"node_modules/expo-router/entry.js"} +{"_e":"metro:bundling:progress","_t":1786735269628,"id":"1","progress":0,"total":1,"current":0} +{"_e":"metro:bundling:progress","_t":1786735269631,"id":"1","progress":0,"total":1,"current":0} +{"_e":"metro:bundling:progress","_t":1786735269731,"id":"1","progress":0.19753086419753085,"total":441,"current":196} +{"_e":"metro:bundling:progress","_t":1786735269832,"id":"1","progress":0.44940145442128326,"total":989,"current":663} +{"_e":"metro:bundling:progress","_t":1786735269932,"id":"1","progress":0.7629691585074808,"total":1399,"current":1222} +{"_e":"metro:bundling:progress","_t":1786735270032,"id":"1","progress":0.8858816690765611,"total":1616,"current":1521} +{"_e":"metro:bundling:progress","_t":1786735270453,"id":"1","progress":0.8870469194196646,"total":1616,"current":1522} +{"_e":"metro:bundling:progress","_t":1786735270554,"id":"1","progress":0.8870469194196646,"total":1620,"current":1524} +{"_e":"metro:bundling:progress","_t":1786735270574,"id":"1","progress":0.8870469194196646,"total":1620,"current":1525} +{"_e":"metro:bundling:progress","_t":1786735270678,"id":"1","progress":0.8870469194196646,"total":1625,"current":1528} +{"_e":"metro:bundling:progress","_t":1786735270790,"id":"1","progress":0.8872350266916148,"total":1636,"current":1541} +{"_e":"metro:bundling:progress","_t":1786735270890,"id":"1","progress":0.8925488339823395,"total":1647,"current":1556} +{"_e":"metro:bundling:progress","_t":1786735270990,"id":"1","progress":0.9005975428084181,"total":1647,"current":1563} +{"_e":"metro:bundling:progress","_t":1786735271091,"id":"1","progress":0.913318506279379,"total":1647,"current":1574} +{"_e":"metro:bundling:progress","_t":1786735271192,"id":"1","progress":0.913318506279379,"total":1675,"current":1596} +{"_e":"metro:bundling:progress","_t":1786735271296,"id":"1","progress":0.9163466179269343,"total":1708,"current":1635} +{"_e":"metro:bundling:progress","_t":1786735271396,"id":"1","progress":0.931230536439669,"total":1743,"current":1682} +{"_e":"metro:bundling:progress","_t":1786735271496,"id":"1","progress":0.931230536439669,"total":1852,"current":1718} +{"_e":"metro:bundling:progress","_t":1786735271597,"id":"1","progress":0.931230536439669,"total":1941,"current":1767} +{"_e":"metro:bundling:progress","_t":1786735271697,"id":"1","progress":0.931230536439669,"total":1963,"current":1825} +{"_e":"metro:bundling:progress","_t":1786735271799,"id":"1","progress":0.931230536439669,"total":1968,"current":1860} +{"_e":"metro:bundling:progress","_t":1786735271899,"id":"1","progress":0.931230536439669,"total":1971,"current":1864} +{"_e":"metro:bundling:progress","_t":1786735271934,"id":"1","progress":0.931230536439669,"total":1971,"current":1865} +{"_e":"metro:bundling:progress","_t":1786735272034,"id":"1","progress":0.9383542495663708,"total":1980,"current":1918} +{"_e":"metro:bundling:progress","_t":1786735272136,"id":"1","progress":0.9592589736455863,"total":1992,"current":1951} +{"_e":"metro:bundling:progress","_t":1786735272236,"id":"1","progress":0.9880538652829541,"total":2003,"current":1991} +{"_e":"metro:bundling:progress","_t":1786735272337,"id":"1","progress":0.999,"total":2004,"current":2004} +{"_e":"metro:bundling:done","_t":1786735272589,"id":"1","total":2004,"ms":2961.243125} diff --git a/.expo/devices.json b/.expo/devices.json index 571b1da..b39b60a 100644 --- a/.expo/devices.json +++ b/.expo/devices.json @@ -1,6 +1,10 @@ { "devices": [ { + "installationId": "fa243d0b-7e85-4203-adb9-0d0bfa0ea281", + "lastUsed": 1786735269450 + }, + { "installationId": "f06b0747-5dd9-402d-ad65-d21673fd02e8", "lastUsed": 1786730404334 } diff --git a/.expo/prebuild/cached-packages.json b/.expo/prebuild/cached-packages.json new file mode 100644 index 0000000..1cd0737 --- /dev/null +++ b/.expo/prebuild/cached-packages.json @@ -0,0 +1,4 @@ +{ + "dependencies": "38dd0936b63efc0bda4ddde42e3a5ac4c932c175", + "devDependencies": "bb8fe344f947319e69cdf214593cc09e6a829d57" +} diff --git a/.expo/types/router.d.ts b/.expo/types/router.d.ts index fe9ff7f..b43aaf4 100644 --- a/.expo/types/router.d.ts +++ b/.expo/types/router.d.ts @@ -6,9 +6,9 @@ export * from 'expo-router'; declare module 'expo-router' { export namespace ExpoRouter { export interface __routes<T extends string | object = string> { - hrefInputParams: { pathname: Router.RelativePathString, params?: Router.UnknownInputParams } | { pathname: Router.ExternalPathString, params?: Router.UnknownInputParams } | { pathname: `/biometrics`; params?: Router.UnknownInputParams; } | { pathname: `/login`; params?: Router.UnknownInputParams; } | { pathname: `/register`; params?: Router.UnknownInputParams; } | { pathname: `/_sitemap`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}` | `/`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/salary` | `/salary`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/recurring` | `/recurring`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/expenses` | `/expenses`; params?: Router.UnknownInputParams; }; - hrefOutputParams: { pathname: Router.RelativePathString, params?: Router.UnknownOutputParams } | { pathname: Router.ExternalPathString, params?: Router.UnknownOutputParams } | { pathname: `/biometrics`; params?: Router.UnknownOutputParams; } | { pathname: `/login`; params?: Router.UnknownOutputParams; } | { pathname: `/register`; params?: Router.UnknownOutputParams; } | { pathname: `/_sitemap`; params?: Router.UnknownOutputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}` | `/`; params?: Router.UnknownOutputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/salary` | `/salary`; params?: Router.UnknownOutputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/recurring` | `/recurring`; params?: Router.UnknownOutputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/expenses` | `/expenses`; params?: Router.UnknownOutputParams; }; - href: Router.RelativePathString | Router.ExternalPathString | `/biometrics${`?${string}` | `#${string}` | ''}` | `/login${`?${string}` | `#${string}` | ''}` | `/register${`?${string}` | `#${string}` | ''}` | `/_sitemap${`?${string}` | `#${string}` | ''}` | `${'/(app)'}${'/(tabs)'}${`?${string}` | `#${string}` | ''}` | `/${`?${string}` | `#${string}` | ''}` | `${'/(app)'}${'/(tabs)'}/salary${`?${string}` | `#${string}` | ''}` | `/salary${`?${string}` | `#${string}` | ''}` | `${'/(app)'}${'/(tabs)'}/recurring${`?${string}` | `#${string}` | ''}` | `/recurring${`?${string}` | `#${string}` | ''}` | `${'/(app)'}${'/(tabs)'}/expenses${`?${string}` | `#${string}` | ''}` | `/expenses${`?${string}` | `#${string}` | ''}` | { pathname: Router.RelativePathString, params?: Router.UnknownInputParams } | { pathname: Router.ExternalPathString, params?: Router.UnknownInputParams } | { pathname: `/biometrics`; params?: Router.UnknownInputParams; } | { pathname: `/login`; params?: Router.UnknownInputParams; } | { pathname: `/register`; params?: Router.UnknownInputParams; } | { pathname: `/_sitemap`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}` | `/`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/salary` | `/salary`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/recurring` | `/recurring`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/expenses` | `/expenses`; params?: Router.UnknownInputParams; }; + hrefInputParams: { pathname: Router.RelativePathString, params?: Router.UnknownInputParams } | { pathname: Router.ExternalPathString, params?: Router.UnknownInputParams } | { pathname: `/biometrics`; params?: Router.UnknownInputParams; } | { pathname: `/login`; params?: Router.UnknownInputParams; } | { pathname: `/register`; params?: Router.UnknownInputParams; } | { pathname: `/_sitemap`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/expenses` | `/expenses`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}` | `/`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/recurring` | `/recurring`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/salary` | `/salary`; params?: Router.UnknownInputParams; }; + hrefOutputParams: { pathname: Router.RelativePathString, params?: Router.UnknownOutputParams } | { pathname: Router.ExternalPathString, params?: Router.UnknownOutputParams } | { pathname: `/biometrics`; params?: Router.UnknownOutputParams; } | { pathname: `/login`; params?: Router.UnknownOutputParams; } | { pathname: `/register`; params?: Router.UnknownOutputParams; } | { pathname: `/_sitemap`; params?: Router.UnknownOutputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/expenses` | `/expenses`; params?: Router.UnknownOutputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}` | `/`; params?: Router.UnknownOutputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/recurring` | `/recurring`; params?: Router.UnknownOutputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/salary` | `/salary`; params?: Router.UnknownOutputParams; }; + href: Router.RelativePathString | Router.ExternalPathString | `/biometrics${`?${string}` | `#${string}` | ''}` | `/login${`?${string}` | `#${string}` | ''}` | `/register${`?${string}` | `#${string}` | ''}` | `/_sitemap${`?${string}` | `#${string}` | ''}` | `${'/(app)'}${'/(tabs)'}/expenses${`?${string}` | `#${string}` | ''}` | `/expenses${`?${string}` | `#${string}` | ''}` | `${'/(app)'}${'/(tabs)'}${`?${string}` | `#${string}` | ''}` | `/${`?${string}` | `#${string}` | ''}` | `${'/(app)'}${'/(tabs)'}/recurring${`?${string}` | `#${string}` | ''}` | `/recurring${`?${string}` | `#${string}` | ''}` | `${'/(app)'}${'/(tabs)'}/salary${`?${string}` | `#${string}` | ''}` | `/salary${`?${string}` | `#${string}` | ''}` | { pathname: Router.RelativePathString, params?: Router.UnknownInputParams } | { pathname: Router.ExternalPathString, params?: Router.UnknownInputParams } | { pathname: `/biometrics`; params?: Router.UnknownInputParams; } | { pathname: `/login`; params?: Router.UnknownInputParams; } | { pathname: `/register`; params?: Router.UnknownInputParams; } | { pathname: `/_sitemap`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/expenses` | `/expenses`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}` | `/`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/recurring` | `/recurring`; params?: Router.UnknownInputParams; } | { pathname: `${'/(app)'}${'/(tabs)'}/salary` | `/salary`; params?: Router.UnknownInputParams; }; } } } diff --git a/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_144.png b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_144.png Binary files differnew file mode 100644 index 0000000..26ed03d --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_144.png diff --git a/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_192.png b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_192.png Binary files differnew file mode 100644 index 0000000..c538b44 --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_192.png diff --git a/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_48.png b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_48.png Binary files differnew file mode 100644 index 0000000..d766b08 --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_48.png diff --git a/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_72.png b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_72.png Binary files differnew file mode 100644 index 0000000..152a1ea --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_72.png diff --git a/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_96.png b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_96.png Binary files differnew file mode 100644 index 0000000..76ffc93 --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-circle/android-standard-circle-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_96.png diff --git a/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_144.png b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_144.png Binary files differnew file mode 100644 index 0000000..adac4a5 --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_144.png diff --git a/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_192.png b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_192.png Binary files differnew file mode 100644 index 0000000..f18ce5d --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_192.png diff --git a/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_48.png b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_48.png Binary files differnew file mode 100644 index 0000000..af1c20c --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_48.png diff --git a/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_72.png b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_72.png Binary files differnew file mode 100644 index 0000000..bac0aac --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_72.png diff --git a/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_96.png b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_96.png Binary files differnew file mode 100644 index 0000000..b58ea7b --- /dev/null +++ b/.expo/web/cache/production/images/android-standard-square/android-standard-square-4c0282c81ca1169161c49fade57b02369065543827bc42527c087b001b76c86d-cover-transparent/icon_96.png diff --git a/.expo/web/cache/production/images/splash-ios/splash-ios-27b060a757a29038c9618586baa0ba3894dbe60d084c6059df497ff429c2d92f-contain/icon_152.png b/.expo/web/cache/production/images/splash-ios/splash-ios-27b060a757a29038c9618586baa0ba3894dbe60d084c6059df497ff429c2d92f-contain/icon_152.png Binary files differnew file mode 100644 index 0000000..c0a217a --- /dev/null +++ b/.expo/web/cache/production/images/splash-ios/splash-ios-27b060a757a29038c9618586baa0ba3894dbe60d084c6059df497ff429c2d92f-contain/icon_152.png diff --git a/.expo/web/cache/production/images/splash-ios/splash-ios-27b060a757a29038c9618586baa0ba3894dbe60d084c6059df497ff429c2d92f-contain/icon_228.png b/.expo/web/cache/production/images/splash-ios/splash-ios-27b060a757a29038c9618586baa0ba3894dbe60d084c6059df497ff429c2d92f-contain/icon_228.png Binary files differnew file mode 100644 index 0000000..c9289ca --- /dev/null +++ b/.expo/web/cache/production/images/splash-ios/splash-ios-27b060a757a29038c9618586baa0ba3894dbe60d084c6059df497ff429c2d92f-contain/icon_228.png diff --git a/.expo/web/cache/production/images/splash-ios/splash-ios-27b060a757a29038c9618586baa0ba3894dbe60d084c6059df497ff429c2d92f-contain/icon_76.png b/.expo/web/cache/production/images/splash-ios/splash-ios-27b060a757a29038c9618586baa0ba3894dbe60d084c6059df497ff429c2d92f-contain/icon_76.png Binary files differnew file mode 100644 index 0000000..dd7546a --- /dev/null +++ b/.expo/web/cache/production/images/splash-ios/splash-ios-27b060a757a29038c9618586baa0ba3894dbe60d084c6059df497ff429c2d92f-contain/icon_76.png @@ -7,4 +7,6 @@ expo-env.d.ts node_modules/ android/ -ios/
\ No newline at end of file +ios/ + +build-*.apk
\ No newline at end of file diff --git a/api/.env.example b/api/.env.example new file mode 100644 index 0000000..8dc873f --- /dev/null +++ b/api/.env.example @@ -0,0 +1,4 @@ +PORT=3333 +DATABASE_URL=yaba.db +CORS_ORIGIN=true +NODE_ENV=development diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..2341bbb --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,9 @@ +node_modules +*.db* +.env + +# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb +# The following patterns were generated by expo-cli + +expo-env.d.ts +# @end expo-cli
\ No newline at end of file diff --git a/api/bun.lock b/api/bun.lock new file mode 100644 index 0000000..f4166c0 --- /dev/null +++ b/api/bun.lock @@ -0,0 +1,398 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "yaba-api", + "dependencies": { + "@fastify/cors": "^10.0.2", + "@fastify/jwt": "^10.2.1", + "bcryptjs": "^3.0.3", + "drizzle-orm": "^0.44.2", + "fastify": "^5.3.2", + "zod": "^3.25.76", + }, + "devDependencies": { + "@libsql/client": "^0.17.4", + "@types/bcryptjs": "^3.0.0", + "@types/bun": "^1.2.18", + "bun-types": "^1.2.18", + "drizzle-kit": "^0.31.4", + "typescript": "^5.8.3", + }, + }, + }, + "packages": { + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@fastify/ajv-compiler": ["@fastify/ajv-compiler@4.0.5", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A=="], + + "@fastify/cors": ["@fastify/cors@10.1.0", "", { "dependencies": { "fastify-plugin": "^5.0.0", "mnemonist": "0.40.0" } }, "sha512-MZyBCBJtII60CU9Xme/iE4aEy8G7QpzGR8zkdXZkDFt7ElEMachbE61tfhAG/bvSaULlqlf0huMT12T7iqEmdQ=="], + + "@fastify/error": ["@fastify/error@4.2.0", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="], + + "@fastify/fast-json-stringify-compiler": ["@fastify/fast-json-stringify-compiler@5.1.0", "", { "dependencies": { "fast-json-stringify": "^7.0.0" } }, "sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw=="], + + "@fastify/forwarded": ["@fastify/forwarded@3.0.2", "", {}, "sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA=="], + + "@fastify/jwt": ["@fastify/jwt@10.2.1", "", { "dependencies": { "@fastify/error": "^4.2.0", "@lukeed/ms": "^2.0.2", "fast-jwt": "^6.2.4", "fastify-plugin": "^6.0.0", "steed": "^1.1.3" } }, "sha512-8FHalB082a6HFj+neP9TdJZrUa65Aq6mtozVjd3vH3Yy7hmjRJUDKLPdxYRYF9S077JeN9RgimsautqnijbDlQ=="], + + "@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.2.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="], + + "@fastify/proxy-addr": ["@fastify/proxy-addr@5.1.0", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw=="], + + "@libsql/client": ["@libsql/client@0.17.4", "", { "dependencies": { "@libsql/core": "^0.17.4", "@libsql/hrana-client": "^0.10.0", "js-base64": "^3.7.5", "libsql": "^0.5.28", "promise-limit": "^2.7.0" } }, "sha512-lYayFWasDV78A+TjlEhr6ubb3odBV6OHjb+wdp8VQcyWWAEIjuwbCHaraEUS4m4yWoo0BvZo96It4VdzZRmRWw=="], + + "@libsql/core": ["@libsql/core@0.17.4", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-LqF9gIvnJ38nmAH1y/ChizHqDO/MO1wLgA96XrraulEEbqXxLjleSH92YWTolbuJKgPUmGu4aJk9W3UnAcxLOQ=="], + + "@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.5.29", "", { "os": "darwin", "cpu": "arm64" }, "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A=="], + + "@libsql/darwin-x64": ["@libsql/darwin-x64@0.5.29", "", { "os": "darwin", "cpu": "x64" }, "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ=="], + + "@libsql/hrana-client": ["@libsql/hrana-client@0.10.0", "", { "dependencies": { "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5" } }, "sha512-OoA4EMqRAC7kn7V2P6EQqRcpZf2W+AjsNIyCizBg339Tq/aMC7sRnzs3SklderhmQWAqEzvv8A2vhxVmWpkVvw=="], + + "@libsql/isomorphic-ws": ["@libsql/isomorphic-ws@0.1.5", "", { "dependencies": { "@types/ws": "^8.5.4", "ws": "^8.13.0" } }, "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg=="], + + "@libsql/linux-arm-gnueabihf": ["@libsql/linux-arm-gnueabihf@0.5.29", "", { "os": "linux", "cpu": "arm" }, "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ=="], + + "@libsql/linux-arm-musleabihf": ["@libsql/linux-arm-musleabihf@0.5.29", "", { "os": "linux", "cpu": "arm" }, "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg=="], + + "@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.5.29", "", { "os": "linux", "cpu": "arm64" }, "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w=="], + + "@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.5.29", "", { "os": "linux", "cpu": "arm64" }, "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg=="], + + "@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.5.29", "", { "os": "linux", "cpu": "x64" }, "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg=="], + + "@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.5.29", "", { "os": "linux", "cpu": "x64" }, "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w=="], + + "@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.5.29", "", { "os": "win32", "cpu": "x64" }, "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg=="], + + "@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="], + + "@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="], + + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + + "@types/bcryptjs": ["@types/bcryptjs@3.0.0", "", { "dependencies": { "bcryptjs": "*" } }, "sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg=="], + + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="], + + "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "asn1.js": ["asn1.js@5.4.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "safer-buffer": "^2.1.0" } }, "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + + "avvio": ["avvio@9.3.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A=="], + + "bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="], + + "bn.js": ["bn.js@4.12.5", "", {}, "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], + + "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], + + "drizzle-orm": ["drizzle-orm@0.44.7", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stringify": ["fast-json-stringify@7.0.1", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^4.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA=="], + + "fast-jwt": ["fast-jwt@6.3.1", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "asn1.js": "^5.4.1", "ecdsa-sig-formatter": "^1.0.11", "mnemonist": "^0.40.0", "safe-regex2": "^5.1.0" } }, "sha512-9LFiA4oIx7CjPjep7G5zrfM+UiA+60lIsYH/6I5kCwGBa06WUtdwJ3F3m8Z79bxdUVz/lW30A/NsWWStqoJKrw=="], + + "fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="], + + "fast-uri": ["fast-uri@3.1.5", "", {}, "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="], + + "fastfall": ["fastfall@1.5.1", "", { "dependencies": { "reusify": "^1.0.0" } }, "sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q=="], + + "fastify": ["fastify@5.11.2", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^7.0.0", "find-my-way": "^9.6.0", "light-my-request": "^6.0.0", "pino": "^9.14.0 || ^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-i/eJG7nXR9OkbFgoX4jFiPOHoRq0rXqDACVAXELh5Fdg6BFBErIVZ8MyibGCwup9Go1pYrxZJ0ogIub8vVdEcQ=="], + + "fastify-plugin": ["fastify-plugin@5.1.0", "", {}, "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw=="], + + "fastparallel": ["fastparallel@2.4.1", "", { "dependencies": { "reusify": "^1.0.4", "xtend": "^4.0.2" } }, "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fastseries": ["fastseries@1.7.2", "", { "dependencies": { "reusify": "^1.0.0", "xtend": "^4.0.0" } }, "sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ=="], + + "find-my-way": ["find-my-way@9.7.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-tsconfig": ["get-tsconfig@4.14.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ipaddr.js": ["ipaddr.js@2.4.0", "", {}, "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ=="], + + "js-base64": ["js-base64@3.9.2", "", {}, "sha512-6zayE8QlUdiweYI6cETD/XBSqFcoCUlufn/29PJR99r82x1yDnIprRca0YvAYpAW+ez0GuQkVBC6xG5QkD7OjA=="], + + "json-schema-ref-resolver": ["json-schema-ref-resolver@3.0.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "libsql": ["libsql@0.5.29", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.29", "@libsql/darwin-x64": "0.5.29", "@libsql/linux-arm-gnueabihf": "0.5.29", "@libsql/linux-arm-musleabihf": "0.5.29", "@libsql/linux-arm64-gnu": "0.5.29", "@libsql/linux-arm64-musl": "0.5.29", "@libsql/linux-x64-gnu": "0.5.29", "@libsql/linux-x64-musl": "0.5.29", "@libsql/win32-x64-msvc": "0.5.29" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg=="], + + "light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="], + + "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="], + + "mnemonist": ["mnemonist@0.40.0", "", { "dependencies": { "obliterator": "^2.0.4" } }, "sha512-kdd8AFNig2AD5Rkih7EPCXhu/iMvwevQFX/uEiGhZyPZi7fHqOoF4V4kHLpCfysxXMgQ4B52kdPMCwARshKvEg=="], + + "obliterator": ["obliterator@2.0.5", "", {}, "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw=="], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + + "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="], + + "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], + + "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], + + "process-warning": ["process-warning@5.1.0", "", {}, "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw=="], + + "promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safe-regex2": ["safe-regex2@5.1.1", "", { "dependencies": { "ret": "~0.5.0" }, "bin": { "safe-regex2": "bin/safe-regex2.js" } }, "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + + "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "steed": ["steed@1.1.3", "", { "dependencies": { "fastfall": "^1.5.0", "fastparallel": "^2.2.0", "fastq": "^1.3.0", "fastseries": "^1.7.0", "reusify": "^1.0.0" } }, "sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA=="], + + "thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="], + + "toad-cache": ["toad-cache@3.7.4", "", {}, "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg=="], + + "tsx": ["tsx@4.23.5", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-rw55FUaqOoI7RvlQwLbhO4nSDApnQ4/CykPuiQ/EPvtrX3WA9Ig55jIt9VvbBJbzJuj12ueRu4PMZ2SxPVbihg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + + "@fastify/jwt/fastify-plugin": ["fastify-plugin@6.0.0", "", {}, "sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg=="], + + "fast-json-stringify/fast-uri": ["fast-uri@4.1.2", "", {}, "sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw=="], + + "light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], + + "thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="], + + "tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + } +} diff --git a/api/drizzle.config.ts b/api/drizzle.config.ts new file mode 100644 index 0000000..7ab2647 --- /dev/null +++ b/api/drizzle.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + // Localização dos arquivos de schema + schema: './src/db/schema/index.ts', + + // Onde o Drizzle Kit vai gerar as migrations + out: './src/db/migrations', + + // Driver SQLite + dialect: 'sqlite', + + dbCredentials: { + url: process.env.DATABASE_URL ?? 'yaba.db', + }, + + // Exibe SQL das migrations no console durante o desenvolvimento + verbose: true, + + // Impede destruição acidental de dados em produção + strict: true, +}); diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000..aa50cff --- /dev/null +++ b/api/package.json @@ -0,0 +1,30 @@ +{ + "name": "yaba-api", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun run --watch src/server.ts", + "start": "bun run src/server.ts", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio", + "db:push": "drizzle-kit push" + }, + "dependencies": { + "@fastify/cors": "^10.0.2", + "@fastify/jwt": "^10.2.1", + "bcryptjs": "^3.0.3", + "drizzle-orm": "^0.44.2", + "fastify": "^5.3.2", + "zod": "^3.25.76" + }, + "devDependencies": { + "@libsql/client": "^0.17.4", + "@types/bcryptjs": "^3.0.0", + "@types/bun": "^1.2.18", + "bun-types": "^1.2.18", + "drizzle-kit": "^0.31.4", + "typescript": "^5.8.3" + } +} diff --git a/api/src/db/client.ts b/api/src/db/client.ts new file mode 100644 index 0000000..5e9673f --- /dev/null +++ b/api/src/db/client.ts @@ -0,0 +1,23 @@ +import { Database } from 'bun:sqlite'; +import { drizzle } from 'drizzle-orm/bun-sqlite'; +import * as schema from './schema'; + +/** + * Instância única do banco de dados SQLite via Drizzle ORM. + * Utiliza o driver nativo do Bun para máxima performance. + * + * O caminho do arquivo pode ser configurado via variável de ambiente DATABASE_URL. + * Quando não definida, usa 'yaba.db' na raiz do projeto. + */ +const sqlite = new Database(process.env.DATABASE_URL ?? 'yaba.db'); + +/** + * Habilita WAL mode para melhor performance em leituras concorrentes. + * @see https://www.sqlite.org/wal.html + */ +sqlite.exec('PRAGMA journal_mode = WAL;'); +sqlite.exec('PRAGMA foreign_keys = ON;'); + +export const db = drizzle(sqlite, { schema }); + +export type DB = typeof db; diff --git a/api/src/db/schema/incomes.ts b/api/src/db/schema/incomes.ts new file mode 100644 index 0000000..f502cb9 --- /dev/null +++ b/api/src/db/schema/incomes.ts @@ -0,0 +1,51 @@ +import { sql } from 'drizzle-orm'; +import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { users } from './users'; + +/** + * Tabela de rendas/salários. + * Armazena o histórico de salários por mês/ano, permitindo que o usuário + * tenha registros diferentes para cada período (ex: aumento de salário em julho). + */ +export const incomes = sqliteTable('incomes', { + id: integer('id').primaryKey({ autoIncrement: true }), + + user_id: integer('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + + /** Salário bruto do período (em R$) */ + gross_salary: real('gross_salary').notNull(), + + /** + * Salário líquido calculado automaticamente pelo app (INSS + IRRF). + * Armazenado para fins de histórico e auditoria. + */ + calculated_net_salary: real('calculated_net_salary').notNull(), + + /** + * Salário líquido efetivo usado no cálculo do saldo. + * Pode diferir do calculado caso o usuário tenha feito um override manual. + */ + effective_net_salary: real('effective_net_salary').notNull(), + + /** Mês de referência (1-12) */ + reference_month: integer('reference_month').notNull(), + + /** Ano de referência (ex: 2025) */ + reference_year: integer('reference_year').notNull(), + + /** Observações opcionais (ex: "13º salário", "PLR") */ + description: text('description'), + + created_at: text('created_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + + updated_at: text('updated_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), +}); + +export type Income = typeof incomes.$inferSelect; +export type NewIncome = typeof incomes.$inferInsert; diff --git a/api/src/db/schema/index.ts b/api/src/db/schema/index.ts new file mode 100644 index 0000000..72cf261 --- /dev/null +++ b/api/src/db/schema/index.ts @@ -0,0 +1,11 @@ +/** + * Ponto central de exportação de todos os schemas do Drizzle. + * Importe sempre a partir deste arquivo para evitar imports circulares. + * + * Uso: + * import { users, incomes, recurringExpenses, oneTimeExpenses } from '@/db/schema'; + */ +export * from './users'; +export * from './incomes'; +export * from './recurring-expenses'; +export * from './one-time-expenses'; diff --git a/api/src/db/schema/one-time-expenses.ts b/api/src/db/schema/one-time-expenses.ts new file mode 100644 index 0000000..10c9c01 --- /dev/null +++ b/api/src/db/schema/one-time-expenses.ts @@ -0,0 +1,53 @@ +import { sql } from 'drizzle-orm'; +import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { users } from './users'; + +/** + * Tabela de gastos avulsos (despesas pontuais). + * Ex: Cinema, iFood, Uber, Farmácia. + * Cada registro representa um gasto único em uma data específica. + * O valor é subtraído do saldo disponível do mês em que a despesa ocorreu. + */ +export const oneTimeExpenses = sqliteTable('one_time_expenses', { + id: integer('id').primaryKey({ autoIncrement: true }), + + user_id: integer('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + + /** Nome/descrição do gasto (ex: "Cinema com a família", "iFood jantar") */ + name: text('name').notNull(), + + /** Valor do gasto (em R$) */ + amount: real('amount').notNull(), + + /** + * Categoria do gasto para organização e futuros relatórios. + * Ex: "alimentação", "lazer", "saúde", "transporte", "educação", "outros" + */ + category: text('category').notNull().default('outros'), + + /** Tipo do lançamento: 'expense' (despesa) ou 'income' (receita) */ + type: text('type').notNull().default('expense'), + + /** + * Data em que o gasto ocorreu. + * Formato ISO 8601 (YYYY-MM-DD). Usado para atribuir o gasto ao mês correto. + * Default: data atual no momento da inserção. + */ + expense_date: text('expense_date').notNull().default(sql`(date('now'))`), + + /** Observações opcionais (ex: "Split 50/50 com João") */ + notes: text('notes'), + + created_at: text('created_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + + updated_at: text('updated_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), +}); + +export type OneTimeExpense = typeof oneTimeExpenses.$inferSelect; +export type NewOneTimeExpense = typeof oneTimeExpenses.$inferInsert; diff --git a/api/src/db/schema/recurring-expenses.ts b/api/src/db/schema/recurring-expenses.ts new file mode 100644 index 0000000..8a0a1df --- /dev/null +++ b/api/src/db/schema/recurring-expenses.ts @@ -0,0 +1,69 @@ +import { sql } from 'drizzle-orm'; +import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { users } from './users'; + +/** + * Tabela de gastos recorrentes (despesas fixas mensais). + * Ex: Aluguel, Internet, Streaming, Academia. + * Estes gastos são deduzidos automaticamente do saldo todo mês, + * sem precisar recadastrar. + */ +export const recurringExpenses = sqliteTable('recurring_expenses', { + id: integer('id').primaryKey({ autoIncrement: true }), + + user_id: integer('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + + /** Nome/descrição do gasto (ex: "Aluguel", "Netflix") */ + name: text('name').notNull(), + + /** Valor mensal do gasto (em R$) */ + amount: real('amount').notNull(), + + /** + * Categoria do gasto para organização e futuros relatórios. + * Ex: "moradia", "alimentação", "lazer", "saúde", "transporte", "educação", "outros" + */ + category: text('category').notNull().default('outros'), + + /** Tipo do lançamento: 'expense' (despesa) ou 'income' (receita) */ + type: text('type').notNull().default('expense'), + + /** + * Dia do mês em que a despesa costuma vencer (1-31). + * Opcional — usado apenas para informação ao usuário. + */ + due_day: integer('due_day'), + + /** + * Indica se o gasto está ativo. + * Quando false, não é deduzido do saldo (útil para pausar sem deletar). + * Armazenado como integer (0 = false, 1 = true) por limitação do SQLite. + */ + is_active: integer('is_active', { mode: 'boolean' }).notNull().default(true), + + /** + * Data a partir da qual este gasto passa a ser contabilizado. + * Formato ISO 8601 (YYYY-MM-DD). + */ + starts_at: text('starts_at').notNull().default(sql`(date('now'))`), + + /** + * Data em que este gasto deixa de ser contabilizado (opcional). + * Útil para despesas com prazo definido (ex: parcelamento de 12x). + * Formato ISO 8601 (YYYY-MM-DD). + */ + ends_at: text('ends_at'), + + created_at: text('created_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + + updated_at: text('updated_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), +}); + +export type RecurringExpense = typeof recurringExpenses.$inferSelect; +export type NewRecurringExpense = typeof recurringExpenses.$inferInsert; diff --git a/api/src/db/schema/users.ts b/api/src/db/schema/users.ts new file mode 100644 index 0000000..636ad2d --- /dev/null +++ b/api/src/db/schema/users.ts @@ -0,0 +1,37 @@ +import { sql } from 'drizzle-orm'; +import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +/** + * Tabela de usuários. + * Por ora, suporta um único usuário local (single-user app). + * O campo `net_salary_override` permite que o usuário edite manualmente + * o salário líquido calculado, para cobrir descontos extras (plano de saúde, VT, etc.). + */ +export const users = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + + name: text('name').notNull(), + email: text('email').unique().notNull(), + password_hash: text('password_hash').notNull(), + + /** Salário bruto informado pelo usuário (em R$) */ + gross_salary: real('gross_salary').notNull().default(0), + + /** + * Override manual do salário líquido. + * Quando NULL, o app usa o valor calculado automaticamente (INSS + IRRF). + * Quando preenchido, este valor é usado como base para o saldo disponível. + */ + net_salary_override: real('net_salary_override'), + + created_at: text('created_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + + updated_at: text('updated_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), +}); + +export type User = typeof users.$inferSelect; +export type NewUser = typeof users.$inferInsert; diff --git a/api/src/modules/auth/auth.routes.ts b/api/src/modules/auth/auth.routes.ts new file mode 100644 index 0000000..d388694 --- /dev/null +++ b/api/src/modules/auth/auth.routes.ts @@ -0,0 +1,40 @@ +import type { FastifyPluginAsync } from 'fastify'; +import { ZodError } from 'zod'; +import { loginSchema, registerSchema } from './auth.schema'; +import * as authService from './auth.service'; + +const authRoutes: FastifyPluginAsync = async (fastify) => { + fastify.post('/auth/register', async (request, reply) => { + try { + const body = registerSchema.parse(request.body); + const user = await authService.registerUser(body); + + const token = fastify.jwt.sign({ sub: user.id }); + + return reply.status(201).send({ data: { user, token } }); + } catch (err: any) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + return reply.status(400).send({ error: err.message }); + } + }); + + fastify.post('/auth/login', async (request, reply) => { + try { + const body = loginSchema.parse(request.body); + const user = await authService.loginUser(body); + + const token = fastify.jwt.sign({ sub: user.id }); + + return reply.send({ data: { user, token } }); + } catch (err: any) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + return reply.status(401).send({ error: err.message }); + } + }); +}; + +export default authRoutes; diff --git a/api/src/modules/auth/auth.schema.ts b/api/src/modules/auth/auth.schema.ts new file mode 100644 index 0000000..550c670 --- /dev/null +++ b/api/src/modules/auth/auth.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +export const registerSchema = z.object({ + name: z.string().min(2, 'Nome deve ter pelo menos 2 caracteres'), + email: z.string().email('Email inválido'), + password: z.string().min(6, 'Senha deve ter pelo menos 6 caracteres'), + gross_salary: z.coerce.number().min(0).optional(), +}); + +export const loginSchema = z.object({ + email: z.string().email('Email inválido'), + password: z.string().min(6, 'Senha inválida'), +}); + +export type RegisterData = z.infer<typeof registerSchema>; +export type LoginData = z.infer<typeof loginSchema>; diff --git a/api/src/modules/auth/auth.service.ts b/api/src/modules/auth/auth.service.ts new file mode 100644 index 0000000..c874b05 --- /dev/null +++ b/api/src/modules/auth/auth.service.ts @@ -0,0 +1,41 @@ +import bcrypt from 'bcryptjs'; +import { eq } from 'drizzle-orm'; +import { db } from '../../db/client'; +import { users } from '../../db/schema/users'; +import type { LoginData, RegisterData } from './auth.schema'; + +export async function registerUser(data: RegisterData) { + const existingUser = await db.select().from(users).where(eq(users.email, data.email)).limit(1); + if (existingUser.length > 0) { + throw new Error('Email já está em uso.'); + } + + const salt = await bcrypt.genSalt(10); + const passwordHash = await bcrypt.hash(data.password, salt); + + const [newUser] = await db.insert(users).values({ + name: data.name, + email: data.email, + password_hash: passwordHash, + gross_salary: data.gross_salary || 0, + }).returning(); + + const { password_hash, ...userWithoutPassword } = newUser; + return userWithoutPassword; +} + +export async function loginUser(data: LoginData) { + const [user] = await db.select().from(users).where(eq(users.email, data.email)).limit(1); + + if (!user) { + throw new Error('Email ou senha inválidos.'); + } + + const isValid = await bcrypt.compare(data.password, user.password_hash); + if (!isValid) { + throw new Error('Email ou senha inválidos.'); + } + + const { password_hash, ...userWithoutPassword } = user; + return userWithoutPassword; +} diff --git a/api/src/modules/dashboard/dashboard.routes.ts b/api/src/modules/dashboard/dashboard.routes.ts new file mode 100644 index 0000000..d579ef2 --- /dev/null +++ b/api/src/modules/dashboard/dashboard.routes.ts @@ -0,0 +1,50 @@ +import type { FastifyPluginAsync } from 'fastify'; +import { z } from 'zod'; +import * as dashboardService from './dashboard.service'; + +const dashboardQuerySchema = z.object({ + month: z.coerce.number().int().min(1).max(12).optional(), + year: z.coerce.number().int().min(2020).optional(), +}); + +const dashboardParamsSchema = z.object({ + userId: z.coerce.number().int().positive(), +}); + +const dashboardRoutes: FastifyPluginAsync = async (fastify) => { + /** + * GET /users/:userId/dashboard + * Retorna o resumo financeiro completo do mês. + * + * Query params (opcionais): + * ?month=6&year=2025 → mês/ano específico + * Sem params → mês/ano atual + * + * Resposta: + * - Dados do usuário e salário + * - Lista de gastos recorrentes ativos + * - Lista de gastos avulsos do mês + * - Saldo disponível e percentual comprometido + */ + fastify.get<{ Params: { userId: string }; Querystring: Record<string, string> }>( + '/users/:userId/dashboard', + async (request, reply) => { + const { userId } = dashboardParamsSchema.parse(request.params); + const query = dashboardQuerySchema.parse(request.query); + + const now = new Date(); + const month = query.month ?? now.getMonth() + 1; + const year = query.year ?? now.getFullYear(); + + const data = await dashboardService.getDashboard(userId, month, year); + + if (!data) { + return reply.status(404).send({ error: 'Usuário não encontrado.' }); + } + + return reply.send({ data }); + }, + ); +}; + +export default dashboardRoutes; diff --git a/api/src/modules/dashboard/dashboard.service.ts b/api/src/modules/dashboard/dashboard.service.ts new file mode 100644 index 0000000..78b1d1b --- /dev/null +++ b/api/src/modules/dashboard/dashboard.service.ts @@ -0,0 +1,177 @@ +import { and, eq, gte, lte, sql } from 'drizzle-orm'; +import { db } from '@/db/client'; +import { incomes, oneTimeExpenses, recurringExpenses, users } from '@/db/schema'; +import { calcularSalarioLiquido } from '@/utils/tax-calculator'; + +export interface DashboardData { + user: { + id: number; + name: string; + gross_salary: number; + net_salary_override: number | null; + }; + period: { + month: number; + year: number; + }; + salary: { + gross: number; + calculated_net: number; + effective_net: number; + inss_discount: number; + irrf_discount: number; + has_override: boolean; + }; + expenses: { + recurring_total: number; + one_time_total: number; + total: number; + recurring_list: { id: number; name: string; amount: number; category: string }[]; + one_time_list: { + id: number; + name: string; + amount: number; + category: string; + expense_date: string; + }[]; + }; + balance: { + available: number; + /** Percentual do salário líquido comprometido com despesas (0-100) */ + expense_ratio: number; + }; +} + +export async function getDashboard( + userId: number, + referenceMonth: number, + referenceYear: number, +): Promise<DashboardData | null> { + // 1. Busca o usuário + const [user] = await db + .select() + .from(users) + .where(eq(users.id, userId)) + .limit(1); + + if (!user) return null; + + // 2. Calcula o salário via tabela INSS/IRRF + const taxResult = calcularSalarioLiquido(user.gross_salary); + + // 3. Salário efetivo: usa override do usuário ou o calculado + const effectiveNet = user.net_salary_override ?? taxResult.salarioLiquidoCalculado; + + // 4. Tenta encontrar um registro de renda para o mês/ano pedido + const [incomeRecord] = await db + .select() + .from(incomes) + .where( + and( + eq(incomes.user_id, userId), + eq(incomes.reference_month, referenceMonth), + eq(incomes.reference_year, referenceYear), + ), + ) + .limit(1); + + // Se existir registro de renda para o mês, usa os valores dele + const salaryData = incomeRecord + ? { + gross: incomeRecord.gross_salary, + calculated_net: incomeRecord.calculated_net_salary, + effective_net: incomeRecord.effective_net_salary, + inss_discount: + incomeRecord.gross_salary - incomeRecord.calculated_net_salary - 0, // simplificado + irrf_discount: 0, + has_override: incomeRecord.effective_net_salary !== incomeRecord.calculated_net_salary, + } + : { + gross: user.gross_salary, + calculated_net: taxResult.salarioLiquidoCalculado, + effective_net: effectiveNet, + inss_discount: taxResult.descontoINSS, + irrf_discount: taxResult.descontoIRRF, + has_override: user.net_salary_override !== null, + }; + + // 5. Busca gastos recorrentes ATIVOS e vigentes no mês/ano pedido + const refDateStr = `${referenceYear}-${String(referenceMonth).padStart(2, '0')}-01`; + const lastDay = new Date(referenceYear, referenceMonth, 0).getDate(); + const refDateEndStr = `${referenceYear}-${String(referenceMonth).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + + const recurringList = await db + .select() + .from(recurringExpenses) + .where( + and( + eq(recurringExpenses.user_id, userId), + eq(recurringExpenses.is_active, true), + lte(recurringExpenses.starts_at, refDateEndStr), + // ends_at IS NULL ou ends_at >= início do mês + sql`(${recurringExpenses.ends_at} IS NULL OR ${recurringExpenses.ends_at} >= ${refDateStr})`, + ), + ); + + // 6. Busca gastos avulsos do mês/ano pedido + const oneTimeList = await db + .select() + .from(oneTimeExpenses) + .where( + and( + eq(oneTimeExpenses.user_id, userId), + gte(oneTimeExpenses.expense_date, refDateStr), + lte(oneTimeExpenses.expense_date, refDateEndStr), + ), + ) + .orderBy(oneTimeExpenses.expense_date); + + // 7. Calcula totais + const recurringTotal = recurringList.reduce((sum, e) => { + return sum + (e.type === 'income' ? -e.amount : e.amount); + }, 0); + const oneTimeTotal = oneTimeList.reduce((sum, e) => { + return sum + (e.type === 'income' ? -e.amount : e.amount); + }, 0); + const totalExpenses = parseFloat((recurringTotal + oneTimeTotal).toFixed(2)); + const available = parseFloat((salaryData.effective_net - totalExpenses).toFixed(2)); + const expenseRatio = + salaryData.effective_net > 0 + ? parseFloat(((totalExpenses / salaryData.effective_net) * 100).toFixed(1)) + : 0; + + return { + user: { + id: user.id, + name: user.name, + gross_salary: user.gross_salary, + net_salary_override: user.net_salary_override, + }, + period: { month: referenceMonth, year: referenceYear }, + salary: salaryData, + expenses: { + recurring_total: parseFloat(recurringTotal.toFixed(2)), + one_time_total: parseFloat(oneTimeTotal.toFixed(2)), + total: totalExpenses, + recurring_list: recurringList.map((e) => ({ + id: e.id, + name: e.name, + amount: e.amount, + category: e.category, + type: e.type, + })), + one_time_list: oneTimeList.map((e) => ({ + id: e.id, + name: e.name, + amount: e.amount, + category: e.category, + type: e.type, + expense_date: e.expense_date, + })), + }, + balance: { + available, + expense_ratio: expenseRatio, + }, + }; +} diff --git a/api/src/modules/incomes/incomes.routes.ts b/api/src/modules/incomes/incomes.routes.ts new file mode 100644 index 0000000..7932d18 --- /dev/null +++ b/api/src/modules/incomes/incomes.routes.ts @@ -0,0 +1,97 @@ +import type { FastifyPluginAsync } from 'fastify'; +import { ZodError } from 'zod'; +import { + createIncomeSchema, + incomeParamsSchema, + incomeQuerySchema, + incomeUserParamsSchema, + updateIncomeSchema, +} from './incomes.schema'; +import * as incomesService from './incomes.service'; + +const incomesRoutes: FastifyPluginAsync = async (fastify) => { + /** + * GET /users/:userId/incomes + * Lista todas as rendas de um usuário. + * Query params: ?month=6&year=2025 + */ + fastify.get<{ Params: { userId: string }; Querystring: Record<string, string> }>( + '/users/:userId/incomes', + async (request, reply) => { + const { userId } = incomeUserParamsSchema.parse(request.params); + const query = incomeQuerySchema.parse(request.query); + const result = await incomesService.findIncomesByUser(userId, query); + return reply.send({ data: result }); + }, + ); + + /** GET /users/:userId/incomes/:id — Busca uma renda específica */ + fastify.get<{ Params: { userId: string; id: string } }>( + '/users/:userId/incomes/:id', + async (request, reply) => { + const { userId, id } = incomeParamsSchema.parse(request.params); + const income = await incomesService.findIncomeById(userId, id); + + if (!income) { + return reply.status(404).send({ error: 'Renda não encontrada.' }); + } + return reply.send({ data: income }); + }, + ); + + /** POST /users/:userId/incomes — Registra a renda de um mês */ + fastify.post<{ Params: { userId: string } }>( + '/users/:userId/incomes', + async (request, reply) => { + try { + const { userId } = incomeUserParamsSchema.parse(request.params); + const body = createIncomeSchema.parse(request.body); + const income = await incomesService.createIncome(userId, body); + return reply.status(201).send({ data: income }); + } catch (err) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + throw err; + } + }, + ); + + /** PUT /users/:userId/incomes/:id — Atualiza uma renda */ + fastify.put<{ Params: { userId: string; id: string } }>( + '/users/:userId/incomes/:id', + async (request, reply) => { + try { + const { userId, id } = incomeParamsSchema.parse(request.params); + const body = updateIncomeSchema.parse(request.body); + const income = await incomesService.updateIncome(userId, id, body); + + if (!income) { + return reply.status(404).send({ error: 'Renda não encontrada.' }); + } + return reply.send({ data: income }); + } catch (err) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + throw err; + } + }, + ); + + /** DELETE /users/:userId/incomes/:id — Remove uma renda */ + fastify.delete<{ Params: { userId: string; id: string } }>( + '/users/:userId/incomes/:id', + async (request, reply) => { + const { userId, id } = incomeParamsSchema.parse(request.params); + const deleted = await incomesService.deleteIncome(userId, id); + + if (!deleted) { + return reply.status(404).send({ error: 'Renda não encontrada.' }); + } + return reply.status(200).send({ data: deleted, message: 'Renda removida com sucesso.' }); + }, + ); +}; + +export default incomesRoutes; diff --git a/api/src/modules/incomes/incomes.schema.ts b/api/src/modules/incomes/incomes.schema.ts new file mode 100644 index 0000000..cc661fd --- /dev/null +++ b/api/src/modules/incomes/incomes.schema.ts @@ -0,0 +1,38 @@ +import { z } from 'zod'; + +export const createIncomeSchema = z.object({ + gross_salary: z.number().positive('Salário bruto deve ser positivo'), + /** + * Salário líquido efetivo (após override do usuário). + * Deve ser enviado pelo app após o cálculo de INSS/IRRF. + */ + calculated_net_salary: z.number().nonnegative(), + effective_net_salary: z.number().nonnegative(), + reference_month: z.number().int().min(1).max(12), + reference_year: z.number().int().min(2020).max(2100), + description: z.string().max(255).nullable().optional(), +}); + +export const updateIncomeSchema = z.object({ + gross_salary: z.number().positive().optional(), + calculated_net_salary: z.number().nonnegative().optional(), + effective_net_salary: z.number().nonnegative().optional(), + description: z.string().max(255).nullable().optional(), +}); + +export const incomeParamsSchema = z.object({ + userId: z.coerce.number().int().positive(), + id: z.coerce.number().int().positive(), +}); + +export const incomeUserParamsSchema = z.object({ + userId: z.coerce.number().int().positive(), +}); + +export const incomeQuerySchema = z.object({ + month: z.coerce.number().int().min(1).max(12).optional(), + year: z.coerce.number().int().min(2020).optional(), +}); + +export type CreateIncomeInput = z.infer<typeof createIncomeSchema>; +export type UpdateIncomeInput = z.infer<typeof updateIncomeSchema>; diff --git a/api/src/modules/incomes/incomes.service.ts b/api/src/modules/incomes/incomes.service.ts new file mode 100644 index 0000000..0a9ea84 --- /dev/null +++ b/api/src/modules/incomes/incomes.service.ts @@ -0,0 +1,83 @@ +import { and, eq } from 'drizzle-orm'; +import { db } from '@/db/client'; +import { incomes } from '@/db/schema'; +import type { CreateIncomeInput, UpdateIncomeInput } from './incomes.schema'; + +export async function findIncomesByUser( + userId: number, + filters?: { month?: number; year?: number }, +) { + const conditions = [eq(incomes.user_id, userId)]; + + if (filters?.month !== undefined) { + conditions.push(eq(incomes.reference_month, filters.month)); + } + if (filters?.year !== undefined) { + conditions.push(eq(incomes.reference_year, filters.year)); + } + + return db + .select() + .from(incomes) + .where(and(...conditions)) + .orderBy(incomes.reference_year, incomes.reference_month); +} + +export async function findIncomeById(userId: number, id: number) { + const result = await db + .select() + .from(incomes) + .where(and(eq(incomes.user_id, userId), eq(incomes.id, id))) + .limit(1); + return result[0] ?? null; +} + +export async function createIncome(userId: number, data: CreateIncomeInput) { + const [created] = await db + .insert(incomes) + .values({ + user_id: userId, + gross_salary: data.gross_salary, + calculated_net_salary: data.calculated_net_salary, + effective_net_salary: data.effective_net_salary, + reference_month: data.reference_month, + reference_year: data.reference_year, + description: data.description ?? null, + }) + .returning(); + return created; +} + +export async function updateIncome( + userId: number, + id: number, + data: UpdateIncomeInput, +) { + const now = new Date().toISOString(); + + const [updated] = await db + .update(incomes) + .set({ + ...(data.gross_salary !== undefined && { gross_salary: data.gross_salary }), + ...(data.calculated_net_salary !== undefined && { + calculated_net_salary: data.calculated_net_salary, + }), + ...(data.effective_net_salary !== undefined && { + effective_net_salary: data.effective_net_salary, + }), + ...(data.description !== undefined && { description: data.description }), + updated_at: now, + }) + .where(and(eq(incomes.user_id, userId), eq(incomes.id, id))) + .returning(); + + return updated ?? null; +} + +export async function deleteIncome(userId: number, id: number) { + const [deleted] = await db + .delete(incomes) + .where(and(eq(incomes.user_id, userId), eq(incomes.id, id))) + .returning(); + return deleted ?? null; +} diff --git a/api/src/modules/one-time-expenses/one-time-expenses.routes.ts b/api/src/modules/one-time-expenses/one-time-expenses.routes.ts new file mode 100644 index 0000000..0658cfc --- /dev/null +++ b/api/src/modules/one-time-expenses/one-time-expenses.routes.ts @@ -0,0 +1,97 @@ +import type { FastifyPluginAsync } from 'fastify'; +import { ZodError } from 'zod'; +import { + createOneTimeExpenseSchema, + oneTimeExpenseParamsSchema, + oneTimeExpenseQuerySchema, + oneTimeExpenseUserParamsSchema, + updateOneTimeExpenseSchema, +} from './one-time-expenses.schema'; +import * as oneTimeService from './one-time-expenses.service'; + +const oneTimeExpensesRoutes: FastifyPluginAsync = async (fastify) => { + /** + * GET /users/:userId/one-time-expenses + * Lista gastos avulsos do usuário. + * Query params: ?month=6&year=2025 + */ + fastify.get<{ Params: { userId: string }; Querystring: Record<string, string> }>( + '/users/:userId/one-time-expenses', + async (request, reply) => { + const { userId } = oneTimeExpenseUserParamsSchema.parse(request.params); + const query = oneTimeExpenseQuerySchema.parse(request.query); + const result = await oneTimeService.findOneTimeExpensesByUser(userId, query); + return reply.send({ data: result }); + }, + ); + + /** GET /users/:userId/one-time-expenses/:id — Busca um gasto avulso específico */ + fastify.get<{ Params: { userId: string; id: string } }>( + '/users/:userId/one-time-expenses/:id', + async (request, reply) => { + const { userId, id } = oneTimeExpenseParamsSchema.parse(request.params); + const expense = await oneTimeService.findOneTimeExpenseById(userId, id); + + if (!expense) { + return reply.status(404).send({ error: 'Gasto avulso não encontrado.' }); + } + return reply.send({ data: expense }); + }, + ); + + /** POST /users/:userId/one-time-expenses — Adiciona um gasto avulso */ + fastify.post<{ Params: { userId: string } }>( + '/users/:userId/one-time-expenses', + async (request, reply) => { + try { + const { userId } = oneTimeExpenseUserParamsSchema.parse(request.params); + const body = createOneTimeExpenseSchema.parse(request.body); + const expense = await oneTimeService.createOneTimeExpense(userId, body); + return reply.status(201).send({ data: expense }); + } catch (err) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + throw err; + } + }, + ); + + /** PUT /users/:userId/one-time-expenses/:id — Atualiza um gasto avulso */ + fastify.put<{ Params: { userId: string; id: string } }>( + '/users/:userId/one-time-expenses/:id', + async (request, reply) => { + try { + const { userId, id } = oneTimeExpenseParamsSchema.parse(request.params); + const body = updateOneTimeExpenseSchema.parse(request.body); + const expense = await oneTimeService.updateOneTimeExpense(userId, id, body); + + if (!expense) { + return reply.status(404).send({ error: 'Gasto avulso não encontrado.' }); + } + return reply.send({ data: expense }); + } catch (err) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + throw err; + } + }, + ); + + /** DELETE /users/:userId/one-time-expenses/:id — Remove um gasto avulso */ + fastify.delete<{ Params: { userId: string; id: string } }>( + '/users/:userId/one-time-expenses/:id', + async (request, reply) => { + const { userId, id } = oneTimeExpenseParamsSchema.parse(request.params); + const deleted = await oneTimeService.deleteOneTimeExpense(userId, id); + + if (!deleted) { + return reply.status(404).send({ error: 'Gasto avulso não encontrado.' }); + } + return reply.status(200).send({ data: deleted, message: 'Gasto avulso removido com sucesso.' }); + }, + ); +}; + +export default oneTimeExpensesRoutes; diff --git a/api/src/modules/one-time-expenses/one-time-expenses.schema.ts b/api/src/modules/one-time-expenses/one-time-expenses.schema.ts new file mode 100644 index 0000000..9e32c8d --- /dev/null +++ b/api/src/modules/one-time-expenses/one-time-expenses.schema.ts @@ -0,0 +1,54 @@ +import { z } from 'zod'; + +const CATEGORIES = [ + 'moradia', + 'alimentação', + 'transporte', + 'saúde', + 'educação', + 'lazer', + 'serviços', + 'outros', +] as const; + +export const categoryEnum = z.enum(CATEGORIES); +export const typeEnum = z.enum(['expense', 'income']); + +export const createOneTimeExpenseSchema = z.object({ + name: z.string().min(1, 'Nome é obrigatório').max(100), + amount: z.number().positive('Valor deve ser positivo'), + category: categoryEnum.default('outros'), + type: typeEnum.default('expense'), + /** Data do gasto no formato YYYY-MM-DD. Default: hoje. */ + expense_date: z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato: YYYY-MM-DD') + .optional(), + notes: z.string().max(500).nullable().optional(), +}); + +export const updateOneTimeExpenseSchema = z.object({ + name: z.string().min(1).max(100).optional(), + amount: z.number().positive().optional(), + category: categoryEnum.optional(), + type: typeEnum.optional(), + expense_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), + notes: z.string().max(500).nullable().optional(), +}); + +export const oneTimeExpenseParamsSchema = z.object({ + userId: z.coerce.number().int().positive(), + id: z.coerce.number().int().positive(), +}); + +export const oneTimeExpenseUserParamsSchema = z.object({ + userId: z.coerce.number().int().positive(), +}); + +export const oneTimeExpenseQuerySchema = z.object({ + month: z.coerce.number().int().min(1).max(12).optional(), + year: z.coerce.number().int().min(2020).optional(), +}); + +export type CreateOneTimeExpenseInput = z.infer<typeof createOneTimeExpenseSchema>; +export type UpdateOneTimeExpenseInput = z.infer<typeof updateOneTimeExpenseSchema>; diff --git a/api/src/modules/one-time-expenses/one-time-expenses.service.ts b/api/src/modules/one-time-expenses/one-time-expenses.service.ts new file mode 100644 index 0000000..c55640d --- /dev/null +++ b/api/src/modules/one-time-expenses/one-time-expenses.service.ts @@ -0,0 +1,102 @@ +import { and, between, eq } from 'drizzle-orm'; +import { db } from '@/db/client'; +import { oneTimeExpenses } from '@/db/schema'; +import type { + CreateOneTimeExpenseInput, + UpdateOneTimeExpenseInput, +} from './one-time-expenses.schema'; + +export async function findOneTimeExpensesByUser( + userId: number, + filters?: { month?: number; year?: number }, +) { + const conditions = [eq(oneTimeExpenses.user_id, userId)]; + + // Filtra pelo mês/ano usando range de datas no formato YYYY-MM-DD + if (filters?.month !== undefined && filters?.year !== undefined) { + const year = filters.year; + const month = filters.month; + const startDate = `${year}-${String(month).padStart(2, '0')}-01`; + // Último dia do mês: pega o dia 0 do mês seguinte + const lastDay = new Date(year, month, 0).getDate(); + const endDate = `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + conditions.push(between(oneTimeExpenses.expense_date, startDate, endDate)); + } else if (filters?.year !== undefined) { + const year = filters.year; + conditions.push( + between(oneTimeExpenses.expense_date, `${year}-01-01`, `${year}-12-31`), + ); + } + + return db + .select() + .from(oneTimeExpenses) + .where(and(...conditions)) + .orderBy(oneTimeExpenses.expense_date); +} + +export async function findOneTimeExpenseById(userId: number, id: number) { + const result = await db + .select() + .from(oneTimeExpenses) + .where( + and(eq(oneTimeExpenses.user_id, userId), eq(oneTimeExpenses.id, id)), + ) + .limit(1); + return result[0] ?? null; +} + +export async function createOneTimeExpense( + userId: number, + data: CreateOneTimeExpenseInput, +) { + const [created] = await db + .insert(oneTimeExpenses) + .values({ + user_id: userId, + name: data.name, + amount: data.amount, + category: data.category, + type: data.type, + expense_date: data.expense_date, + notes: data.notes ?? null, + }) + .returning(); + return created; +} + +export async function updateOneTimeExpense( + userId: number, + id: number, + data: UpdateOneTimeExpenseInput, +) { + const now = new Date().toISOString(); + + const [updated] = await db + .update(oneTimeExpenses) + .set({ + ...(data.name !== undefined && { name: data.name }), + ...(data.amount !== undefined && { amount: data.amount }), + ...(data.category !== undefined && { category: data.category }), + ...(data.type !== undefined && { type: data.type }), + ...(data.expense_date !== undefined && { expense_date: data.expense_date }), + ...(data.notes !== undefined && { notes: data.notes }), + updated_at: now, + }) + .where( + and(eq(oneTimeExpenses.user_id, userId), eq(oneTimeExpenses.id, id)), + ) + .returning(); + + return updated ?? null; +} + +export async function deleteOneTimeExpense(userId: number, id: number) { + const [deleted] = await db + .delete(oneTimeExpenses) + .where( + and(eq(oneTimeExpenses.user_id, userId), eq(oneTimeExpenses.id, id)), + ) + .returning(); + return deleted ?? null; +} diff --git a/api/src/modules/recurring-expenses/recurring-expenses.routes.ts b/api/src/modules/recurring-expenses/recurring-expenses.routes.ts new file mode 100644 index 0000000..73594e8 --- /dev/null +++ b/api/src/modules/recurring-expenses/recurring-expenses.routes.ts @@ -0,0 +1,108 @@ +import type { FastifyPluginAsync } from 'fastify'; +import { ZodError } from 'zod'; +import { + createRecurringExpenseSchema, + recurringExpenseParamsSchema, + recurringExpenseUserParamsSchema, + updateRecurringExpenseSchema, +} from './recurring-expenses.schema'; +import * as recurringService from './recurring-expenses.service'; + +const recurringExpensesRoutes: FastifyPluginAsync = async (fastify) => { + /** GET /users/:userId/recurring-expenses — Lista todos os gastos recorrentes */ + fastify.get<{ Params: { userId: string } }>( + '/users/:userId/recurring-expenses', + async (request, reply) => { + const { userId } = recurringExpenseUserParamsSchema.parse(request.params); + const result = await recurringService.findRecurringExpensesByUser(userId); + return reply.send({ data: result }); + }, + ); + + /** GET /users/:userId/recurring-expenses/:id — Busca um gasto recorrente específico */ + fastify.get<{ Params: { userId: string; id: string } }>( + '/users/:userId/recurring-expenses/:id', + async (request, reply) => { + const { userId, id } = recurringExpenseParamsSchema.parse(request.params); + const expense = await recurringService.findRecurringExpenseById(userId, id); + + if (!expense) { + return reply.status(404).send({ error: 'Gasto recorrente não encontrado.' }); + } + return reply.send({ data: expense }); + }, + ); + + /** POST /users/:userId/recurring-expenses — Cadastra um novo gasto fixo */ + fastify.post<{ Params: { userId: string } }>( + '/users/:userId/recurring-expenses', + async (request, reply) => { + try { + const { userId } = recurringExpenseUserParamsSchema.parse(request.params); + const body = createRecurringExpenseSchema.parse(request.body); + const expense = await recurringService.createRecurringExpense(userId, body); + return reply.status(201).send({ data: expense }); + } catch (err) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + throw err; + } + }, + ); + + /** PUT /users/:userId/recurring-expenses/:id — Atualiza um gasto recorrente */ + fastify.put<{ Params: { userId: string; id: string } }>( + '/users/:userId/recurring-expenses/:id', + async (request, reply) => { + try { + const { userId, id } = recurringExpenseParamsSchema.parse(request.params); + const body = updateRecurringExpenseSchema.parse(request.body); + const expense = await recurringService.updateRecurringExpense(userId, id, body); + + if (!expense) { + return reply.status(404).send({ error: 'Gasto recorrente não encontrado.' }); + } + return reply.send({ data: expense }); + } catch (err) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + throw err; + } + }, + ); + + /** + * PATCH /users/:userId/recurring-expenses/:id/toggle + * Ativa ou desativa um gasto recorrente sem precisar deletar. + */ + fastify.patch<{ Params: { userId: string; id: string } }>( + '/users/:userId/recurring-expenses/:id/toggle', + async (request, reply) => { + const { userId, id } = recurringExpenseParamsSchema.parse(request.params); + const expense = await recurringService.toggleRecurringExpense(userId, id); + + if (!expense) { + return reply.status(404).send({ error: 'Gasto recorrente não encontrado.' }); + } + return reply.send({ data: expense }); + }, + ); + + /** DELETE /users/:userId/recurring-expenses/:id — Remove um gasto recorrente */ + fastify.delete<{ Params: { userId: string; id: string } }>( + '/users/:userId/recurring-expenses/:id', + async (request, reply) => { + const { userId, id } = recurringExpenseParamsSchema.parse(request.params); + const deleted = await recurringService.deleteRecurringExpense(userId, id); + + if (!deleted) { + return reply.status(404).send({ error: 'Gasto recorrente não encontrado.' }); + } + return reply.status(200).send({ data: deleted, message: 'Gasto recorrente removido com sucesso.' }); + }, + ); +}; + +export default recurringExpensesRoutes; diff --git a/api/src/modules/recurring-expenses/recurring-expenses.schema.ts b/api/src/modules/recurring-expenses/recurring-expenses.schema.ts new file mode 100644 index 0000000..8f9d473 --- /dev/null +++ b/api/src/modules/recurring-expenses/recurring-expenses.schema.ts @@ -0,0 +1,49 @@ +import { z } from 'zod'; + +const CATEGORIES = [ + 'moradia', + 'alimentação', + 'transporte', + 'saúde', + 'educação', + 'lazer', + 'serviços', + 'outros', +] as const; + +export const categoryEnum = z.enum(CATEGORIES); +export const typeEnum = z.enum(['expense', 'income']); + +export const createRecurringExpenseSchema = z.object({ + name: z.string().min(1, 'Nome é obrigatório').max(100), + amount: z.number().positive('Valor deve ser positivo'), + category: categoryEnum.default('outros'), + type: typeEnum.default('expense'), + due_day: z.number().int().min(1).max(31).nullable().optional(), + is_active: z.boolean().default(true), + starts_at: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato: YYYY-MM-DD').optional(), + ends_at: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato: YYYY-MM-DD').nullable().optional(), +}); + +export const updateRecurringExpenseSchema = z.object({ + name: z.string().min(1).max(100).optional(), + amount: z.number().positive().optional(), + category: categoryEnum.optional(), + type: typeEnum.optional(), + due_day: z.number().int().min(1).max(31).nullable().optional(), + is_active: z.boolean().optional(), + starts_at: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), + ends_at: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(), +}); + +export const recurringExpenseParamsSchema = z.object({ + userId: z.coerce.number().int().positive(), + id: z.coerce.number().int().positive(), +}); + +export const recurringExpenseUserParamsSchema = z.object({ + userId: z.coerce.number().int().positive(), +}); + +export type CreateRecurringExpenseInput = z.infer<typeof createRecurringExpenseSchema>; +export type UpdateRecurringExpenseInput = z.infer<typeof updateRecurringExpenseSchema>; diff --git a/api/src/modules/recurring-expenses/recurring-expenses.service.ts b/api/src/modules/recurring-expenses/recurring-expenses.service.ts new file mode 100644 index 0000000..2bb9720 --- /dev/null +++ b/api/src/modules/recurring-expenses/recurring-expenses.service.ts @@ -0,0 +1,115 @@ +import { and, eq } from 'drizzle-orm'; +import { db } from '@/db/client'; +import { recurringExpenses } from '@/db/schema'; +import type { + CreateRecurringExpenseInput, + UpdateRecurringExpenseInput, +} from './recurring-expenses.schema'; + +export async function findRecurringExpensesByUser(userId: number) { + return db + .select() + .from(recurringExpenses) + .where(eq(recurringExpenses.user_id, userId)) + .orderBy(recurringExpenses.name); +} + +export async function findRecurringExpenseById(userId: number, id: number) { + const result = await db + .select() + .from(recurringExpenses) + .where( + and( + eq(recurringExpenses.user_id, userId), + eq(recurringExpenses.id, id), + ), + ) + .limit(1); + return result[0] ?? null; +} + +export async function createRecurringExpense( + userId: number, + data: CreateRecurringExpenseInput, +) { + const [created] = await db + .insert(recurringExpenses) + .values({ + user_id: userId, + name: data.name, + amount: data.amount, + category: data.category, + type: data.type, + due_day: data.due_day ?? null, + is_active: data.is_active, + starts_at: data.starts_at, + ends_at: data.ends_at ?? null, + }) + .returning(); + return created; +} + +export async function updateRecurringExpense( + userId: number, + id: number, + data: UpdateRecurringExpenseInput, +) { + const now = new Date().toISOString(); + + const [updated] = await db + .update(recurringExpenses) + .set({ + ...(data.name !== undefined && { name: data.name }), + ...(data.amount !== undefined && { amount: data.amount }), + ...(data.category !== undefined && { category: data.category }), + ...(data.type !== undefined && { type: data.type }), + ...(data.due_day !== undefined && { due_day: data.due_day }), + ...(data.is_active !== undefined && { is_active: data.is_active }), + ...(data.starts_at !== undefined && { starts_at: data.starts_at }), + ...(data.ends_at !== undefined && { ends_at: data.ends_at }), + updated_at: now, + }) + .where( + and( + eq(recurringExpenses.user_id, userId), + eq(recurringExpenses.id, id), + ), + ) + .returning(); + + return updated ?? null; +} + +export async function toggleRecurringExpense(userId: number, id: number) { + const expense = await findRecurringExpenseById(userId, id); + if (!expense) return null; + + const [updated] = await db + .update(recurringExpenses) + .set({ + is_active: !expense.is_active, + updated_at: new Date().toISOString(), + }) + .where( + and( + eq(recurringExpenses.user_id, userId), + eq(recurringExpenses.id, id), + ), + ) + .returning(); + + return updated ?? null; +} + +export async function deleteRecurringExpense(userId: number, id: number) { + const [deleted] = await db + .delete(recurringExpenses) + .where( + and( + eq(recurringExpenses.user_id, userId), + eq(recurringExpenses.id, id), + ), + ) + .returning(); + return deleted ?? null; +} diff --git a/api/src/modules/users/users.routes.ts b/api/src/modules/users/users.routes.ts new file mode 100644 index 0000000..28cf584 --- /dev/null +++ b/api/src/modules/users/users.routes.ts @@ -0,0 +1,73 @@ +import type { FastifyPluginAsync } from 'fastify'; +import { ZodError } from 'zod'; +import { + createUserSchema, + updateUserSchema, + userParamsSchema, +} from './users.schema'; +import * as usersService from './users.service'; + +const usersRoutes: FastifyPluginAsync = async (fastify) => { + /** GET /users — Lista todos os usuários */ + fastify.get('/users', async (_request, reply) => { + const result = await usersService.findAllUsers(); + return reply.send({ data: result }); + }); + + /** GET /users/:id — Busca um usuário pelo ID */ + fastify.get<{ Params: { id: string } }>('/users/:id', async (request, reply) => { + const { id } = userParamsSchema.parse(request.params); + const user = await usersService.findUserById(id); + + if (!user) { + return reply.status(404).send({ error: 'Usuário não encontrado.' }); + } + return reply.send({ data: user }); + }); + + /** POST /users — Cria um novo usuário */ + fastify.post('/users', async (request, reply) => { + try { + const body = createUserSchema.parse(request.body); + const user = await usersService.createUser(body); + return reply.status(201).send({ data: user }); + } catch (err) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + throw err; + } + }); + + /** PUT /users/:id — Atualiza um usuário (incluindo salário e override) */ + fastify.put<{ Params: { id: string } }>('/users/:id', async (request, reply) => { + try { + const { id } = userParamsSchema.parse(request.params); + const body = updateUserSchema.parse(request.body); + const user = await usersService.updateUser(id, body); + + if (!user) { + return reply.status(404).send({ error: 'Usuário não encontrado.' }); + } + return reply.send({ data: user }); + } catch (err) { + if (err instanceof ZodError) { + return reply.status(400).send({ error: 'Dados inválidos.', details: err.errors }); + } + throw err; + } + }); + + /** DELETE /users/:id — Remove um usuário (cascade nos gastos) */ + fastify.delete<{ Params: { id: string } }>('/users/:id', async (request, reply) => { + const { id } = userParamsSchema.parse(request.params); + const deleted = await usersService.deleteUser(id); + + if (!deleted) { + return reply.status(404).send({ error: 'Usuário não encontrado.' }); + } + return reply.status(200).send({ data: deleted, message: 'Usuário removido com sucesso.' }); + }); +}; + +export default usersRoutes; diff --git a/api/src/modules/users/users.schema.ts b/api/src/modules/users/users.schema.ts new file mode 100644 index 0000000..62ab514 --- /dev/null +++ b/api/src/modules/users/users.schema.ts @@ -0,0 +1,23 @@ +import { z } from 'zod'; + +export const createUserSchema = z.object({ + name: z.string().min(1, 'Nome é obrigatório').max(100), + gross_salary: z.number().nonnegative('Salário bruto deve ser >= 0'), + net_salary_override: z.number().nonnegative().nullable().optional(), +}); + +export const updateUserSchema = z.object({ + name: z.string().min(1).max(100).optional(), + gross_salary: z.number().nonnegative().optional(), + /** + * Envia `null` para limpar o override e voltar ao valor calculado automaticamente. + */ + net_salary_override: z.number().nonnegative().nullable().optional(), +}); + +export const userParamsSchema = z.object({ + id: z.coerce.number().int().positive(), +}); + +export type CreateUserInput = z.infer<typeof createUserSchema>; +export type UpdateUserInput = z.infer<typeof updateUserSchema>; diff --git a/api/src/modules/users/users.service.ts b/api/src/modules/users/users.service.ts new file mode 100644 index 0000000..a0fda40 --- /dev/null +++ b/api/src/modules/users/users.service.ts @@ -0,0 +1,53 @@ +import { eq } from 'drizzle-orm'; +import { db } from '@/db/client'; +import { users } from '@/db/schema'; +import type { CreateUserInput, UpdateUserInput } from './users.schema'; + +export async function findAllUsers() { + return db.select().from(users); +} + +export async function findUserById(id: number) { + const result = await db.select().from(users).where(eq(users.id, id)).limit(1); + return result[0] ?? null; +} + +export async function createUser(data: CreateUserInput) { + const [created] = await db + .insert(users) + .values({ + name: data.name, + gross_salary: data.gross_salary, + net_salary_override: data.net_salary_override ?? null, + }) + .returning(); + return created; +} + +export async function updateUser(id: number, data: UpdateUserInput) { + const now = new Date().toISOString(); + + const [updated] = await db + .update(users) + .set({ + ...(data.name !== undefined && { name: data.name }), + ...(data.gross_salary !== undefined && { gross_salary: data.gross_salary }), + // Permite setar null explicitamente (para remover o override) + ...(data.net_salary_override !== undefined && { + net_salary_override: data.net_salary_override, + }), + updated_at: now, + }) + .where(eq(users.id, id)) + .returning(); + + return updated ?? null; +} + +export async function deleteUser(id: number) { + const [deleted] = await db + .delete(users) + .where(eq(users.id, id)) + .returning(); + return deleted ?? null; +} diff --git a/api/src/plugins/cors.ts b/api/src/plugins/cors.ts new file mode 100644 index 0000000..5398e39 --- /dev/null +++ b/api/src/plugins/cors.ts @@ -0,0 +1,18 @@ +import cors from '@fastify/cors'; +import type { FastifyPluginAsync } from 'fastify'; + +/** + * Plugin de CORS para o Fastify. + * Em produção, substitua o `origin: true` pelo domínio/IP do seu app mobile + * ou pelo endereço da sua VPS. + */ +const corsPlugin: FastifyPluginAsync = async (fastify) => { + fastify.register(cors, { + // Em dev: aceita qualquer origem + // Em prod: restrinja para o IP/domínio conhecido + origin: process.env.CORS_ORIGIN ?? true, + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + }); +}; + +export default corsPlugin; diff --git a/api/src/server.ts b/api/src/server.ts new file mode 100644 index 0000000..6b3fd46 --- /dev/null +++ b/api/src/server.ts @@ -0,0 +1,89 @@ +import Fastify from 'fastify'; +import corsPlugin from './plugins/cors'; +import fastifyJwt from '@fastify/jwt'; +import authRoutes from './modules/auth/auth.routes'; +import dashboardRoutes from './modules/dashboard/dashboard.routes'; +import incomesRoutes from './modules/incomes/incomes.routes'; +import oneTimeExpensesRoutes from './modules/one-time-expenses/one-time-expenses.routes'; +import recurringExpensesRoutes from './modules/recurring-expenses/recurring-expenses.routes'; +import usersRoutes from './modules/users/users.routes'; + +const PORT = Number(process.env.PORT) || 3333; +const HOST = process.env.HOST ?? '0.0.0.0'; + +const fastify = Fastify({ + logger: { + transport: + process.env.NODE_ENV === 'development' + ? { target: 'pino-pretty', options: { colorize: true } } + : undefined, + }, +}); + +// --------------------------------------------------------------------------- +// Plugins globais +// --------------------------------------------------------------------------- +fastify.register(corsPlugin); + +fastify.register(fastifyJwt, { + secret: process.env.JWT_SECRET || 'super-secret-yaba-jwt-key-2026', +}); + +// --------------------------------------------------------------------------- +// JWT Middleware +// --------------------------------------------------------------------------- +fastify.addHook('onRequest', async (request, reply) => { + if (request.url.includes('/api/v1/auth') || request.url.startsWith('/health')) { + return; // Pula autenticação para rotas públicas + } + try { + await request.jwtVerify(); + } catch (err) { + return reply.status(401).send({ error: 'Não autorizado.' }); + } +}); + +// --------------------------------------------------------------------------- +// Rotas com prefixo /api/v1 +// --------------------------------------------------------------------------- +const API_PREFIX = '/api/v1'; + +fastify.register(authRoutes, { prefix: API_PREFIX }); +fastify.register(usersRoutes, { prefix: API_PREFIX }); +fastify.register(incomesRoutes, { prefix: API_PREFIX }); +fastify.register(recurringExpensesRoutes, { prefix: API_PREFIX }); +fastify.register(oneTimeExpensesRoutes, { prefix: API_PREFIX }); +fastify.register(dashboardRoutes, { prefix: API_PREFIX }); + +// --------------------------------------------------------------------------- +// Health check +// --------------------------------------------------------------------------- +fastify.get('/health', async () => ({ + status: 'ok', + timestamp: new Date().toISOString(), + version: '1.0.0', +})); + +// --------------------------------------------------------------------------- +// Handler de erros global +// --------------------------------------------------------------------------- +fastify.setErrorHandler((error, _request, reply) => { + fastify.log.error(error); + const statusCode = error.statusCode ?? 500; + reply.status(statusCode).send({ + error: statusCode >= 500 ? 'Erro interno do servidor.' : error.message, + ...(process.env.NODE_ENV === 'development' && { stack: error.stack }), + }); +}); + +// --------------------------------------------------------------------------- +// Start +// --------------------------------------------------------------------------- +try { + await fastify.listen({ port: PORT, host: HOST }); + console.log(`🚀 YABA API rodando em http://${HOST}:${PORT}`); + console.log(`📖 Health check: http://${HOST}:${PORT}/health`); +} catch (err) { + fastify.log.error(err); + process.exit(1); +} diff --git a/api/src/utils/tax-calculator.ts b/api/src/utils/tax-calculator.ts new file mode 100644 index 0000000..36ffc7e --- /dev/null +++ b/api/src/utils/tax-calculator.ts @@ -0,0 +1,111 @@ +/** + * Calculadora de impostos brasileiros (INSS + IRRF). + * Tabelas vigentes: 2025. + * + * IMPORTANTE: As alíquotas e faixas mudam anualmente por decreto. + * Verifique a legislação vigente a cada ano fiscal. + * + * Fonte: Portaria MPS / IN RFB 2.141/2023 e atualizações. + */ + +// --------------------------------------------------------------------------- +// INSS — Tabela Progressiva 2025 +// --------------------------------------------------------------------------- +const INSS_TABLE = [ + { upTo: 1_518.0, rate: 0.075 }, + { upTo: 2_793.88, rate: 0.09 }, + { upTo: 4_190.83, rate: 0.12 }, + { upTo: 8_157.41, rate: 0.14 }, +] as const; + +const INSS_TETO = 908.86; // Valor máximo de desconto do INSS + +/** + * Calcula o desconto do INSS com a tabela progressiva. + * Cada faixa incide apenas sobre a parte do salário dentro dela. + */ +export function calcularINSS(salarioBruto: number): number { + if (salarioBruto <= 0) return 0; + if (salarioBruto > INSS_TABLE[INSS_TABLE.length - 1].upTo) return INSS_TETO; + + let inss = 0; + let salarioRestante = salarioBruto; + let limiteAnterior = 0; + + for (const faixa of INSS_TABLE) { + if (salarioRestante <= 0) break; + + const baseNaFaixa = Math.min(salarioBruto, faixa.upTo) - limiteAnterior; + inss += baseNaFaixa * faixa.rate; + + salarioRestante -= baseNaFaixa; + limiteAnterior = faixa.upTo; + } + + return parseFloat(inss.toFixed(2)); +} + +// --------------------------------------------------------------------------- +// IRRF — Tabela Progressiva 2025 +// --------------------------------------------------------------------------- +interface FaixaIRRF { + upTo: number | null; // null = sem limite superior + rate: number; + deduction: number; +} + +const IRRF_TABLE: FaixaIRRF[] = [ + { upTo: 2_259.2, rate: 0, deduction: 0 }, + { upTo: 2_826.65, rate: 0.075, deduction: 169.44 }, + { upTo: 3_751.05, rate: 0.15, deduction: 381.44 }, + { upTo: 4_664.68, rate: 0.225, deduction: 662.77 }, + { upTo: null, rate: 0.275, deduction: 896.0 }, +]; + +/** + * Calcula o desconto do IRRF. + * Base de cálculo: Salário Bruto − INSS (sem considerar dependentes). + */ +export function calcularIRRF(salarioBruto: number, inss: number): number { + const baseCalculo = salarioBruto - inss; + if (baseCalculo <= 0) return 0; + + const faixa = IRRF_TABLE.find( + (f) => f.upTo === null || baseCalculo <= f.upTo, + )!; + + const irrf = baseCalculo * faixa.rate - faixa.deduction; + return parseFloat(Math.max(0, irrf).toFixed(2)); +} + +// --------------------------------------------------------------------------- +// Resultado consolidado +// --------------------------------------------------------------------------- +export interface ResultadoCalculo { + salarioBruto: number; + descontoINSS: number; + descontoIRRF: number; + totalDescontos: number; + salarioLiquidoCalculado: number; +} + +/** + * Calcula o salário líquido completo a partir do salário bruto. + * Retorna um objeto com o detalhamento de cada desconto. + */ +export function calcularSalarioLiquido(salarioBruto: number): ResultadoCalculo { + const descontoINSS = calcularINSS(salarioBruto); + const descontoIRRF = calcularIRRF(salarioBruto, descontoINSS); + const totalDescontos = parseFloat((descontoINSS + descontoIRRF).toFixed(2)); + const salarioLiquidoCalculado = parseFloat( + (salarioBruto - totalDescontos).toFixed(2), + ); + + return { + salarioBruto, + descontoINSS, + descontoIRRF, + totalDescontos, + salarioLiquidoCalculado, + }; +} diff --git a/api/tsconfig.json b/api/tsconfig.json new file mode 100644 index 0000000..51e98fd --- /dev/null +++ b/api/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "types": ["bun-types"], + "strict": true, + "skipLibCheck": true, + "outDir": "./dist", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*", "drizzle.config.ts"], + "exclude": ["node_modules", "dist"] +} @@ -1,14 +1,19 @@ { "expo": { "name": "YABA", - "slug": "YABA", + "slug": "yaba", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/images/icon.png", - "scheme": "YABA", + "scheme": "yaba", "userInterfaceStyle": "automatic", "ios": { - "icon": "./assets/expo.icon" + "icon": "./assets/expo.icon", + "bundleIdentifier": "com.lucas.yaba", + "infoPlist": { + "ITSAppUsesNonExemptEncryption": false, + "NSAllowsArbitraryLoads": true + } }, "android": { "adaptiveIcon": { @@ -22,7 +27,7 @@ }, "web": { "output": "static", - "favicon": "./assets/images/favicon.png" + "favicon": "./assets/images/icon.png" }, "plugins": [ "expo-router", @@ -36,7 +41,15 @@ ], "expo-secure-store", "expo-font", - "expo-status-bar" + "expo-status-bar", + [ + "expo-build-properties", + { + "android": { + "usesCleartextTraffic": true + } + } + ] ], "experiments": { "typedRoutes": true, diff --git a/assets/.DS_Store b/assets/.DS_Store Binary files differindex 59fbaf7..b2256f9 100644 --- a/assets/.DS_Store +++ b/assets/.DS_Store @@ -11,6 +11,7 @@ "@react-navigation/bottom-tabs": "^7.18.16", "expo": "~57.0.12", "expo-blur": "~57.0.2", + "expo-build-properties": "~57.0.11", "expo-constants": "~57.0.10", "expo-dev-client": "~57.0.11", "expo-device": "~57.0.1", @@ -587,6 +588,8 @@ "expo-blur": ["expo-blur@57.0.2", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-Aoud8H8lmlNkbRufyvRLefmGFELdBf1n5Te/Xm+Zx8ORINH+aXL+gKb5mbftFSha860+I7pMArz77TBYz8HDVg=="], + "expo-build-properties": ["expo-build-properties@57.0.11", "", { "dependencies": { "@expo/schema-utils": "^57.0.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-+YlioKtW5rNVoGMGlw2t0pY198vjGYN/JcCQyc2sKhJYh3w2C4Lu72tkQAzNd/lLRIkUXTU7J0ICRQ8EaXTtbQ=="], + "expo-constants": ["expo-constants@57.0.10", "", { "dependencies": { "@expo/env": "~2.4.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-GCDXYEsloBfouMdT3BzoGhAkcLnYxEFNLQoSbNKIvIrD9FY5MmSeWuvRSveJdOiuydwQY2iH6hy020vTaQflCQ=="], "expo-dev-client": ["expo-dev-client@57.0.11", "", { "dependencies": { "expo-dev-launcher": "~57.0.11", "expo-dev-menu": "~57.0.11", "expo-dev-menu-interface": "~57.0.0", "expo-manifests": "~57.0.1", "expo-updates-interface": "~57.0.1" }, "peerDependencies": { "expo": "*" } }, "sha512-IHatS97Vl0eQT8IG1Tsc10qDoyItxwjOw9AXLAbub+3uU7KljcqnXiCWRlhsI5KTjWV9/Dboc8psgiRb7oXXQw=="], @@ -6,10 +6,16 @@ "build": { "development": { "developmentClient": true, - "distribution": "internal" + "distribution": "internal", + "android": { + "buildType": "apk" + } }, "preview": { - "distribution": "internal" + "distribution": "internal", + "android": { + "buildType": "apk" + } }, "production": { "autoIncrement": true diff --git a/package.json b/package.json index 73ebdb4..58ea41b 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@react-navigation/bottom-tabs": "^7.18.16", "expo": "~57.0.12", "expo-blur": "~57.0.2", + "expo-build-properties": "~57.0.11", "expo-constants": "~57.0.10", "expo-dev-client": "~57.0.11", "expo-device": "~57.0.1", diff --git a/src/app/login.tsx b/src/app/login.tsx index 15ec3be..16b6777 100644 --- a/src/app/login.tsx +++ b/src/app/login.tsx @@ -43,8 +43,8 @@ export default function LoginScreen() { } else { router.replace("/"); } - } catch (error) { - Alert.alert("Erro", "Falha ao fazer login. Verifique suas credenciais e tente novamente."); + } catch (error: any) { + Alert.alert("Erro", error.message || "Falha ao fazer login. Verifique suas credenciais e tente novamente."); } finally { setIsLoading(false); } diff --git a/src/app/register.tsx b/src/app/register.tsx index aac588d..108761f 100644 --- a/src/app/register.tsx +++ b/src/app/register.tsx @@ -38,7 +38,7 @@ export default function RegisterScreen() { router.replace('/'); } } catch (err: any) { - Alert.alert('Erro no cadastro', 'Verifique seus dados.'); + Alert.alert('Erro no cadastro', err.message || 'Verifique seus dados.'); setIsLoading(false); } } diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index d2fd7a9..2d0a725 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -75,6 +75,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { async function register(data: RegisterPayload) { const res = await apiRegister(data); + console.log('Register response:', res); await SecureStore.setItemAsync('yaba_token', res.token); await SecureStore.setItemAsync('yaba_user', JSON.stringify(res.user)); setApiToken(res.token); diff --git a/src/services/api.ts b/src/services/api.ts index edd376e..d2a4f73 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -58,6 +58,7 @@ export async function apiRegister(data: any): Promise<{ user: User; token: strin method: 'POST', body: JSON.stringify(data), }); + console.log('API Register response:', result); return result.data; } |